]>
Commit | Line | Data |
---|---|---|
18f8bd28 CP |
1 | /* |
2 | nterfacer relay4 | |
3 | Copyright (C) 2004-2005 Chris Porter. | |
4 | ||
6bc02131 CP |
5 | v1.14 |
6 | - made it return an error instead of a timeout when sending to local users | |
3193eda0 CP |
7 | v1.13 |
8 | - found a load of stuff on froo's box | |
99f1fd92 CP |
9 | v1.12 |
10 | - added nickprefix | |
95ee3dac CP |
11 | v1.11 |
12 | - made sure stats buffer was checked (no security problem, just helps pauline) | |
16d29ce2 CP |
13 | v1.10 |
14 | - added stats support | |
af0c1aed CP |
15 | v1.06 |
16 | - found \r bug, and another format string problem... | |
18f8bd28 CP |
17 | v1.05 |
18 | - oops, fixed quit bug | |
19 | - oops, forgot about rehash hook | |
20 | v1.04 | |
21 | - added S support, though it's gross | |
22 | v1.03 | |
23 | - fixed audit found format string vunerability | |
24 | v1.02 | |
25 | - split O username and password from .h file into config file | |
26 | - added rehash support | |
27 | v1.01 | |
28 | - item->schedule was not unset and checked upon timeout | |
29 | */ | |
30 | ||
31 | #include <stdio.h> | |
32 | #include <string.h> | |
af0c1aed | 33 | #include <stdarg.h> |
18f8bd28 CP |
34 | |
35 | #include "../lib/irc_string.h" | |
c5c119a6 | 36 | #include "../lib/strlfunc.h" |
18f8bd28 CP |
37 | #include "../core/schedule.h" |
38 | #include "../irc/irc.h" | |
39 | #include "../core/config.h" | |
87698d77 | 40 | #include "../lib/version.h" |
18f8bd28 CP |
41 | #include "library.h" |
42 | #include "nterfacer_relay.h" | |
43 | ||
70b0a4e5 | 44 | MODULE_VERSION(""); |
87698d77 | 45 | |
18f8bd28 CP |
46 | struct service_node *node; |
47 | sstring *ousername, *opassword; | |
48 | regex onickname; | |
49 | int number = 1; | |
50 | char started = 0; | |
c5c119a6 | 51 | char nickprefix[NICKLEN - 5 + 1]; |
3ee42b39 | 52 | static unsigned long userid; |
18f8bd28 | 53 | |
16d29ce2 CP |
54 | int stats_handler(struct rline *ri, int argc, char **argv); |
55 | ||
18f8bd28 CP |
56 | void _init(void) { |
57 | if(!load_config()) | |
58 | return; | |
59 | ||
60 | node = register_service("R"); | |
61 | MemCheck(node); | |
62 | ||
63 | register_handler(node, "relay", 4, relay_handler); | |
16d29ce2 CP |
64 | register_handler(node, "stats", 2, stats_handler); |
65 | ||
3193eda0 | 66 | registerhook(HOOK_NICK_LOSTNICK, &relay_quits); |
18f8bd28 CP |
67 | registerhook(HOOK_IRC_DISCON, &relay_disconnect); |
68 | registerhook(HOOK_CORE_REHASH, &relay_rehash); | |
69 | ||
70 | started = 1; | |
71 | } | |
72 | ||
73 | int load_config(void) { | |
74 | /* eww, I don't know how to make this any nicer! */ | |
c5c119a6 | 75 | sstring *ou, *op, *on, *opr; |
18f8bd28 CP |
76 | regex newregex; |
77 | ||
78 | memset(&newregex, 0, sizeof(newregex)); | |
79 | ||
3ee42b39 CP |
80 | userid = getcopyconfigitemintpositive("nterfacer", "serviceuserid", 0); |
81 | if(!userid) { | |
82 | Error("nterfacer_relay", ERR_WARNING, "No user id specified (serviceuserid)!"); | |
83 | return 0; | |
84 | } | |
85 | ||
c5c119a6 CP |
86 | opr = getcopyconfigitem("nterfacer", "nickprefix", DEFAULT_NICK_PREFIX, sizeof(nickprefix) - 1); /* the terminator is included in nickprefix */ |
87 | strlcpy(nickprefix, (opr&&opr->content)?opr->content:DEFAULT_NICK_PREFIX, sizeof(nickprefix)); | |
88 | freesstring(opr); /* works fine with NULL */ | |
89 | ||
18f8bd28 CP |
90 | ou = getcopyconfigitem("nterfacer", "serviceusername", "nterfacer", 100); |
91 | op = getcopyconfigitem("nterfacer", "servicepassword", "setme", 100); | |
ea658f59 | 92 | on = getcopyconfigitem("nterfacer", "servicenickname", "^O$", 100); |
18f8bd28 CP |
93 | |
94 | if(on) { | |
95 | int erroroffset; | |
96 | const char *rerror; | |
97 | newregex.phrase = pcre_compile(on->content, PCRE_CASELESS, &rerror, &erroroffset, NULL); | |
98 | if(newregex.phrase) { | |
99 | newregex.hint = pcre_study(newregex.phrase, 0, &rerror); | |
100 | if(rerror) { | |
101 | pcre_free(newregex.phrase); | |
102 | newregex.phrase = NULL; | |
103 | } | |
104 | } | |
105 | } | |
106 | ||
107 | if(!ou || !op || !on || !newregex.phrase) { | |
c5c119a6 CP |
108 | freesstring(ou); |
109 | freesstring(op); | |
110 | freesstring(on); | |
18f8bd28 CP |
111 | if(newregex.phrase) { |
112 | pcre_free(newregex.phrase); | |
113 | if(newregex.hint) | |
114 | pcre_free(newregex.hint); | |
115 | } | |
116 | MemError(); | |
117 | return 0; | |
118 | } | |
119 | ||
120 | if(started) { | |
121 | if(ousername) | |
122 | freesstring(ousername); | |
123 | if(opassword) | |
124 | freesstring(opassword); | |
125 | if(onickname.phrase) { | |
126 | pcre_free(onickname.phrase); | |
127 | if(onickname.hint) | |
128 | pcre_free(onickname.hint); | |
129 | } | |
130 | } | |
131 | ||
132 | freesstring(on); | |
133 | ||
134 | ousername = ou; | |
135 | opassword = op; | |
136 | memcpy(&onickname, &newregex, sizeof(onickname)); | |
137 | return 1; | |
138 | } | |
139 | ||
140 | void relay_rehash(int hook, void *args) { | |
141 | load_config(); | |
142 | } | |
143 | ||
144 | void _fini(void) { | |
145 | struct rld *np, *lp; | |
146 | ||
147 | if(!started) | |
148 | return; | |
149 | ||
150 | if(ousername) | |
151 | freesstring(ousername); | |
152 | if(opassword) | |
153 | freesstring(opassword); | |
154 | if(onickname.phrase) { | |
155 | pcre_free(onickname.phrase); | |
156 | if(onickname.hint) | |
157 | pcre_free(onickname.hint); | |
158 | } | |
159 | ||
3193eda0 | 160 | deregisterhook(HOOK_NICK_LOSTNICK, &relay_quits); |
18f8bd28 CP |
161 | deregisterhook(HOOK_IRC_DISCON, &relay_disconnect); |
162 | deregisterhook(HOOK_CORE_REHASH, &relay_rehash); | |
163 | ||
164 | for(np=list;np;) { | |
165 | lp = np->next; | |
166 | ri_error(np->rline, RELAY_UNLOADED, "Module was unloaded"); | |
167 | dispose_rld(np); | |
168 | ||
169 | np = lp; | |
170 | } | |
171 | ||
172 | if(node) | |
173 | deregister_service(node); | |
174 | } | |
175 | ||
16d29ce2 CP |
176 | nick *relay_getnick(void) { |
177 | static char ournick[NICKLEN + 1]; | |
178 | int attempts = 100; | |
af0c1aed | 179 | |
16d29ce2 | 180 | do { |
c5c119a6 | 181 | snprintf(ournick, sizeof(ournick), "%s%d", nickprefix, number++); |
16d29ce2 CP |
182 | if(number > 60000) |
183 | number = 1; | |
af0c1aed | 184 | |
16d29ce2 | 185 | if(!getnickbynick(ournick)) |
c4ffdb9b | 186 | return registerlocaluserflags(ournick, "nterf", "cer", "nterfacer relay", "nterfacer", userid, 0, UMODE_SERVICE | UMODE_DEAF | UMODE_OPER | UMODE_INV | UMODE_ACCOUNT, &relay_messages); |
16d29ce2 CP |
187 | |
188 | attempts--; | |
189 | } while(attempts > 0); | |
190 | ||
191 | return NULL; | |
af0c1aed CP |
192 | } |
193 | ||
18f8bd28 | 194 | int relay_handler(struct rline *ri, int argc, char **argv) { |
18f8bd28 | 195 | struct rld *np; |
16d29ce2 | 196 | int lines = 0, i; |
18f8bd28 CP |
197 | nick *dest; |
198 | const char *rerror; | |
199 | int erroroffset; | |
200 | ||
201 | if(!connected) | |
202 | return ri_error(ri, RELAY_NOT_ON_IRC, "Not currently on IRC"); | |
203 | ||
204 | if(argv[0][0] == '1') { | |
205 | lines = positive_atoi(argv[1]); | |
206 | if(lines < 1) | |
207 | return ri_error(ri, RELAY_PARSE_ERROR, "Parse error"); | |
208 | } else if(argv[0][0] != '2') { | |
209 | return ri_error(ri, RELAY_PARSE_ERROR, "Parse error"); | |
210 | } | |
211 | ||
212 | dest = getnickbynick(argv[2]); | |
213 | if(!dest) | |
214 | return ri_error(ri, RELAY_NICK_NOT_FOUND, "Nickname not found!"); | |
215 | ||
6bc02131 CP |
216 | if(homeserver(dest->numeric) == mylongnum) |
217 | return ri_error(ri, RELAY_LOCAL_USER, "Cannot relay to local users"); | |
218 | ||
16d29ce2 CP |
219 | for(i=3;i<argc;i++) |
220 | if(strchr(argv[i], '\r')) | |
221 | return ri_error(ri, RELAY_INVALID_CHARS, "Invalid character in input"); | |
222 | ||
5ab07198 | 223 | np = ntmalloc(sizeof(struct rld)); |
18f8bd28 CP |
224 | MemCheckR(np, ri_error(ri, RELAY_MEMORY_ERROR, "Memory error")); |
225 | ||
226 | np->rline = ri; | |
227 | if(!lines) { | |
228 | np->termination.pcre.phrase = pcre_compile(argv[1], PCRE_FLAGS, &rerror, &erroroffset, NULL); | |
229 | if(!np->termination.pcre.phrase) { | |
230 | MemError(); | |
5ab07198 | 231 | ntfree(np); |
18f8bd28 CP |
232 | return ri_error(ri, RELAY_REGEX_ERROR, "Regex compliation error"); |
233 | } | |
234 | np->termination.pcre.hint = pcre_study(np->termination.pcre.phrase, 0, &rerror); | |
235 | if(rerror) { | |
236 | pcre_free(np->termination.pcre.phrase); | |
5ab07198 | 237 | ntfree(np); |
18f8bd28 CP |
238 | return ri_error(ri, RELAY_REGEX_HINT_ERROR, "Regex hint error"); |
239 | } | |
240 | ||
241 | np->mode = MODE_TAG; | |
242 | } else { | |
243 | np->mode = MODE_LINES; | |
244 | np->termination.remaining_lines = lines; | |
245 | } | |
246 | ||
16d29ce2 | 247 | if(!(np->nick = relay_getnick())) { |
18f8bd28 CP |
248 | if(!lines) { |
249 | pcre_free(np->termination.pcre.phrase); | |
250 | if(np->termination.pcre.hint) | |
251 | pcre_free(np->termination.pcre.hint); | |
252 | } | |
5ab07198 | 253 | ntfree(np); |
18f8bd28 CP |
254 | return ri_error(ri, RELAY_NICK_ERROR, "Unable to get a nickname!"); |
255 | } | |
256 | ||
18f8bd28 CP |
257 | np->schedule = scheduleoneshot(time(NULL) + MESSAGE_TIMEOUT, &relay_timeout, np); |
258 | if(!np->schedule) { | |
259 | MemError(); | |
260 | if(!lines) { | |
261 | pcre_free(np->termination.pcre.phrase); | |
262 | if(np->termination.pcre.hint) | |
263 | pcre_free(np->termination.pcre.hint); | |
264 | } | |
265 | deregisterlocaluser(np->nick, NULL); | |
5ab07198 | 266 | ntfree(np); |
16d29ce2 | 267 | return ri_error(ri, RELAY_MEMORY_ERROR, "Memory error"); |
18f8bd28 CP |
268 | } |
269 | ||
270 | np->dest = dest; | |
271 | ||
272 | np->next = list; | |
273 | list = np; | |
274 | ||
275 | if(pcre_exec(onickname.phrase, onickname.hint, dest->nick, strlen(dest->nick), 0, 0, NULL, 0) >= 0) { | |
276 | np->mode|=MODE_IS_O; | |
16d29ce2 | 277 | sendsecuremessagetouser(np->nick, dest, serverlist[dest->numeric>>18].name->content, "AUTH %s %s", ousername->content, opassword->content); |
18f8bd28 CP |
278 | } |
279 | ||
280 | for(i=3;i<argc;i++) | |
16d29ce2 CP |
281 | sendmessagetouser(np->nick, dest, "%s", argv[i]); |
282 | ||
283 | return RE_OK; | |
284 | } | |
285 | ||
286 | int stats_handler(struct rline *ri, int argc, char **argv) { | |
287 | struct rld *np; | |
288 | char *server = argv[0], *command = argv[1], *more; | |
289 | int serverid = findserver(server); | |
290 | char targetnumeric[3]; | |
291 | ||
292 | if(serverid == -1) | |
293 | return ri_error(ri, RELAY_SERVER_NOT_FOUND, "Server not found"); | |
294 | ||
295 | if(!command || !command[0] || (command[0] == ' ') || (command[0] == '\r') || (command[0] == ':')) | |
296 | return ri_error(ri, RELAY_INVALID_COMMAND, "Invalid command"); | |
297 | ||
298 | if(argc > 2) { | |
299 | more = argv[2]; | |
300 | if(strchr(more, '\r')) | |
301 | return ri_error(ri, RELAY_INVALID_CHARS, "Invalid character in input"); | |
302 | } else { | |
303 | more = NULL; | |
304 | } | |
305 | ||
5ab07198 | 306 | np = ntmalloc(sizeof(struct rld)); |
16d29ce2 CP |
307 | MemCheckR(np, ri_error(ri, RELAY_MEMORY_ERROR, "Memory error")); |
308 | ||
309 | np->rline = ri; | |
310 | np->mode = MODE_STATS; | |
311 | np->dest = NULL; | |
312 | ||
313 | if(!(np->nick = relay_getnick())) { | |
5ab07198 | 314 | ntfree(np); |
16d29ce2 CP |
315 | return ri_error(ri, RELAY_NICK_ERROR, "Unable to get a nickname!"); |
316 | } | |
317 | ||
318 | np->schedule = scheduleoneshot(time(NULL) + MESSAGE_TIMEOUT, &relay_timeout, np); | |
319 | if(!np->schedule) { | |
320 | MemError(); | |
321 | deregisterlocaluser(np->nick, NULL); | |
5ab07198 | 322 | ntfree(np); |
16d29ce2 CP |
323 | } |
324 | ||
325 | np->next = list; | |
326 | list = np; | |
327 | ||
328 | memcpy(targetnumeric, longtonumeric(serverid, 2), 3); | |
329 | targetnumeric[2] = '\0'; | |
330 | if(more) { | |
331 | irc_send("%s R %c %s :%s\r\n", longtonumeric(np->nick->numeric,5), command[0], targetnumeric, more); | |
332 | } else { | |
333 | irc_send("%s R %c :%s\r\n", longtonumeric(np->nick->numeric,5), command[0], targetnumeric); | |
334 | } | |
18f8bd28 CP |
335 | |
336 | return RE_OK; | |
337 | } | |
338 | ||
339 | void relay_messages(nick *target, int messagetype, void **args) { | |
340 | struct rld *item, *prev = NULL; | |
341 | for(item=list;item;prev=item,item=item->next) | |
342 | if(item->nick == target) | |
343 | break; | |
344 | ||
345 | if(!item) | |
346 | return; | |
347 | ||
348 | switch(messagetype) { | |
349 | case LU_PRIVNOTICE: | |
350 | case LU_PRIVMSG: | |
351 | case LU_SECUREMSG: | |
16d29ce2 | 352 | if(!item->dest || (item->dest != (nick *)args[0])) |
18f8bd28 CP |
353 | return; |
354 | ||
355 | if((item->mode & MODE_IS_O) && !(item->mode & MODE_O_AUTH2)) { | |
356 | if(item->mode & MODE_O_AUTH1) { | |
357 | if(!strncmp((char *)args[1], "Your authlevel is ", 18)) { | |
358 | item->mode|=MODE_O_AUTH2; | |
359 | } else { | |
360 | ri_error(item->rline, RELAY_O_AUTH_ERROR, "Unable to auth with O"); | |
361 | dispose_rld_prev(item, prev); | |
362 | } | |
363 | } else { | |
364 | if(!strcmp((char *)args[1], "Auth successful.")) { | |
365 | item->mode|=MODE_O_AUTH1; | |
366 | } else { | |
367 | ri_error(item->rline, RELAY_O_AUTH_ERROR, "Unable to auth with O"); | |
368 | dispose_rld_prev(item, prev); | |
369 | } | |
370 | } | |
371 | } else if(ri_append(item->rline, "%s", (char *)args[1]) == BF_OVER) { | |
372 | ri_error(item->rline, BF_OVER, "Buffer overflow"); | |
373 | dispose_rld_prev(item, prev); | |
374 | } else { | |
375 | if(((item->mode & MODE_LINES) && (!--item->termination.remaining_lines)) || ((item->mode & MODE_TAG) && (pcre_exec(item->termination.pcre.phrase, item->termination.pcre.hint, (char *)args[1], strlen((char *)args[1]), 0, 0, NULL, 0) >= 0))) { | |
376 | ri_final(item->rline); | |
377 | dispose_rld_prev(item, prev); | |
378 | } | |
379 | } | |
380 | break; | |
381 | ||
382 | case LU_KILLED: | |
383 | ri_error(item->rline, RELAY_KILLED, "Killed"); | |
b00ef0b1 CP |
384 | if(prev) { |
385 | prev->next = item->next; | |
386 | } else { | |
387 | list = item->next; | |
388 | } | |
389 | dispose_rld_dontquit(item, 1); | |
18f8bd28 | 390 | break; |
16d29ce2 CP |
391 | |
392 | case LU_STATS: | |
393 | if(item->mode != MODE_STATS) | |
394 | return; | |
95ee3dac CP |
395 | if(ri_append(item->rline, "%s", (char *)args[2]) == BF_OVER) { |
396 | ri_error(item->rline, BF_OVER, "Buffer overflow"); | |
397 | dispose_rld_prev(item, prev); | |
398 | } | |
16d29ce2 CP |
399 | break; |
400 | ||
401 | case LU_STATS_END: | |
402 | if(item->mode != MODE_STATS) | |
403 | return; | |
404 | ri_final(item->rline); | |
405 | dispose_rld_prev(item, prev); | |
406 | break; | |
18f8bd28 CP |
407 | } |
408 | } | |
409 | ||
410 | void dispose_rld_prev(struct rld *item, struct rld *prev) { | |
411 | if(prev) { | |
412 | prev->next = item->next; | |
413 | } else { | |
414 | list = item->next; | |
415 | } | |
416 | dispose_rld(item); | |
417 | } | |
418 | ||
419 | void dispose_rld_dontquit(struct rld *item, int dontquit) { | |
420 | if(item->schedule) | |
421 | deleteschedule(item->schedule, &relay_timeout, item); | |
b00ef0b1 | 422 | if(!dontquit) |
18f8bd28 CP |
423 | deregisterlocaluser(item->nick, NULL); |
424 | if(item->mode & MODE_TAG) { | |
425 | pcre_free(item->termination.pcre.phrase); | |
426 | if(item->termination.pcre.hint) | |
427 | pcre_free(item->termination.pcre.hint); | |
428 | } | |
5ab07198 | 429 | ntfree(item); |
18f8bd28 CP |
430 | } |
431 | ||
432 | void relay_timeout(void *arg) { | |
433 | struct rld *item = (struct rld *)arg, *lp = NULL, *np; | |
434 | ||
435 | item->schedule = NULL; | |
436 | ||
437 | ri_error(item->rline, RELAY_TIMEOUT, "Timed out"); | |
438 | ||
439 | for(np=list;np;lp=np,np=np->next) { | |
440 | if(np == item) { | |
441 | dispose_rld_prev(item, lp); | |
442 | break; | |
443 | } | |
444 | } | |
445 | } | |
446 | ||
447 | void relay_quits(int hook, void *args) { | |
3193eda0 | 448 | nick *np = (nick *)args; |
18f8bd28 CP |
449 | struct rld *cp, *lp; |
450 | ||
451 | for(lp=NULL,cp=list;cp;) { | |
452 | if(cp->dest == np) { | |
453 | ri_error(cp->rline, RELAY_TARGET_LEFT, "Target left QuakeNet"); | |
454 | if(lp) { | |
455 | lp->next = cp->next; | |
b00ef0b1 | 456 | dispose_rld(cp); |
18f8bd28 CP |
457 | cp = lp->next; |
458 | } else { | |
459 | list = cp->next; | |
b00ef0b1 | 460 | dispose_rld(cp); |
18f8bd28 CP |
461 | cp = list; |
462 | } | |
463 | } else { | |
464 | lp = cp; | |
465 | cp = cp->next; | |
466 | } | |
467 | } | |
468 | } | |
469 | ||
470 | void relay_disconnect(int hook, void *args) { | |
471 | struct rld *np, *lp = NULL; | |
472 | while(list) { | |
473 | ri_error(list->rline, RELAY_DISCONNECTED, "Disconnected from IRC"); | |
474 | dispose_rld_prev(list, NULL); | |
475 | ||
476 | np = lp; | |
477 | } | |
478 | } |