]>
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;) { | |
ecc4fa34 | 165 | struct rline *ri = np->rline; |
18f8bd28 | 166 | lp = np->next; |
ecc4fa34 | 167 | |
18f8bd28 | 168 | dispose_rld(np); |
ecc4fa34 | 169 | ri_error(ri, RELAY_UNLOADED, "Module was unloaded"); |
18f8bd28 CP |
170 | |
171 | np = lp; | |
172 | } | |
173 | ||
174 | if(node) | |
175 | deregister_service(node); | |
176 | } | |
177 | ||
16d29ce2 CP |
178 | nick *relay_getnick(void) { |
179 | static char ournick[NICKLEN + 1]; | |
180 | int attempts = 100; | |
af0c1aed | 181 | |
16d29ce2 | 182 | do { |
c5c119a6 | 183 | snprintf(ournick, sizeof(ournick), "%s%d", nickprefix, number++); |
16d29ce2 CP |
184 | if(number > 60000) |
185 | number = 1; | |
af0c1aed | 186 | |
16d29ce2 | 187 | if(!getnickbynick(ournick)) |
c4ffdb9b | 188 | return registerlocaluserflags(ournick, "nterf", "cer", "nterfacer relay", "nterfacer", userid, 0, UMODE_SERVICE | UMODE_DEAF | UMODE_OPER | UMODE_INV | UMODE_ACCOUNT, &relay_messages); |
16d29ce2 CP |
189 | |
190 | attempts--; | |
191 | } while(attempts > 0); | |
192 | ||
193 | return NULL; | |
af0c1aed CP |
194 | } |
195 | ||
18f8bd28 | 196 | int relay_handler(struct rline *ri, int argc, char **argv) { |
18f8bd28 | 197 | struct rld *np; |
16d29ce2 | 198 | int lines = 0, i; |
18f8bd28 CP |
199 | nick *dest; |
200 | const char *rerror; | |
201 | int erroroffset; | |
202 | ||
203 | if(!connected) | |
204 | return ri_error(ri, RELAY_NOT_ON_IRC, "Not currently on IRC"); | |
205 | ||
206 | if(argv[0][0] == '1') { | |
207 | lines = positive_atoi(argv[1]); | |
208 | if(lines < 1) | |
209 | return ri_error(ri, RELAY_PARSE_ERROR, "Parse error"); | |
210 | } else if(argv[0][0] != '2') { | |
211 | return ri_error(ri, RELAY_PARSE_ERROR, "Parse error"); | |
212 | } | |
213 | ||
214 | dest = getnickbynick(argv[2]); | |
215 | if(!dest) | |
216 | return ri_error(ri, RELAY_NICK_NOT_FOUND, "Nickname not found!"); | |
217 | ||
6bc02131 CP |
218 | if(homeserver(dest->numeric) == mylongnum) |
219 | return ri_error(ri, RELAY_LOCAL_USER, "Cannot relay to local users"); | |
220 | ||
16d29ce2 CP |
221 | for(i=3;i<argc;i++) |
222 | if(strchr(argv[i], '\r')) | |
223 | return ri_error(ri, RELAY_INVALID_CHARS, "Invalid character in input"); | |
224 | ||
5ab07198 | 225 | np = ntmalloc(sizeof(struct rld)); |
18f8bd28 CP |
226 | MemCheckR(np, ri_error(ri, RELAY_MEMORY_ERROR, "Memory error")); |
227 | ||
228 | np->rline = ri; | |
229 | if(!lines) { | |
230 | np->termination.pcre.phrase = pcre_compile(argv[1], PCRE_FLAGS, &rerror, &erroroffset, NULL); | |
231 | if(!np->termination.pcre.phrase) { | |
232 | MemError(); | |
5ab07198 | 233 | ntfree(np); |
18f8bd28 CP |
234 | return ri_error(ri, RELAY_REGEX_ERROR, "Regex compliation error"); |
235 | } | |
236 | np->termination.pcre.hint = pcre_study(np->termination.pcre.phrase, 0, &rerror); | |
237 | if(rerror) { | |
238 | pcre_free(np->termination.pcre.phrase); | |
5ab07198 | 239 | ntfree(np); |
18f8bd28 CP |
240 | return ri_error(ri, RELAY_REGEX_HINT_ERROR, "Regex hint error"); |
241 | } | |
242 | ||
243 | np->mode = MODE_TAG; | |
244 | } else { | |
245 | np->mode = MODE_LINES; | |
246 | np->termination.remaining_lines = lines; | |
247 | } | |
248 | ||
16d29ce2 | 249 | if(!(np->nick = relay_getnick())) { |
18f8bd28 CP |
250 | if(!lines) { |
251 | pcre_free(np->termination.pcre.phrase); | |
252 | if(np->termination.pcre.hint) | |
253 | pcre_free(np->termination.pcre.hint); | |
254 | } | |
5ab07198 | 255 | ntfree(np); |
18f8bd28 CP |
256 | return ri_error(ri, RELAY_NICK_ERROR, "Unable to get a nickname!"); |
257 | } | |
258 | ||
18f8bd28 CP |
259 | np->schedule = scheduleoneshot(time(NULL) + MESSAGE_TIMEOUT, &relay_timeout, np); |
260 | if(!np->schedule) { | |
261 | MemError(); | |
262 | if(!lines) { | |
263 | pcre_free(np->termination.pcre.phrase); | |
264 | if(np->termination.pcre.hint) | |
265 | pcre_free(np->termination.pcre.hint); | |
266 | } | |
267 | deregisterlocaluser(np->nick, NULL); | |
5ab07198 | 268 | ntfree(np); |
16d29ce2 | 269 | return ri_error(ri, RELAY_MEMORY_ERROR, "Memory error"); |
18f8bd28 CP |
270 | } |
271 | ||
272 | np->dest = dest; | |
273 | ||
274 | np->next = list; | |
275 | list = np; | |
276 | ||
277 | if(pcre_exec(onickname.phrase, onickname.hint, dest->nick, strlen(dest->nick), 0, 0, NULL, 0) >= 0) { | |
278 | np->mode|=MODE_IS_O; | |
16d29ce2 | 279 | sendsecuremessagetouser(np->nick, dest, serverlist[dest->numeric>>18].name->content, "AUTH %s %s", ousername->content, opassword->content); |
18f8bd28 CP |
280 | } |
281 | ||
282 | for(i=3;i<argc;i++) | |
16d29ce2 CP |
283 | sendmessagetouser(np->nick, dest, "%s", argv[i]); |
284 | ||
285 | return RE_OK; | |
286 | } | |
287 | ||
288 | int stats_handler(struct rline *ri, int argc, char **argv) { | |
289 | struct rld *np; | |
290 | char *server = argv[0], *command = argv[1], *more; | |
291 | int serverid = findserver(server); | |
292 | char targetnumeric[3]; | |
293 | ||
294 | if(serverid == -1) | |
295 | return ri_error(ri, RELAY_SERVER_NOT_FOUND, "Server not found"); | |
296 | ||
297 | if(!command || !command[0] || (command[0] == ' ') || (command[0] == '\r') || (command[0] == ':')) | |
298 | return ri_error(ri, RELAY_INVALID_COMMAND, "Invalid command"); | |
299 | ||
300 | if(argc > 2) { | |
301 | more = argv[2]; | |
302 | if(strchr(more, '\r')) | |
303 | return ri_error(ri, RELAY_INVALID_CHARS, "Invalid character in input"); | |
304 | } else { | |
305 | more = NULL; | |
306 | } | |
307 | ||
5ab07198 | 308 | np = ntmalloc(sizeof(struct rld)); |
16d29ce2 CP |
309 | MemCheckR(np, ri_error(ri, RELAY_MEMORY_ERROR, "Memory error")); |
310 | ||
311 | np->rline = ri; | |
312 | np->mode = MODE_STATS; | |
313 | np->dest = NULL; | |
314 | ||
315 | if(!(np->nick = relay_getnick())) { | |
5ab07198 | 316 | ntfree(np); |
16d29ce2 CP |
317 | return ri_error(ri, RELAY_NICK_ERROR, "Unable to get a nickname!"); |
318 | } | |
319 | ||
320 | np->schedule = scheduleoneshot(time(NULL) + MESSAGE_TIMEOUT, &relay_timeout, np); | |
321 | if(!np->schedule) { | |
322 | MemError(); | |
323 | deregisterlocaluser(np->nick, NULL); | |
5ab07198 | 324 | ntfree(np); |
16d29ce2 CP |
325 | } |
326 | ||
327 | np->next = list; | |
328 | list = np; | |
329 | ||
330 | memcpy(targetnumeric, longtonumeric(serverid, 2), 3); | |
331 | targetnumeric[2] = '\0'; | |
332 | if(more) { | |
333 | irc_send("%s R %c %s :%s\r\n", longtonumeric(np->nick->numeric,5), command[0], targetnumeric, more); | |
334 | } else { | |
335 | irc_send("%s R %c :%s\r\n", longtonumeric(np->nick->numeric,5), command[0], targetnumeric); | |
336 | } | |
18f8bd28 CP |
337 | |
338 | return RE_OK; | |
339 | } | |
340 | ||
341 | void relay_messages(nick *target, int messagetype, void **args) { | |
342 | struct rld *item, *prev = NULL; | |
ecc4fa34 CP |
343 | struct rline *ri; |
344 | ||
18f8bd28 CP |
345 | for(item=list;item;prev=item,item=item->next) |
346 | if(item->nick == target) | |
347 | break; | |
348 | ||
349 | if(!item) | |
350 | return; | |
ecc4fa34 CP |
351 | |
352 | ri = item->rline; | |
353 | ||
18f8bd28 CP |
354 | switch(messagetype) { |
355 | case LU_PRIVNOTICE: | |
356 | case LU_PRIVMSG: | |
357 | case LU_SECUREMSG: | |
16d29ce2 | 358 | if(!item->dest || (item->dest != (nick *)args[0])) |
18f8bd28 CP |
359 | return; |
360 | ||
361 | if((item->mode & MODE_IS_O) && !(item->mode & MODE_O_AUTH2)) { | |
362 | if(item->mode & MODE_O_AUTH1) { | |
363 | if(!strncmp((char *)args[1], "Your authlevel is ", 18)) { | |
364 | item->mode|=MODE_O_AUTH2; | |
365 | } else { | |
18f8bd28 | 366 | dispose_rld_prev(item, prev); |
ecc4fa34 | 367 | ri_error(ri, RELAY_O_AUTH_ERROR, "Unable to auth with O"); |
18f8bd28 CP |
368 | } |
369 | } else { | |
370 | if(!strcmp((char *)args[1], "Auth successful.")) { | |
371 | item->mode|=MODE_O_AUTH1; | |
372 | } else { | |
18f8bd28 | 373 | dispose_rld_prev(item, prev); |
ecc4fa34 | 374 | ri_error(ri, RELAY_O_AUTH_ERROR, "Unable to auth with O"); |
18f8bd28 CP |
375 | } |
376 | } | |
ecc4fa34 | 377 | } else if(ri_append(ri, "%s", (char *)args[1]) == BF_OVER) { |
18f8bd28 | 378 | dispose_rld_prev(item, prev); |
ecc4fa34 | 379 | ri_error(ri, BF_OVER, "Buffer overflow"); |
18f8bd28 CP |
380 | } else { |
381 | 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))) { | |
18f8bd28 | 382 | dispose_rld_prev(item, prev); |
ecc4fa34 | 383 | ri_final(ri); |
18f8bd28 CP |
384 | } |
385 | } | |
386 | break; | |
387 | ||
388 | case LU_KILLED: | |
b00ef0b1 CP |
389 | if(prev) { |
390 | prev->next = item->next; | |
391 | } else { | |
392 | list = item->next; | |
393 | } | |
394 | dispose_rld_dontquit(item, 1); | |
ecc4fa34 | 395 | ri_error(ri, RELAY_KILLED, "Killed"); |
18f8bd28 | 396 | break; |
16d29ce2 CP |
397 | |
398 | case LU_STATS: | |
399 | if(item->mode != MODE_STATS) | |
400 | return; | |
ecc4fa34 | 401 | if(ri_append(ri, "%s", (char *)args[2]) == BF_OVER) { |
95ee3dac | 402 | dispose_rld_prev(item, prev); |
ecc4fa34 | 403 | ri_error(ri, BF_OVER, "Buffer overflow"); |
95ee3dac | 404 | } |
16d29ce2 CP |
405 | break; |
406 | ||
407 | case LU_STATS_END: | |
408 | if(item->mode != MODE_STATS) | |
409 | return; | |
ecc4fa34 | 410 | |
16d29ce2 | 411 | dispose_rld_prev(item, prev); |
ecc4fa34 CP |
412 | |
413 | ri_final(ri); | |
16d29ce2 | 414 | break; |
18f8bd28 CP |
415 | } |
416 | } | |
417 | ||
418 | void dispose_rld_prev(struct rld *item, struct rld *prev) { | |
419 | if(prev) { | |
420 | prev->next = item->next; | |
421 | } else { | |
422 | list = item->next; | |
423 | } | |
424 | dispose_rld(item); | |
425 | } | |
426 | ||
427 | void dispose_rld_dontquit(struct rld *item, int dontquit) { | |
428 | if(item->schedule) | |
429 | deleteschedule(item->schedule, &relay_timeout, item); | |
b00ef0b1 | 430 | if(!dontquit) |
18f8bd28 CP |
431 | deregisterlocaluser(item->nick, NULL); |
432 | if(item->mode & MODE_TAG) { | |
433 | pcre_free(item->termination.pcre.phrase); | |
434 | if(item->termination.pcre.hint) | |
435 | pcre_free(item->termination.pcre.hint); | |
436 | } | |
5ab07198 | 437 | ntfree(item); |
18f8bd28 CP |
438 | } |
439 | ||
440 | void relay_timeout(void *arg) { | |
441 | struct rld *item = (struct rld *)arg, *lp = NULL, *np; | |
ecc4fa34 | 442 | struct rline *ri = item->rline; |
18f8bd28 CP |
443 | |
444 | item->schedule = NULL; | |
18f8bd28 CP |
445 | for(np=list;np;lp=np,np=np->next) { |
446 | if(np == item) { | |
447 | dispose_rld_prev(item, lp); | |
448 | break; | |
449 | } | |
450 | } | |
ecc4fa34 CP |
451 | |
452 | ri_error(ri, RELAY_TIMEOUT, "Timed out"); | |
18f8bd28 CP |
453 | } |
454 | ||
455 | void relay_quits(int hook, void *args) { | |
3193eda0 | 456 | nick *np = (nick *)args; |
ecc4fa34 CP |
457 | struct rld *cp, *lp, *freed = NULL; |
458 | ||
459 | /* get them out of the list before freeing */ | |
18f8bd28 CP |
460 | for(lp=NULL,cp=list;cp;) { |
461 | if(cp->dest == np) { | |
18f8bd28 CP |
462 | if(lp) { |
463 | lp->next = cp->next; | |
ecc4fa34 CP |
464 | cp->next = freed; |
465 | freed = cp; | |
18f8bd28 CP |
466 | cp = lp->next; |
467 | } else { | |
468 | list = cp->next; | |
ecc4fa34 CP |
469 | cp->next = freed; |
470 | freed = cp; | |
18f8bd28 CP |
471 | cp = list; |
472 | } | |
473 | } else { | |
474 | lp = cp; | |
475 | cp = cp->next; | |
476 | } | |
477 | } | |
ecc4fa34 CP |
478 | |
479 | /* now free them up */ | |
480 | while(freed) { | |
481 | struct rline *ri = freed->rline; | |
482 | cp = freed->next; | |
483 | ||
484 | dispose_rld(freed); | |
485 | ri_error(ri, RELAY_TARGET_LEFT, "Target left QuakeNet"); | |
486 | ||
487 | freed = cp; | |
488 | } | |
18f8bd28 CP |
489 | } |
490 | ||
491 | void relay_disconnect(int hook, void *args) { | |
492 | struct rld *np, *lp = NULL; | |
493 | while(list) { | |
ecc4fa34 CP |
494 | struct rline *ri = list->rline; |
495 | ||
18f8bd28 CP |
496 | dispose_rld_prev(list, NULL); |
497 | ||
ecc4fa34 CP |
498 | ri_error(ri, RELAY_DISCONNECTED, "Disconnected from IRC"); |
499 | ||
18f8bd28 CP |
500 | np = lp; |
501 | } | |
502 | } |