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