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