1 /*
2 * parse.c: handles messages from the server. Believe it or not. I
3 * certainly wouldn't if I were you.
4 *
5 * Copyright (c) 1990 Michael Sandroff.
6 * Copyright (c) 1991, 1992 Troy Rollo.
7 * Copyright (c) 1992-1996 Matthew Green.
8 * Copyright 1997, 2003 EPIC Software Labs.
9 * All rights reserved.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notices, the above paragraph (the one permitting redistribution),
18 * this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. The names of the author(s) may not be used to endorse or promote
21 * products derived from this software without specific prior written
22 * permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
25 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
26 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
27 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
29 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
31 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
32 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
35 */
36
37 #include "irc.h"
38 #include "server.h"
39 #include "names.h"
40 #include "vars.h"
41 #include "ctcp.h"
42 #include "hook.h"
43 #include "commands.h"
44 #include "ignore.h"
45 #include "lastlog.h"
46 #include "ircaux.h"
47 #include "sedcrypt.h"
48 #include "termx.h"
49 #include "flood.h"
50 #include "window.h"
51 #include "screen.h"
52 #include "output.h"
53 #include "numbers.h"
54 #include "parse.h"
55 #include "notify.h"
56 #include "timer.h"
57
58 #define STRING_CHANNEL '+'
59 #define MULTI_CHANNEL '#'
60 #define LOCAL_CHANNEL '&'
61 #define ID_CHANNEL '!'
62
63 #define space ' ' /* Taken from rfc 1459 */
64 #define MAXPARA 20 /* RFC1459 says 15, but RusNet uses more */
65
66 static void strip_modes (const char *, const char *, const char *);
67
68 /* User and host information from server 2.7 */
69 const char *FromUserHost = empty_string;
70
71 /*
72 * is_channel: determines if the argument is a channel. If it's a number,
73 * begins with MULTI_CHANNEL and has no '*', or STRING_CHANNEL, then its a
74 * channel
75 */
is_channel(const char * to)76 int is_channel (const char *to)
77 {
78 const char *chantypes;
79
80 if (!to || !*to)
81 return 0;
82
83 chantypes = get_server_005(from_server, "CHANTYPES");
84 if (chantypes && *chantypes)
85 return (!!strchr(chantypes, *to));
86
87 return ((*to == MULTI_CHANNEL) || (*to == STRING_CHANNEL) ||
88 (*to == LOCAL_CHANNEL) || (*to == ID_CHANNEL));
89 }
90
91 /*
92 * is_to_channel: determines if the argument is a channel target for
93 * privmsg/notice. STATUSMSG can appear before CHANTYPES on 005 servers.
94 */
is_target_channel_wall(const char * to)95 static int is_target_channel_wall (const char *to)
96 {
97 const char * statusmsg;
98
99 if (!to || !*to)
100 return 0;
101
102 statusmsg = get_server_005(from_server, "STATUSMSG");
103 if (statusmsg && strchr(statusmsg, to[0]))
104 return 1;
105 else
106 return 0;
107 }
108
109
110 /*
111 * This function reverses the action of BreakArgs but only after a certain
112 * token. You shall not call this function with an 'arg_list' that was
113 * not previously passed to BreakArgs.
114 */
PasteArgs(const char ** arg_list,int paste_point)115 const char * PasteArgs (const char **arg_list, int paste_point)
116 {
117 int i;
118 char *ptr;
119 size_t len;
120
121 /*
122 * Make sure there are enough args to parse...
123 */
124 for (i = 0; i < paste_point; i++)
125 if (!arg_list[i] || !*arg_list[i])
126 return NULL; /* Not enough args */
127
128 /*
129 * Tokens are followed by one or more nul's. We need to change
130 * ALL of those nuls back to spaces. We don't know how many nuls
131 * there might be so we have to check for each one.
132 */
133 for (i = paste_point; arg_list[i] && arg_list[i + 1]; i++)
134 {
135 /*
136 * arg_list is (const char **) to prevent OTHER people from
137 * modifying it underneath us. But we own arg_list, so this
138 * laundering away of const is reasonable safe, and proper.
139 */
140 ptr = (char *)
141 #ifdef HAVE_INTPTR_T
142 (intptr_t)
143 #endif
144 arg_list[i];
145
146 /*
147 * Yes, this IS safe! Please note above that the above for
148 * loop walks tokens (1, N-1), so we are NOT clobbering the
149 * actual final nul on the end of the original string.
150 * We leave the nul on the end of the final arg exactly the
151 * way that BreakArgs parsed it.
152 */
153 len = strlen(ptr);
154 while (ptr[len] == '\0')
155 ptr[len++] = ' ';
156 }
157
158 arg_list[paste_point + 1] = NULL;
159 return arg_list[paste_point];
160 }
161
162 /*
163 * BreakArgs: breaks up the line from the server, in to where its from,
164 * setting FromUserHost if it should be, and returns all the arguements
165 * that are there. Re-written by phone, dec 1992.
166 * Re-written again by esl, april 1996.
167 *
168 * This doesnt strip out extraneous spaces any more.
169 */
BreakArgs(char * Input,const char ** Sender,const char ** OutPut)170 static void BreakArgs (char *Input, const char **Sender, const char **OutPut)
171 {
172 int ArgCount;
173
174 /*
175 * Paranoia. Clean out any bogus ptrs still left on OutPut...
176 */
177 for (ArgCount = 0; ArgCount <= MAXPARA + 1; ArgCount++)
178 OutPut[ArgCount] = NULL;
179 ArgCount = 0;
180
181 /*
182 * The RFC describes it fully, but in a short form, a line looks like:
183 * [:sender[!user@host]] COMMAND ARGUMENT [[:]ARGUMENT]{0..14}
184 */
185
186 /*
187 * Look to see if the optional :sender is present.
188 */
189 if (*Input == ':')
190 {
191 char *fuh;
192
193 fuh = ++Input;
194 while (*Input && *Input != space)
195 Input++;
196 while (*Input == space)
197 *Input++ = 0;
198
199 /*
200 * Look to see if the optional !user@host is present.
201 */
202 *Sender = fuh;
203 while (*fuh && *fuh != '!')
204 fuh++;
205 if (*fuh == '!')
206 *fuh++ = 0;
207 FromUserHost = fuh;
208 }
209 /*
210 * No sender present.
211 */
212 else
213 *Sender = FromUserHost = empty_string;
214
215 /*
216 * This changes all spaces (" ") in the protocol command to nuls ('\0')
217 * and puts pointers at the start of every token into OutPut[]. Note
218 * that if a token is followed by more than one space, they will be
219 * all changed into nul's. The PasteArgs() function (above) handles
220 * that properly.
221 */
222 for (;;)
223 {
224 if (!*Input)
225 break;
226
227 if (*Input == ':')
228 {
229 /* Squash the : so if PasteArgs() is called it doesn't reappear */
230 ov_strcpy(Input, Input + 1);
231 OutPut[ArgCount++] = Input;
232 break;
233 }
234
235 OutPut[ArgCount++] = Input;
236 if (ArgCount > MAXPARA)
237 break;
238
239 while (*Input && *Input != space)
240 Input++;
241 while (*Input && *Input == space)
242 *Input++ = 0;
243 }
244 OutPut[ArgCount] = NULL;
245 }
246
247 /* in response to a TOPIC message from the server */
p_topic(const char * from,const char * comm,const char ** ArgList)248 static void p_topic (const char *from, const char *comm, const char **ArgList)
249 {
250 const char *channel, *new_topic;
251 int l;
252
253 if (!(channel = ArgList[0]))
254 { rfc1459_odd(from, comm, ArgList); return; }
255 if (!(new_topic = ArgList[1]))
256 { rfc1459_odd(from, comm, ArgList); return; }
257
258 if (check_ignore_channel(from, FromUserHost,
259 channel, LEVEL_TOPIC) == IGNORED)
260 return;
261
262 if (new_check_flooding(from, FromUserHost,
263 channel, new_topic, LEVEL_TOPIC))
264 return;
265
266 l = message_from(channel, LEVEL_TOPIC);
267 if (do_hook(TOPIC_LIST, "%s %s %s", from, channel, new_topic))
268 say("%s has changed the topic on channel %s to %s",
269 from, check_channel_type(channel), new_topic);
270 pop_message_from(l);
271 }
272
p_wallops(const char * from,const char * comm,const char ** ArgList)273 static void p_wallops (const char *from, const char *comm, const char **ArgList)
274 {
275 const char *message;
276 int server_wallop;
277 int l;
278
279 if (!(message = ArgList[0]))
280 { rfc1459_odd(from, comm, ArgList); return; }
281
282 server_wallop = strchr(from, '.') ? 1 : 0;
283
284 /* So Black will stop asking me to add it... */
285 if (!strncmp(message, "OPERWALL - ", 11))
286 {
287 int retval;
288
289 /* Check for ignores... */
290 if (check_ignore(from, FromUserHost, LEVEL_OPERWALL) == IGNORED)
291 return;
292
293 /* Check for floods... servers are exempted from flood checks */
294 if (!server_wallop && check_flooding(from, FromUserHost,
295 LEVEL_OPERWALL, message))
296 return;
297
298 l = message_from(NULL, LEVEL_OPERWALL);
299 retval = do_hook(OPERWALL_LIST, "%s %s", from, message + 11);
300 pop_message_from(l);
301 if (!retval)
302 return;
303 }
304
305 /*
306 * If it's not an operwall, ,or if the user didn't catch it,
307 * treat it as a wallop.
308 */
309
310 /* Check for ignores... */
311 if (check_ignore(from, FromUserHost, LEVEL_WALLOP) == IGNORED)
312 return;
313
314 /* Check for floods... servers are exempted from flood checks */
315 if (!server_wallop && check_flooding(from, FromUserHost,
316 LEVEL_WALLOP, message))
317 return;
318
319 l = message_from(NULL, LEVEL_WALLOP);
320 if (do_hook(WALLOP_LIST, "%s %c %s",
321 from,
322 server_wallop ? 'S' : '*',
323 message))
324 put_it("!%s%s! %s",
325 from, server_wallop ? empty_string : star,
326 message);
327 pop_message_from(l);
328 }
329
330 /*
331 * p_privmsg - Handle PRIVMSG messages from irc server
332 *
333 * Arguments:
334 * from - The sender of the PRIVMSG (usually a nick; servers uncommon)
335 * comm - The actual protocol command ("PRIVMSG")
336 * ArgList - Arguments to the PRIVMSG:
337 * ArgList[0] - The receiver of the message (nick or channel)
338 * ArgList[1] - Payload of the message
339 *
340 * Notes:
341 * PRIVMSGs may be sent to
342 * 1. A nick (our nick)
343 * 2. A channel (that we're on)
344 * 3. A prefix + A channel (eg "@#channel" or "+#channel")
345 * We treat this as #2.
346 * 4. Something else (a wall, ie, "*.iastate.edu")
347 *
348 * PRIVMSG are sorted into several piles:
349 * "PUBLIC" level:
350 * 1. Someone on the channel sends a message to channel -> PUBLIC
351 * + The channel is current channel in any window
352 * 2. Someone on the channel sends a message to channel -> PUBLIC_OTHER
353 * + The channel is NOT a current channel on any window
354 * 3. Someone not on channel sends a message to channel -> PUBLIC_MSG
355 *
356 * "MSG" level:
357 * 4. A message sent to our nickname -> MSG
358 *
359 * "WALL" level:
360 * 5. A message sent to any other target -> MSG_GROUP
361 *
362 *
363 * Processing
364 * ==========
365 * CTCPs are delivered via PRIVMSGs, causing a rewrite of ArgList[1].
366 * + Most CTCPs are just removed (CTCP requests)
367 * + Some do in-place substitution (Encryption - ie, CTCP CAST5)
368 * + CTCP handling happens first, before anything below
369 * + CTCP handling does its own ignore, flood control, and throttling.
370 *
371 * If the PRIVMSG contains nothing after CTCP handling, then stop.
372 *
373 * First, IGNOREs are checked. (CTCPs do their own ignore handling)
374 *
375 * If the PRIVMSG is encrypted, it will first be offered via
376 * /ON ENCRYPTED_PRIVMSG no matter who sent it or to whom
377 * it was sent.
378 *
379 * Next, flood control is checked.
380 *
381 * All PRIVMSGs are offered via /ON GENERAL_PRIVMSG,
382 * no matter who sent it or to whom it was sent.
383 *
384 * All PRIVMSGs are offered via their respective types (see above)
385 *
386 * Otherwise, a default message is output.
387 *
388 * Finally, /NOTIFY is checked.
389 */
p_privmsg(const char * from,const char * comm,const char ** ArgList)390 static void p_privmsg (const char *from, const char *comm, const char **ArgList)
391 {
392 const char *real_target, *target, *message;
393 int hook_type,
394 level;
395 const char *hook_format;
396 const char *flood_channel = NULL;
397 int l;
398
399 PasteArgs(ArgList, 1);
400 if (!(target = ArgList[0]))
401 { rfc1459_odd(from, comm, ArgList); return; }
402 if (!(message = ArgList[1]))
403 { rfc1459_odd(from, comm, ArgList); return; }
404
405 set_server_doing_privmsg(from_server, 1);
406 sed = 0;
407
408 /*
409 * Do ctcp's first, and if there's nothing left, then dont
410 * go to all the work below. Plus, we dont set message_from
411 * until we know there's other stuff besides the ctcp in the
412 * message, which keeps things going to the wrong window.
413 */
414 message = do_ctcp(1, from, target, (char *)
415 #ifdef HAVE_INTPTR_T
416 (intptr_t)
417 #endif
418 message);
419 if (!*message) {
420 set_server_doing_privmsg(from_server, 0);
421 return;
422 }
423
424 /* If this is a @#chan or +#chan, ignore the @ or +. */
425 real_target = target;
426 if (is_target_channel_wall(target) &&
427 im_on_channel(target + 1, from_server))
428 target++;
429
430 /* ooops. cant just do is_channel(to) because of # walls... */
431 if (is_channel(target) && im_on_channel(target, from_server))
432 {
433 level = LEVEL_PUBLIC;
434 flood_channel = target;
435
436 if (!is_channel_nomsgs(target, from_server) &&
437 !is_on_channel(target, from)) {
438 hook_type = PUBLIC_MSG_LIST;
439 hook_format = "(%s/%s) %s";
440 } else if (is_current_channel(target, from_server)) {
441 hook_type = PUBLIC_LIST;
442 hook_format = "<%s%.0s> %s";
443 } else if (target != real_target &&
444 is_current_channel(target, from_server)) {
445 hook_type = PUBLIC_LIST;
446 hook_format = "<%s:%s> %s";
447 } else {
448 hook_type = PUBLIC_OTHER_LIST;
449 hook_format = "<%s:%s> %s";
450 }
451 }
452 else if (!is_me(from_server, target))
453 {
454 level = LEVEL_WALL;
455 flood_channel = NULL;
456
457 hook_type = MSG_GROUP_LIST;
458 hook_format = "<-%s:%s-> %s";
459 }
460 else
461 {
462 level = LEVEL_MSG;
463 flood_channel = NULL;
464
465 hook_type = MSG_LIST;
466 hook_format = NULL; /* See below */
467 target = NULL; /* Target is the sender */
468 }
469
470 if (!target || !*target)
471 target = from; /* Target is actually sender here */
472
473 if (check_ignore_channel(from, FromUserHost, target, level) == IGNORED
474 || (check_ignore_channel(from, FromUserHost, real_target, level) == IGNORED))
475 {
476 set_server_doing_privmsg(from_server, 0);
477 return;
478 }
479
480 /* Encrypted privmsgs are specifically exempted from flood control */
481 if (sed)
482 {
483 int do_return = 1;
484
485 sed = 0;
486 l = message_from(target, level);
487 if (do_hook(ENCRYPTED_PRIVMSG_LIST, "%s %s %s",
488 from, real_target, message))
489 do_return = 0;
490 pop_message_from(l);
491
492 if (do_return) {
493 set_server_doing_privmsg(from_server, 0);
494 return;
495 }
496 }
497
498 if (new_check_flooding(from, FromUserHost, flood_channel,
499 message, level)) {
500 set_server_doing_privmsg(from_server, 0);
501 return;
502 }
503
504 /* Control the "last public/private nickname" variables */
505 if (hook_type == PUBLIC_LIST ||
506 hook_type == PUBLIC_MSG_LIST ||
507 hook_type == PUBLIC_OTHER_LIST)
508 set_server_public_nick(from_server, from);
509 else if (hook_type == MSG_LIST)
510 set_server_recv_nick(from_server, from);
511
512 /* Go ahead and throw it to the user */
513 l = message_from(target, level);
514
515 if (do_hook(GENERAL_PRIVMSG_LIST, "%s %s %s", from, real_target, message))
516 {
517 if (hook_type == MSG_LIST)
518 {
519 const char *away = get_server_away(NOSERV);
520
521 if (do_hook(hook_type, "%s %s", from, message))
522 {
523 if (away)
524 put_it("*%s* %s <%.16s>", from, message, my_ctime(time(NULL)));
525 else
526 put_it("*%s* %s", from, message);
527 }
528 }
529
530 else if (do_hook(hook_type, "%s %s %s", from, real_target, message))
531 put_it(hook_format, from, check_channel_type(real_target), message);
532 }
533
534 /* Clean up and go home. */
535 pop_message_from(l);
536 set_server_doing_privmsg(from_server, 0);
537
538 /* Alas, this is not protected by protocol enforcement. :( */
539 notify_mark(from_server, from, 1, 0);
540 }
541
p_quit(const char * from,const char * comm,const char ** ArgList)542 static void p_quit (const char *from, const char *comm, const char **ArgList)
543 {
544 const char * quit_message;
545 int one_prints = 1;
546 const char * chan;
547 int l;
548
549 if (!(quit_message = ArgList[0]))
550 { rfc1459_odd(from, comm, ArgList); return; }
551
552 /*
553 * Normally, we do not throw the user a hook until after we
554 * have taken care of administrative details. But in this case,
555 * someone has QUIT but the user may want their user@host info
556 * so we cannot remove them from the channel until after we have
557 * thrown the hook. That is the only reason this is out of order.
558 */
559 if (check_ignore(from, FromUserHost, LEVEL_QUIT) == IGNORED)
560 goto remove_quitter;
561
562 if (check_flooding(from, FromUserHost, LEVEL_QUIT, quit_message))
563 goto remove_quitter;
564
565 for (chan = walk_channels(1, from); chan; chan = walk_channels(0, from))
566 {
567 if (check_ignore_channel(from, FromUserHost,
568 chan, LEVEL_QUIT) == IGNORED)
569 {
570 one_prints = 0;
571 continue;
572 }
573
574 l = message_from(chan, LEVEL_QUIT);
575 if (!do_hook(CHANNEL_SIGNOFF_LIST, "%s %s %s", chan, from,
576 quit_message))
577 one_prints = 0;
578 pop_message_from(l);
579 }
580
581 if (one_prints)
582 {
583 l = message_from(what_channel(from, from_server), LEVEL_QUIT);
584 if (do_hook(SIGNOFF_LIST, "%s %s", from, quit_message))
585 say("Signoff: %s (%s)", from, quit_message);
586 pop_message_from(l);
587 }
588
589 /*
590 * This is purely ergonomic. If the user is ignoring this person
591 * then if we tell the user that this person is offline as soon as
592 * we get the QUIT, this will leak to the user that the person was
593 * on the channel, thus defeating the ignore. Best to just wait
594 * until the top of the next minute.
595 */
596 notify_mark(from_server, from, 0, 0);
597
598 remove_quitter:
599 /* Send all data about this unperson to the memory hole. */
600 remove_from_channel(NULL, from, from_server);
601 }
602
p_pong(const char * from,const char * comm,const char ** ArgList)603 static void p_pong (const char *from, const char *comm, const char **ArgList)
604 {
605 const char * pong_server, *pong_message;
606 int server_pong = 0;
607
608 PasteArgs(ArgList, 1);
609 if (!(pong_server = ArgList[0]))
610 { rfc1459_odd(from, comm, ArgList); return; }
611 if (!(pong_message = ArgList[1]))
612 { rfc1459_odd(from, comm, ArgList); return; }
613
614 if (strchr(pong_server, '.'))
615 server_pong = 1;
616
617 /*
618 * In theory, we could try to use PING/PONG messages to
619 * do /redirect and /wait, but we don't. When the day comes
620 * that we do, this code will do that for us.
621 */
622 if (!my_stricmp(from, get_server_itsname(from_server)))
623 {
624 if (check_server_redirect(from_server, pong_message))
625 return;
626 if (check_server_wait(from_server, pong_message))
627 return;
628 }
629
630 if (check_ignore(from, FromUserHost, LEVEL_OTHER) == IGNORED)
631 return;
632
633 if (do_hook(PONG_LIST, "%s %s %s", from, pong_server, pong_message))
634 if (server_pong)
635 say("%s: PONG received from %s (%s)", pong_server,
636 from, pong_message);
637 }
638
p_error(const char * from,const char * comm,const char ** ArgList)639 static void p_error (const char *from, const char *comm, const char **ArgList)
640 {
641 const char * the_error;
642
643 PasteArgs(ArgList, 0);
644 if (!(the_error = ArgList[0]))
645 { rfc1459_odd(from, comm, ArgList); return; }
646
647 if (!from || !isgraph(*from))
648 from = star;
649
650 if (do_hook(ERROR_LIST, "%s %s", from, the_error))
651 say("%s %s", from, the_error);
652 }
653
p_channel(const char * from,const char * comm,const char ** ArgList)654 static void p_channel (const char *from, const char *comm, const char **ArgList)
655 {
656 const char *channel;
657 char *c;
658 int op = 0, vo = 0, ha = 0;
659 char extra[20];
660 int l;
661
662 /* We cannot join channel 0 */
663 if (!(channel = ArgList[0]))
664 { rfc1459_odd(from, comm, ArgList); return; }
665 if (!strcmp(channel, zero))
666 { rfc1459_odd(from, comm, ArgList); return; }
667
668 /*
669 * Workaround for extremely gratuitous protocol change in av2.9
670 */
671 if ((c = strchr(channel, '\007')))
672 {
673 for (*c++ = 0; *c; c++)
674 {
675 if (*c == 'o') op = 1;
676 else if (*c == 'v') vo = 1;
677 }
678 }
679
680 if (is_me(from_server, from))
681 {
682 add_channel(channel, from_server);
683 send_to_server("MODE %s", channel);
684 }
685 else
686 {
687 add_to_channel(channel, from, from_server, 0, op, vo, ha);
688 add_userhost_to_channel(channel, from, from_server, FromUserHost);
689 }
690
691 if (check_ignore_channel(from, FromUserHost,
692 channel, LEVEL_JOIN) == IGNORED)
693 return;
694
695 if (new_check_flooding(from, FromUserHost, channel, star, LEVEL_JOIN))
696 return;
697
698 set_server_joined_nick(from_server, from);
699
700 *extra = 0;
701 if (op)
702 strlcat(extra, " (+o)", sizeof extra);
703 if (vo)
704 strlcat(extra, " (+v)", sizeof extra);
705
706 l = message_from(channel, LEVEL_JOIN);
707 if (do_hook(JOIN_LIST, "%s %s %s %s",
708 from, channel, FromUserHost, extra))
709 say("%s (%s) has joined channel %s%s",
710 from, FromUserHost,
711 check_channel_type(channel), extra);
712 pop_message_from(l);
713
714 /*
715 * The placement of this is purely ergonomic. The user might
716 * be alarmed if epic thrown an /on notify_signon before it
717 * throws the /on join that triggers it. Plus, if the user is
718 * ignoring this person (nothing says you can't ignore someone
719 * who is on your notify list), then it would also not be the
720 * best idea to throw /on notify_signon as a result of an
721 * /on join since that would leak to the user that the person
722 * has joined the channel -- best to just leave the notify stuff
723 * alone until the top of the next minute.
724 */
725 notify_mark(from_server, from, 1, 0);
726 }
727
p_invite(const char * from,const char * comm,const char ** ArgList)728 static void p_invite (const char *from, const char *comm, const char **ArgList)
729 {
730 const char *invitee, *invited_to;
731 int l;
732
733 if (!(invitee = ArgList[0]))
734 { rfc1459_odd(from, comm, ArgList); return; }
735 if (!(invited_to = ArgList[1]))
736 { rfc1459_odd(from, comm, ArgList); return; }
737
738 if (check_ignore_channel(from, FromUserHost,
739 invited_to, LEVEL_INVITE) == IGNORED)
740 return;
741
742 if (check_flooding(from, FromUserHost, LEVEL_INVITE, invited_to))
743 return;
744
745 set_server_invite_channel(from_server, invited_to);
746 set_server_recv_nick(from_server, from);
747
748 l = message_from(from, LEVEL_INVITE);
749 if (do_hook(INVITE_LIST, "%s %s %s", from, invited_to, FromUserHost))
750 say("%s (%s) invites you to channel %s",
751 from, FromUserHost, invited_to);
752 pop_message_from(l);
753 }
754
755 /*
756 * Received whenever we have been killed.
757 * (On old MS COMIC CHAT servers, also when someone else was killed)
758 *
759 * Up through epic4, there used to be a /set auto_reconnect which was
760 * used here, but epic5 uses server states, so p_kill is now treated
761 * as an advisory message (your script pack decides what to do once you
762 * actually get the EOF from the server).
763 *
764 * All we do now is throw /on disconnect and/or tell you what
765 * happened and it's someone else's problem what to do with that.
766 */
p_kill(const char * from,const char * comm,const char ** ArgList)767 static void p_kill (const char *from, const char *comm, const char **ArgList)
768 {
769 const char *victim, *reason;
770 int hooked;
771
772 if (!(victim = ArgList[0]))
773 { rfc1459_odd(from, comm, ArgList); return; }
774 if (!(reason = ArgList[1])) { }
775
776 /*
777 * Old MS Comic Chat servers (exchange) sent a KILL instead of
778 * a QUIT when someone was killed. We reroute that to QUIT.
779 */
780 if (!is_me(from_server, victim))
781 {
782 p_quit(from, comm, ArgList);
783 return;
784 }
785
786
787 /*
788 * If we've been killed by our server, we need take no action
789 * because when we are dropped by the server, we will take this as
790 * any other case where we recieve abnormal termination from the
791 * server and we will reconnect and rejoin.
792 */
793 if (strchr(from, '.'))
794 {
795 if (!reason) { reason = "Probably due to a nick collision"; }
796
797 say("Server [%s] has rejected you. (%s)", from, reason);
798 return;
799 }
800
801
802 /*
803 * We've been killed. Bummer for us.
804 */
805 if (!reason) { reason = "No Reason Given"; }
806
807 if ((hooked = do_hook(DISCONNECT_LIST, "Killed by %s (%s)",
808 from, reason)))
809 {
810 say("You have been killed by that fascist [%s] %s",
811 from, reason);
812 }
813
814
815 /*
816 * If we are a bot, and /on disconnect didnt hook,
817 * then we arent going anywhere. We might as well quit.
818 */
819 if (background && !hooked)
820 irc_exit(1, NULL);
821 }
822
p_ping(const char * from,const char * comm,const char ** ArgList)823 static void p_ping (const char *from, const char *comm, const char **ArgList)
824 {
825 const char * message;
826
827 PasteArgs(ArgList, 0);
828 if (!(message = ArgList[0]))
829 { rfc1459_odd(from, comm, ArgList); return; }
830
831 send_to_server("PONG %s", message);
832 }
833
p_silence(const char * from,const char * comm,const char ** ArgList)834 static void p_silence (const char *from, const char *comm, const char **ArgList)
835 {
836 const char *target;
837 char mag;
838
839 if (!(target = ArgList[0]))
840 { rfc1459_odd(from, comm, ArgList); return; }
841
842 mag = *target++;
843 if (do_hook(SILENCE_LIST, "%c %s", mag, target))
844 {
845 if (mag == '+')
846 say ("You will no longer receive msgs from %s", target);
847 else if (mag == '-')
848 say ("You will now recieve msgs from %s", target);
849 else
850 say ("Unrecognized silence argument: %s", target);
851 }
852 }
853
p_nick(const char * from,const char * comm,const char ** ArgList)854 static void p_nick (const char *from, const char *comm, const char **ArgList)
855 {
856 const char *new_nick;
857 int been_hooked = 0,
858 its_me = 0;
859 const char *chan;
860 int ignored = 0;
861 int l;
862
863 if (!(new_nick = ArgList[0]))
864 { rfc1459_odd(from, comm, ArgList); return; }
865
866 /*
867 * Is this me changing nick?
868 */
869 if (is_me(from_server, from))
870 {
871 its_me = 1;
872 accept_server_nickname(from_server, new_nick);
873 }
874
875 if (check_ignore(from, FromUserHost, LEVEL_NICK) == IGNORED)
876 goto do_rename;
877
878 if (check_flooding(from, FromUserHost, LEVEL_NICK, new_nick))
879 goto do_rename;
880
881 for (chan = walk_channels(1, from); chan; chan = walk_channels(0, from))
882 {
883 if (check_ignore_channel(from, FromUserHost, chan,
884 LEVEL_NICK) == IGNORED)
885 {
886 ignored = 1;
887 continue;
888 }
889
890 l = message_from(chan, LEVEL_NICK);
891 if (!do_hook(CHANNEL_NICK_LIST, "%s %s %s", chan, from, new_nick))
892 been_hooked = 1;
893 pop_message_from(l);
894 }
895
896 if (!been_hooked && !ignored)
897 {
898 if (its_me)
899 l = message_from(NULL, LEVEL_NICK);
900 else
901 l = message_from(what_channel(from, from_server),
902 LEVEL_NICK);
903
904 if (do_hook(NICKNAME_LIST, "%s %s", from, new_nick))
905 say("%s is now known as %s", from, new_nick);
906
907 pop_message_from(l);
908 }
909
910 do_rename:
911 notify_mark(from_server, from, 0, 0);
912 rename_nick(from, new_nick, from_server);
913 notify_mark(from_server, new_nick, 1, 0);
914 }
915
p_mode(const char * from,const char * comm,const char ** ArgList)916 static void p_mode (const char *from, const char *comm, const char **ArgList)
917 {
918 const char *target, *changes;
919 const char *m_target;
920 const char *type;
921 int l;
922
923 while (ArgList[0] && !*ArgList[0])
924 ++ArgList; /* Ride Austhex breakage */
925 PasteArgs(ArgList, 1);
926 if (!(target = ArgList[0]))
927 { rfc1459_odd(from, comm, ArgList); return; }
928 if (!(changes = ArgList[1])) { return; } /* Ignore UnrealIRCD */
929 if (!*changes) { return; } /* Ignore UnrealIRCD */
930
931 if (get_int_var(MODE_STRIPPER_VAR))
932 strip_modes(from, target, changes);
933
934 if (is_channel(target))
935 {
936 m_target = target;
937 target = check_channel_type(target);
938 type = "on channel";
939 }
940 else
941 {
942 m_target = NULL;
943 type = "for user";
944 }
945
946 if (check_ignore_channel(from, FromUserHost,
947 target, LEVEL_MODE) == IGNORED)
948 goto do_update_mode;
949
950 if (new_check_flooding(from, FromUserHost, target, changes, LEVEL_MODE))
951 goto do_update_mode;
952
953 l = message_from(m_target, LEVEL_MODE);
954 if (do_hook(MODE_LIST, "%s %s %s", from, target, changes))
955 say("Mode change \"%s\" %s %s by %s",
956 changes, type, target, from);
957 pop_message_from(l);
958
959 do_update_mode:
960 if (is_channel(target))
961 update_channel_mode(target, changes);
962 else
963 update_user_mode(from_server, changes);
964 }
965
strip_modes(const char * from,const char * channel,const char * line)966 static void strip_modes (const char *from, const char *channel, const char *line)
967 {
968 char *mode;
969 char *pointer;
970 char mag = '+'; /* XXXX Bogus */
971 char *copy = (char *) 0;
972 char *free_copy;
973 int l;
974
975 free_copy = LOCAL_COPY(line);
976 copy = free_copy;
977 mode = next_arg(copy, ©);
978
979 if (is_channel(channel))
980 {
981 l = message_from(channel, LEVEL_MODE);
982
983 for (pointer = mode; *pointer; pointer++)
984 {
985 char c = *pointer;
986 char *arg = NULL;
987
988 switch (chanmodetype(c))
989 {
990 case 1:
991 mag = c;
992 continue;
993 case 6:
994 break;
995 case 5:
996 if (mag == '-')
997 break;
998 FALLTHROUGH
999 case 4: case 3: case 2:
1000 if (!(arg = next_arg(copy, ©)))
1001 arg = endstr(copy);
1002 break;
1003 default:
1004 /* We already get a yell from decifer_mode() */
1005 break;
1006 }
1007 if (arg)
1008 do_hook(MODE_STRIPPED_LIST,
1009 "%s %s %c%c %s",
1010 from, channel, mag,
1011 c,arg);
1012 else
1013 do_hook(MODE_STRIPPED_LIST,
1014 "%s %s %c%c",
1015 from,channel,mag,c);
1016 }
1017
1018 pop_message_from(l);
1019 }
1020
1021 else /* User mode */
1022 {
1023 l = message_from(NULL, LEVEL_MODE);
1024
1025 for (pointer = mode; *pointer; pointer++)
1026 {
1027 char c = *pointer;
1028
1029 switch (c)
1030 {
1031 case '+' :
1032 case '-' : mag = c; break;
1033 default :
1034 do_hook(MODE_STRIPPED_LIST,
1035 "%s %s %c%c",
1036 from, channel, mag, c);
1037 break;
1038 }
1039 }
1040
1041 pop_message_from(l);
1042 }
1043
1044 }
1045
p_kick(const char * from,const char * comm,const char ** ArgList)1046 static void p_kick (const char *from, const char *comm, const char **ArgList)
1047 {
1048 const char *channel, *victim, *comment;
1049 int l;
1050
1051 if (!(channel = ArgList[0]))
1052 { rfc1459_odd(from, comm, ArgList); return; }
1053 if (!(victim = ArgList[1]))
1054 { rfc1459_odd(from, comm, ArgList); return; }
1055 if (!(comment = ArgList[2])) { comment = "(no comment)"; }
1056
1057
1058 /*
1059 * All this to handle being kicked...
1060 */
1061 if (is_me(-1, victim))
1062 {
1063 Window *win, *old_cw;
1064
1065 /*
1066 * Uh-oh. If win is null we have a problem.
1067 */
1068 if (!(win = get_window_by_refnum(
1069 get_channel_winref(channel, from_server))))
1070 {
1071 /*
1072 * Check to see if we got a KICK for a
1073 * channel we dont think we're on.
1074 */
1075 if (im_on_channel(channel, from_server))
1076 panic(0, "Window is NULL for channel [%s]", channel);
1077
1078 yell("You were KICKed by [%s] on channel [%s] "
1079 "(reason [%s]), which you are not on! "
1080 "Will not try to auto-rejoin",
1081 from, channel, comment);
1082
1083 return;
1084 }
1085
1086 /* XXX A POX ON ANYONE WHO ASKS ME TO MOVE THIS AGAIN XXX */
1087 old_cw = current_window;
1088 current_window = win;
1089 l = message_setall(win->refnum, channel, LEVEL_KICK);
1090
1091 if (do_hook(KICK_LIST, "%s %s %s %s", victim, from,
1092 check_channel_type(channel), comment))
1093 say("You have been kicked off channel %s by %s (%s)",
1094 check_channel_type(channel), from,
1095 comment);
1096
1097 pop_message_from(l);
1098 current_window = old_cw;
1099
1100 remove_channel(channel, from_server);
1101 update_all_status();
1102 return;
1103 }
1104
1105 if (check_ignore_channel(from, FromUserHost,
1106 channel, LEVEL_KICK) == IGNORED)
1107 goto do_remove_nick;
1108
1109 if (check_ignore_channel(victim, fetch_userhost(from_server, NULL,
1110 victim),
1111 channel, LEVEL_KICK) == IGNORED)
1112 goto do_remove_nick;
1113
1114
1115 if (new_check_flooding(from, FromUserHost, channel, victim, LEVEL_KICK))
1116 goto do_remove_nick;
1117
1118 l = message_from(channel, LEVEL_KICK);
1119 if (do_hook(KICK_LIST, "%s %s %s %s",
1120 victim, from, channel, comment))
1121 say("%s has been kicked off channel %s by %s (%s)",
1122 victim, check_channel_type(channel), from, comment);
1123 pop_message_from(l);
1124
1125 do_remove_nick:
1126 /*
1127 * The placement of this is purely ergonomic. When someone is
1128 * kicked, the user may want to know what their userhost was so
1129 * they can take whatever appropriate action is called for. This
1130 * requires that the user still be considered "on channel" in the
1131 * /on kick, even though the user has departed.
1132 *
1133 * Send all data for this unperson to the memory hole.
1134 */
1135 remove_from_channel(channel, victim, from_server);
1136 }
1137
p_part(const char * from,const char * comm,const char ** ArgList)1138 static void p_part (const char *from, const char *comm, const char **ArgList)
1139 {
1140 const char *channel, *reason;
1141 int l;
1142
1143 PasteArgs(ArgList, 1);
1144 if (!(channel = ArgList[0]))
1145 { rfc1459_odd(from, comm, ArgList); return; }
1146 if (!(reason = ArgList[1])) { }
1147
1148 if ((check_ignore_channel(from, FromUserHost,
1149 channel, LEVEL_PART) != IGNORED)
1150 && !new_check_flooding(from, FromUserHost, channel,
1151 reason ? reason : star, LEVEL_PART))
1152 {
1153 l = message_from(channel, LEVEL_PART);
1154 if (reason) /* Dalnet part messages */
1155 {
1156 if (do_hook(PART_LIST, "%s %s %s %s",
1157 from, channel, FromUserHost, reason))
1158 say("%s has left channel %s because (%s)",
1159 from, check_channel_type(channel), reason);
1160 }
1161 else
1162 {
1163 if (do_hook(PART_LIST, "%s %s %s",
1164 from, channel, FromUserHost))
1165 say("%s has left channel %s",
1166 from, check_channel_type(channel));
1167 }
1168 pop_message_from(l);
1169 }
1170
1171 if (is_me(from_server, from))
1172 remove_channel(channel, from_server);
1173 else
1174 remove_from_channel(channel, from, from_server);
1175
1176 }
1177
1178 /*
1179 * Egads. i hope this is right.
1180 */
p_rpong(const char * from,const char * comm,const char ** ArgList)1181 static void p_rpong (const char *from, const char *comm, const char **ArgList)
1182 {
1183 const char * nick, *target_server, *millisecs, *orig_time;
1184 time_t delay;
1185
1186 if (!(nick = ArgList[0]))
1187 { rfc1459_odd(from, comm, ArgList); return; }
1188 if (!(target_server = ArgList[1]))
1189 { rfc1459_odd(from, comm, ArgList); return; }
1190 if (!(millisecs = ArgList[2]))
1191 { rfc1459_odd(from, comm, ArgList); return; }
1192 if (!(orig_time = ArgList[3]))
1193 { rfc1459_odd(from, comm, ArgList); return; }
1194
1195 /*
1196 * :server RPONG yournick remoteserv ms :yourargs
1197 *
1198 * ArgList[0] -- our nickname (presumably)
1199 * ArgList[1] -- The server we RPING'd
1200 * ArgList[2] -- The number of ms it took to return
1201 * ArgList[3] -- The arguments we passed (presumably)
1202 */
1203
1204 delay = time(NULL) - atol(orig_time);
1205 say("Pingtime %s - %s : %s ms (total delay: "INTMAX_FORMAT" s)",
1206 from, target_server, millisecs, (intmax_t)delay);
1207 }
1208
1209 /*
1210 * This is a special subset of server (OPER) notice.
1211 */
p_killmsg(const char * from,const char * to,const char * cline)1212 static int p_killmsg (const char *from, const char *to, const char *cline)
1213 {
1214 char *poor_sap;
1215 char *bastard;
1216 const char *path_to_bastard;
1217 const char *reason;
1218 char *line;
1219 int l, retval;
1220
1221 l = message_from(to, LEVEL_OPNOTE);
1222 line = LOCAL_COPY(cline);
1223 if (!(poor_sap = next_arg(line, &line)))
1224 return 0; /* MALFORMED - IGNORED */
1225
1226 /* Dalnet kill BBC and doesnt append the period */
1227 if (!end_strcmp(poor_sap, ".", 1))
1228 chop(poor_sap, 1);
1229
1230 /* dalnet kill BBC and doesnt use "From", but "from" */
1231 if (my_strnicmp(line, "From ", 5))
1232 {
1233 yell("Attempted to parse an ill-formed KILL request [%s %s]",
1234 poor_sap, line);
1235 pop_message_from(l);
1236 return 0;
1237 }
1238 line += 5;
1239 bastard = next_arg(line, &line);
1240
1241 /* Hybrid BBC and doesn't include the kill-path. */
1242 /* Fend off future BBC kills */
1243 if (my_strnicmp(line, "Path: ", 6))
1244 {
1245 path_to_bastard = "*";
1246 reason = line; /* Hope for the best */
1247 }
1248 else
1249 {
1250 line += 6;
1251 path_to_bastard = next_arg(line, &line);
1252 reason = line;
1253 }
1254
1255 retval = do_hook(KILL_LIST, "%s %s %s %s %s", from, poor_sap, bastard,
1256 path_to_bastard, reason);
1257 pop_message_from(l);
1258 return !retval;
1259 }
1260
1261
1262 /*
1263 * This is a special subset of NOTICEs, that were sent from the server
1264 * we are connected to (not a remote server), to us (not to a channel).
1265 */
p_snotice(const char * from,const char * to,const char * line)1266 static void p_snotice (const char *from, const char *to, const char *line)
1267 {
1268 const char * f;
1269 int l;
1270 int retval;
1271
1272 f = from;
1273 if (!f || !*f)
1274 if (!(f = get_server_itsname(from_server)))
1275 f = get_server_name(from_server);
1276
1277 /* OPERator Notices */
1278 if (!strncmp(line, "*** Notice -- ", 13))
1279 {
1280 if (!strncmp(line + 14, "Received KILL message for ", 26))
1281 {
1282 if (p_killmsg(f, to, line + 40))
1283 return;
1284 }
1285
1286 l = message_from(to, LEVEL_OPNOTE);
1287 retval = do_hook(OPER_NOTICE_LIST, "%s %s", f, line + 14);
1288 pop_message_from(l);
1289 if (!retval)
1290 return;
1291 }
1292
1293 l = message_from(to, LEVEL_SNOTE);
1294
1295 /* Check to see if the notice already has its own header... */
1296 if (do_hook(GENERAL_NOTICE_LIST, "%s %s %s", f, to, line))
1297 {
1298 if (*line == '*' || *line == '#')
1299 {
1300 if (do_hook(SERVER_NOTICE_LIST, "%s %s", f, line))
1301 put_it("%s", line);
1302 }
1303 else
1304 if (do_hook(SERVER_NOTICE_LIST, "%s *** %s", f, line))
1305 say("%s", line);
1306 }
1307
1308 pop_message_from(l);
1309 }
1310
1311 /*
1312 * The main handler for those wacky NOTICE commands...
1313 * This is as much like p_privmsg as i can get away with.
1314 */
p_notice(const char * from,const char * comm,const char ** ArgList)1315 static void p_notice (const char *from, const char *comm, const char **ArgList)
1316 {
1317 const char *target, *message;
1318 const char *real_target;
1319 int hook_type;
1320 const char * flood_channel = NULL;
1321 int l;
1322
1323 PasteArgs(ArgList, 1);
1324 if (!(target = ArgList[0]))
1325 { rfc1459_odd(from, comm, ArgList); return; }
1326 if (!(message = ArgList[1]))
1327 { rfc1459_odd(from, comm, ArgList); return; }
1328
1329 set_server_doing_notice(from_server, 1);
1330 sed = 0;
1331
1332 /* Do normal /CTCP reply handling */
1333 /* XXX -- Casting "message" to (char *) is cheating. */
1334 message = do_ctcp(0, from, target, (char *)
1335 #ifdef HAVE_INTPTR_T
1336 (intptr_t)
1337 #endif
1338 message);
1339 if (!*message) {
1340 new_check_flooding(from, FromUserHost, flood_channel,
1341 message, LEVEL_NOTICE);
1342 set_server_doing_notice(from_server, 0);
1343 return;
1344 }
1345
1346 /* If this is a @#chan or +#chan, ignore the @ or +. */
1347 real_target = target;
1348 if (is_target_channel_wall(target) &&
1349 im_on_channel(target + 1, from_server))
1350 target++;
1351
1352 /* For pesky prefix-less NOTICEs substitute the server's name */
1353 /* Check to see if it is a "Server Notice" */
1354 if (!from || !*from || !strcmp(get_server_itsname(from_server), from))
1355 {
1356 p_snotice(from, target, message);
1357 set_server_doing_notice(from_server, 0);
1358 return;
1359 }
1360
1361 /*
1362 * Note that NOTICEs from servers are not "server notices" unless
1363 * the target is not a channel (ie, it is sent to us). Any notice
1364 * that is sent to a channel is a normal NOTICE, notwithstanding
1365 * _who_ sent it.
1366 */
1367 if (is_channel(target) && im_on_channel(target, from_server))
1368 {
1369 flood_channel = target;
1370 hook_type = PUBLIC_NOTICE_LIST;
1371 }
1372 else if (!is_me(from_server, target))
1373 {
1374 flood_channel = NULL;
1375 hook_type = NOTICE_LIST;
1376 }
1377 else
1378 {
1379 flood_channel = NULL;
1380 hook_type = NOTICE_LIST;
1381 target = from;
1382 }
1383
1384 /* Check for /ignore's */
1385 if (check_ignore_channel(from, FromUserHost,
1386 target, LEVEL_NOTICE) == IGNORED)
1387 {
1388 set_server_doing_notice(from_server, 0);
1389 return;
1390 }
1391
1392 /* Let the user know if it is an encrypted notice */
1393 /* Note that this is always hooked, even during a flood */
1394 if (sed)
1395 {
1396 int do_return = 1;
1397
1398 sed = 0;
1399 l = message_from(target, LEVEL_NOTICE);
1400
1401 if (do_hook(ENCRYPTED_NOTICE_LIST, "%s %s %s",
1402 from, real_target, message))
1403 do_return = 0;
1404
1405 pop_message_from(l);
1406
1407 if (do_return) {
1408 set_server_doing_notice(from_server, 0);
1409 return;
1410 }
1411 }
1412
1413 if (new_check_flooding(from, FromUserHost, flood_channel,
1414 message, LEVEL_NOTICE)) {
1415 set_server_doing_notice(from_server, 0);
1416 return;
1417 }
1418
1419
1420 /* Go ahead and throw it to the user */
1421 l = message_from(target, LEVEL_NOTICE);
1422
1423 if (do_hook(GENERAL_NOTICE_LIST, "%s %s %s", from,real_target, message))
1424 {
1425 if (hook_type == NOTICE_LIST)
1426 {
1427 if (do_hook(hook_type, "%s %s", from, message))
1428 put_it("-%s- %s", from, message);
1429 }
1430 else
1431 {
1432 if (do_hook(hook_type, "%s %s %s", from, real_target, message))
1433 put_it("-%s:%s- %s", from, real_target, message);
1434 }
1435 }
1436
1437 /* Clean up and go home. */
1438 pop_message_from(l);
1439 set_server_doing_notice(from_server, 0);
1440
1441 /* Alas, this is not protected by protocol enforcement. :( */
1442 notify_mark(from_server, from, 1, 0);
1443 }
1444
rfc1459_odd(const char * from,const char * comm,const char ** ArgList)1445 void rfc1459_odd (const char *from, const char *comm, const char **ArgList)
1446 {
1447 const char * stuff;
1448
1449 PasteArgs(ArgList, 0);
1450 if (!(stuff = ArgList[0]))
1451 stuff = empty_string;
1452
1453 if (!from || !*from)
1454 from = "*";
1455 if (!comm || !*comm)
1456 comm = "*";
1457
1458 if (do_hook(ODD_SERVER_STUFF_LIST, "%s %s %s", from, comm, stuff))
1459 say("Odd server stuff: \"%s %s\" (%s)", comm, stuff, from);
1460 }
1461
1462 typedef struct {
1463 const char *command;
1464 void (*inbound_handler) (const char *, const char *, const char **);
1465 int flags;
1466 } protocol_command;
1467 #define PROTO_QUOTEBAD (1 << 0)
1468
1469 static protocol_command rfc1459[] = {
1470 { "ADMIN", NULL, 0 },
1471 { "AWAY", NULL, 0 },
1472 { "CONNECT", NULL, 0 },
1473 { "ERROR", p_error, 0 },
1474 { "ERROR:", p_error, 0 },
1475 { "INFO", NULL, 0 },
1476 { "INVITE", p_invite, 0 },
1477 { "ISON", NULL, PROTO_QUOTEBAD },
1478 { "JOIN", p_channel, 0 },
1479 { "KICK", p_kick, 0 },
1480 { "KILL", p_kill, 0 },
1481 { "LINKS", NULL, 0 },
1482 { "LIST", NULL, 0 },
1483 { "MODE", p_mode, 0 },
1484 { "NAMES", NULL, 0 },
1485 { "NICK", p_nick, PROTO_QUOTEBAD },
1486 { "NOTICE", p_notice, 0 },
1487 { "OPER", NULL, 0 },
1488 { "PART", p_part, 0 },
1489 { "PASS", NULL, 0 },
1490 { "PING", p_ping, 0 },
1491 { "PONG", p_pong, 0 },
1492 { "PRIVMSG", p_privmsg, 0 },
1493 { "QUIT", p_quit, PROTO_QUOTEBAD },
1494 { "REHASH", NULL, 0 },
1495 { "RESTART", NULL, 0 },
1496 { "RPONG", p_rpong, 0 },
1497 { "SERVER", NULL, PROTO_QUOTEBAD },
1498 { "SILENCE", p_silence, 0 },
1499 { "SQUIT", NULL, 0 },
1500 { "STATS", NULL, 0 },
1501 { "SUMMON", NULL, 0 },
1502 { "TIME", NULL, 0 },
1503 { "TOPIC", p_topic, 0 },
1504 { "TRACE", NULL, 0 },
1505 { "USER", NULL, 0 },
1506 { "USERHOST", NULL, PROTO_QUOTEBAD },
1507 { "USERS", NULL, 0 },
1508 { "VERSION", NULL, 0 },
1509 { "WALLOPS", p_wallops, 0 },
1510 { "WHO", NULL, PROTO_QUOTEBAD },
1511 { "WHOIS", NULL, 0 },
1512 { "WHOWAS", NULL, 0 },
1513 { NULL, NULL, 0 }
1514 };
1515 #define NUMBER_OF_COMMANDS (sizeof(rfc1459) / sizeof(protocol_command)) - 2;
1516 static int num_protocol_cmds = -1;
1517
1518 #define islegal(c) ((((c) >= 'A') && ((c) <= '~')) || \
1519 (((c) >= '0') && ((c) <= '9')) || \
1520 ((c) == '*') || \
1521 ((c) & 0x80))
1522
1523 /*
1524 * parse_server: parses messages from the server, doing what should be done
1525 * with them
1526 */
parse_server(const char * orig_line,size_t orig_line_size)1527 void parse_server (const char *orig_line, size_t orig_line_size)
1528 {
1529 const char *from;
1530 const char *comm;
1531 const char **ArgList;
1532 const char *TrueArgs[MAXPARA + 2]; /* Include space for command */
1533 const char *OldFromUserHost;
1534 int loc;
1535 char *line;
1536
1537 if (num_protocol_cmds == -1)
1538 num_protocol_cmds = NUMBER_OF_COMMANDS;
1539
1540 if (!orig_line || !*orig_line)
1541 return; /* empty line from server -- bye bye */
1542
1543 if (*orig_line == ':')
1544 {
1545 if (!do_hook(RAW_IRC_LIST, "%s", orig_line + 1))
1546 return;
1547 }
1548 else if (!do_hook(RAW_IRC_LIST, "* %s", orig_line))
1549 return;
1550
1551 if (inbound_line_mangler)
1552 {
1553 char *s;
1554 s = new_normalize_string(orig_line, 1, inbound_line_mangler);
1555 line = LOCAL_COPY(s);
1556 new_free(&s);
1557 }
1558 else
1559 line = LOCAL_COPY(orig_line);
1560
1561 OldFromUserHost = FromUserHost;
1562 FromUserHost = empty_string;
1563 ArgList = TrueArgs;
1564 BreakArgs(line, &from, ArgList);
1565
1566 if ((!(comm = *ArgList++)) || !from || !*ArgList)
1567 {
1568 rfc1459_odd(from, comm, ArgList);
1569 return; /* Serious protocol violation -- ByeBye */
1570 }
1571
1572 if (*from && !islegal(*from))
1573 {
1574 rfc1459_odd(from, comm, ArgList);
1575 return;
1576 }
1577
1578 /* Some day all this needs to be replaced with an alist. */
1579 if (is_number(comm))
1580 numbered_command(from, comm, ArgList);
1581 else
1582 {
1583 for (loc = 0; rfc1459[loc].command; loc++)
1584 if (!strcmp(rfc1459[loc].command, comm))
1585 break;
1586
1587 if (rfc1459[loc].command && rfc1459[loc].inbound_handler)
1588 rfc1459[loc].inbound_handler(from, comm, ArgList);
1589 else
1590 rfc1459_odd(from, comm, ArgList);
1591 }
1592
1593 FromUserHost = OldFromUserHost;
1594 from_server = -1;
1595 }
1596
1597 /*
1598 * rfc1459_any_to_utf8 - Pre-process an RFC1459 message so it's in utf8.
1599 *
1600 * Arguments:
1601 * buffer - A null terminated RFC1459 message (not in utf8 already)
1602 * Upon return, if possible, will hold the message in utf8.
1603 * If not possible, 'extra' will hold the message.
1604 * buffsiz - How many bytes 'buffer' can hold.
1605 * extra - A pointer to NULL -- if 'buffer' is bigger than 'buffsiz'
1606 * after converting to utf8, then this will be set to a
1607 * new_malloc()ed string. YOU MUST new_free() THIS IF IT IS SET!
1608 *
1609 * This function divides an RFC1459 message into two parts
1610 * 1. The "server part"
1611 * 2. The "payload part"
1612 * The "server part" is formatted by the server, and contains nicks and
1613 * channel names, and those will be encoded with the server's encoding.
1614 * The "payload part" is encoded with whatever the sender of the message
1615 * is using, which may not be the same thing as the server.
1616 *
1617 * The "server part" is recoded first -- which yields utf8 channels and nicks,
1618 * which are then used to recode the "payload part". The two parts are put
1619 * back together into "buffer" unless the result is bigger than "buffsiz"
1620 * in which case it's put into a new_malloc()ed buffer stashed in 'extra'.
1621 *
1622 * XXX Ugh. I hate that I made this so complicated just to generalize this.
1623 */
rfc1459_any_to_utf8(char * buffer,size_t buffsiz,char ** extra)1624 void rfc1459_any_to_utf8 (char *buffer, size_t buffsiz, char **extra)
1625 {
1626 char * server_part;
1627 char * payload_part;
1628 size_t bytes_needed = 0;
1629 char * from = NULL, *to = NULL;
1630 char * command = NULL;
1631 char * endp;
1632 char * extra_server_part = NULL;
1633 char * extra_payload_part = NULL;
1634 int bytes;
1635
1636 if (x_debug & DEBUG_RECODE)
1637 yell(">> Received %s", buffer);
1638
1639 /*
1640 * For the benefit of Fusion, I want to support ISO-2022-JP.
1641 * This encoding is "valid" UTF-8, but is not UTF-8. So we need
1642 * to check it first.
1643 *
1644 * Please see http://www.sljfaq.org/afaq/encodings.html
1645 * There are other Japanese encodings that collide with UTF-8,
1646 * but I don't have any users to test, so I'm focused on what
1647 * I can actually test.
1648 */
1649 if (is_iso2022_jp(buffer))
1650 {
1651 if (x_debug & DEBUG_RECODE)
1652 yell(">> This looks like a JIS message...");
1653 }
1654 /* If the data is already utf8, then do nothing. */
1655 else if ((bytes = invalid_utf8str(buffer)) == 0)
1656 {
1657 return;
1658 }
1659 else
1660 {
1661 if (x_debug & DEBUG_RECODE)
1662 yell(">> There are %d invalid utf8 sequences...", bytes);
1663 }
1664
1665 /*
1666 * Point the "server part" at the start, and move the
1667 * "payload part" to the argument starting with colon.
1668 */
1669 server_part = buffer;
1670 for (payload_part = server_part; *payload_part; payload_part++)
1671 {
1672 if (payload_part[0] == ' ' && payload_part[1] == ':')
1673 {
1674 *payload_part++ = 0; /* Whack the space */
1675 if (x_debug & DEBUG_RECODE)
1676 yell(">> Found payload (%ld bytes): %s",
1677 (long)strlen(payload_part), payload_part);
1678 break;
1679 }
1680 }
1681
1682 if (x_debug & DEBUG_RECODE)
1683 {
1684 yell(">> server part is %s, payload_part is %s",
1685 server_part, payload_part);
1686 }
1687
1688 /*
1689 * If "payload_part" is pointing at a nul here, there was no payload.
1690 */
1691
1692
1693 /*
1694 * If the server part is not in utf8, then we need to convert it to
1695 * utf8 using the server's encoding to get nicks and channels in utf8.
1696 */
1697 if (is_iso2022_jp(server_part) || invalid_utf8str(server_part))
1698 {
1699 if (x_debug & DEBUG_RECODE)
1700 say(">> Need to recode server part for %d", from_server);
1701
1702 /*
1703 * XXX The use of the bogus nick "zero" is just because
1704 * ``from'' can't be NULL, but we want it to use the
1705 * server's default encoding.
1706 */
1707 inbound_recode(zero, from_server, NULL, server_part, &extra_server_part);
1708 if (extra_server_part)
1709 server_part = extra_server_part;
1710
1711 if (x_debug & DEBUG_RECODE)
1712 say(">> Recoded server part: %s", server_part);
1713 }
1714
1715 /*
1716 * If the payload part exists and is not valid utf8, then we need
1717 * to figure out who sent this message, and recode it with their
1718 * encoding. This deals with channels and nicks already in utf8.
1719 */
1720 if (*payload_part &&
1721 (is_iso2022_jp(payload_part) || invalid_utf8str(payload_part)))
1722 do
1723 {
1724 char * server_part_copy;
1725
1726 if (x_debug & DEBUG_RECODE)
1727 say(">> Need to recode payload part for %d", from_server);
1728
1729 server_part_copy = LOCAL_COPY(server_part);
1730
1731 /*
1732 * Figure out who the sender is (-> "from")
1733 * Put 'endp' at the start of the command word
1734 */
1735 if (*server_part_copy == ':')
1736 {
1737 from = server_part_copy + 1;
1738 for (endp = from; *endp; endp++)
1739 {
1740 if (*endp == '!')
1741 *endp++ = 0;
1742
1743 /* Not connected, so don't "fix" it */
1744 if (*endp == ' ')
1745 {
1746 *endp++ = 0;
1747 break;
1748 }
1749 }
1750
1751 /*
1752 * So now 'from' points to the nick or server
1753 * that sent us the message, and 'endp' points
1754 * at the word after the prefix
1755 */
1756 }
1757 else
1758 {
1759 from = NULL;
1760 endp = server_part_copy;
1761 }
1762
1763 /* Skip over the command word */
1764 command = endp;
1765 for (; *endp; endp++)
1766 {
1767 if (*endp == ' ')
1768 {
1769 *endp++ = 0;
1770 break;
1771 }
1772 }
1773
1774 /* "endp" points at the target word (or a nul) */
1775 to = endp;
1776 for (; *endp; endp++)
1777 {
1778 if (*endp == ' ')
1779 {
1780 *endp++ = 0;
1781 break;
1782 }
1783 }
1784
1785 if (from && !*from)
1786 from = NULL;
1787 if (to && !*to)
1788 to = NULL;
1789
1790 /*
1791 * XXX UGH! BLEH! HIDEOUS!
1792 *
1793 * Some things are not recode ready. We must detect them and
1794 * then force them to recode on their own later.
1795 * This is where the special cases are handled.
1796 */
1797
1798 /*
1799 * Special case #1 -- CTCP messages
1800 * Description:
1801 * A PRIVMSG or NOTICE where the first byte of the
1802 * payload is \001 and the final bytes of the payload
1803 * are \001\r\n, and there are no intervening \001s
1804 * shall be treated as a well-formed CTCP message/request
1805 * and is not subject to recoding.
1806 */
1807 if (!strcmp(command, "PRIVMSG") || !strcmp(command, "NOTICE"))
1808 {
1809 if (payload_part[0] == ':' && payload_part[1] == '\001')
1810 {
1811 const char *p;
1812
1813 /* The second \001 must be before newline */
1814 p = strchr(payload_part + 2, '\001');
1815 if (p && p[1] == 0)
1816 break;
1817
1818 /* Otherwise it's nonsense; recode it */
1819 }
1820 }
1821
1822
1823 /*
1824 * Everything else isn't subject to special casing.
1825 */
1826 if (x_debug & DEBUG_RECODE)
1827 say(">> Recoding payload from [%s], to [%s], server [%d]",
1828 from?from:"", to?to:"", from_server);
1829
1830 /* UTF8-ify the payload with 'from' and 'to' */
1831 inbound_recode(from, from_server, to, payload_part, &extra_payload_part);
1832 if (extra_payload_part)
1833 payload_part = extra_payload_part;
1834
1835 if (x_debug & DEBUG_RECODE)
1836 say(">> Recoded payload part: %s", payload_part);
1837 }
1838 while (0);
1839
1840 /* Make copies just to get them out of 'buffer' */
1841 server_part = LOCAL_COPY(server_part);
1842 payload_part = LOCAL_COPY(payload_part);
1843
1844 if (x_debug & DEBUG_RECODE)
1845 say(">> server part: %s", server_part);
1846 if (x_debug & DEBUG_RECODE)
1847 say(">> payload part: %s", payload_part);
1848
1849 /*
1850 * Now paste the two parts back together.
1851 */
1852 bytes_needed = strlen(server_part);
1853 if (*payload_part)
1854 bytes_needed += strlen(payload_part) + 1;
1855
1856 if (bytes_needed > buffsiz)
1857 {
1858 *extra = new_malloc(bytes_needed + 2);
1859 buffer = *extra;
1860 buffsiz = bytes_needed + 1;
1861 }
1862
1863 strlcpy(buffer, server_part, buffsiz);
1864 if (*payload_part)
1865 {
1866 strlcat(buffer, " ", buffsiz);
1867 strlcat(buffer, payload_part, buffsiz);
1868 }
1869
1870 if (x_debug & DEBUG_RECODE)
1871 say(">> Reconstituted UTF8 message: %s", buffer);
1872
1873 new_free(&extra_server_part);
1874 new_free(&extra_payload_part);
1875 }
1876
1877