AmendHub

Download

jcs

/

wallops

/

irc.c

 

(View History)

jcs   *: Support /monitor and its numerics, do better server reconnecting Latest amendment: 129 on 2024-09-22

1 /*
2 * Copyright (c) 2021-2022 joshua stein <jcs@jcs.org>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include <ctype.h>
18 #include <string.h>
19 #include <stdarg.h>
20
21 #include "chatter.h"
22 #include "irc.h"
23 #include "settings.h"
24
25 short irc_recv(struct irc_connection *conn);
26 size_t irc_send(struct irc_connection *conn, char *line, size_t size);
27 size_t irc_printf(struct irc_connection *conn, const char *format, ...);
28 char * irc_get_line(struct irc_connection *conn, size_t *retsize);
29 struct irc_user * irc_parse_user(char *str);
30 bool irc_can_send(struct irc_connection *conn);
31 struct irc_channel * irc_find_channel(struct irc_connection *conn,
32 char *channame);
33 void irc_register(struct irc_connection *conn);
34 bool irc_process_server(struct irc_connection *conn);
35 struct irc_channel * irc_create_channel(struct irc_connection *conn,
36 char *channame);
37 void irc_quit_with_message(struct irc_connection *conn, char *str);
38 void irc_dealloc_channel(struct irc_channel *channel);
39 void irc_set_active_channel(struct irc_connection *conn, char *channame);
40 void irc_parse_names(struct irc_channel *channel, char *line);
41 void irc_add_nick_to_channel(struct irc_channel *channel, char *nick,
42 short flags, short count_hint);
43 void irc_remove_nick_from_channel(struct irc_channel *channel, char *nick);
44 bool irc_nick_is_in_channel(struct irc_channel *channel, char *nick);
45 void irc_change_user_nick(struct irc_connection *conn,
46 struct irc_channel *channel, struct irc_user *user, char *nick);
47 bool irc_parse_channel_mode_change(struct irc_channel *channel, char *mode,
48 char *args);
49 void irc_do_mode_to_users(struct irc_connection *conn, char *channel_name,
50 char *mode, char *nicks);
51
52 #define IRC_CAN_SEND(conn) ((conn)->send_pb.ioResult <= 0)
53
54 struct irc_connections_head irc_connections_list =
55 SLIST_HEAD_INITIALIZER(irc_connections_list);
56
57 struct irc_connection *
58 irc_connect(struct chatter *chatter, const char *server,
59 const unsigned short port, const char *password, const char *nick,
60 const char *ident, const char *realname, bool hide_motd,
61 const char *channel)
62 {
63 struct irc_connection *conn;
64 char ip_str[] = "255.255.255.255";
65 char *retname;
66 ip_addr ip = 0, local_ip = 0;
67 ip_port local_port = 0;
68 size_t len;
69 short err;
70
71 if ((err = _TCPInit()) != 0)
72 panic("TCPInit failed (%d)", err);
73
74 conn = xmalloczero(sizeof(struct irc_connection));
75 if (conn == NULL)
76 goto conn_fail;
77 SLIST_APPEND(&irc_connections_list, conn, irc_connection, list);
78 SLIST_INIT(&conn->channels_list);
79 conn->chatter = chatter;
80 conn->state = IRC_STATE_DISCONNECTED;
81 conn->hostname = xstrdup(server);
82 if (conn->hostname == NULL)
83 goto conn_fail;
84 conn->port = port;
85 if (password && password[0]) {
86 conn->password = xstrdup(password);
87 if (conn->password == NULL)
88 goto conn_fail;
89 }
90
91 conn->nick_len = strlen(nick);
92 conn->nick = xstrdup(nick);
93 if (conn->nick == NULL)
94 goto conn_fail;
95 conn->ident = xstrdup(ident);
96 if (conn->ident == NULL)
97 goto conn_fail;
98 conn->realname = xstrdup(realname);
99 if (conn->realname == NULL)
100 goto conn_fail;
101 conn->hide_motd = hide_motd;
102 if (channel && channel[0]) {
103 conn->channel_autojoin = xstrdup(channel);
104 if (conn->channel_autojoin == NULL)
105 goto conn_fail;
106 }
107
108 chatter_printf(conn->chatter, conn, NULL,
109 "$B***$0 Connecting to $B%s$0 port $B%d$0...", conn->hostname,
110 conn->port);
111
112 if ((err = _TCPCreate(&conn->send_pb, &conn->stream, (Ptr)conn->tcp_buf,
113 sizeof(conn->tcp_buf), NULL, NULL, NULL, false)) != noErr) {
114 chatter_printf(conn->chatter, conn, NULL,
115 "$B*!* TCPCreate failed: %d$0", err);
116 return conn;
117 }
118
119 if ((err = DNSResolveName(conn->hostname, &ip, NULL)) != 0) {
120 chatter_printf(conn->chatter, conn, NULL,
121 "$B*!* Couldn't resolve host %s (%d)$0", conn->hostname, err);
122 conn->state = IRC_STATE_DEAD;
123 return conn;
124 }
125
126 long2ip(ip, ip_str);
127
128 if ((err = _TCPActiveOpen(&conn->send_pb, conn->stream, ip,
129 conn->port, &local_ip, &local_port, NULL, NULL, false)) != noErr) {
130 chatter_printf(conn->chatter, conn, NULL,
131 "$B*!* Failed connecting to %s (%s) port %d: %d$0",
132 conn->hostname, ip_str, conn->port, err);
133 conn->state = IRC_STATE_DEAD;
134 return conn;
135 }
136
137 chatter_printf(conn->chatter, conn, NULL,
138 "$B***$0 Connected to $B%s$0 (%s) port $B%d$0", conn->hostname,
139 ip_str, conn->port);
140
141 conn->state = IRC_STATE_UNREGISTERED;
142
143 return conn;
144
145 conn_fail:
146 warn("Out of memory for new connection");
147 if (conn != NULL)
148 irc_dealloc_connection(conn);
149 return NULL;
150 }
151
152 void
153 irc_close_connection(struct irc_connection *conn)
154 {
155 if (conn->state == IRC_STATE_CONNECTED)
156 irc_quit_with_message(conn, NULL);
157
158 if (conn->stream) {
159 _TCPAbort(&conn->close_pb, conn->stream, NULL, NULL, false);
160 _TCPRelease(&conn->close_pb, conn->stream, NULL, NULL, false);
161 conn->stream = 0;
162 }
163
164 conn->state = IRC_STATE_DISCONNECTED;
165 }
166
167 void
168 irc_dealloc_connection(struct irc_connection *conn)
169 {
170 struct irc_connection *tconn;
171 short n;
172
173 irc_close_connection(conn);
174
175 while (!SLIST_EMPTY(&conn->channels_list))
176 irc_dealloc_channel(SLIST_FIRST(&conn->channels_list));
177
178 if (conn->hostname != NULL)
179 xfree(&conn->hostname);
180 if (conn->password != NULL)
181 xfree(&conn->password);
182 if (conn->nick != NULL)
183 xfree(&conn->nick);
184 if (conn->ident != NULL)
185 xfree(&conn->ident);
186 if (conn->realname != NULL)
187 xfree(&conn->realname);
188 if (conn->channel_autojoin != NULL)
189 xfree(&conn->channel_autojoin);
190
191 SLIST_REMOVE(&irc_connections_list, conn, irc_connection, list);
192
193 xfree(&conn);
194 }
195
196 void
197 irc_process(struct irc_connection *conn)
198 {
199 if (conn->state >= IRC_STATE_UNREGISTERED)
200 irc_recv(conn);
201
202 switch (conn->state) {
203 case IRC_STATE_DISCONNECTED:
204 break;
205 case IRC_STATE_DEAD:
206 irc_close_connection(conn);
207 break;
208 case IRC_STATE_UNREGISTERED:
209 irc_register(conn);
210 break;
211 case IRC_STATE_CONNECTED:
212 irc_process_server(conn);
213 break;
214 }
215 }
216
217 short
218 irc_recv(struct irc_connection *conn)
219 {
220 unsigned short rlen;
221 short error, rerror, n;
222
223 if (conn->state < IRC_STATE_UNREGISTERED)
224 return 0;
225
226 error = _TCPStatus(&conn->rcv_pb, conn->stream, &conn->status_pb, NULL,
227 NULL, false);
228
229 if (conn->status_pb.amtUnreadData > 0 &&
230 conn->ibuflen < sizeof(conn->ibuf)) {
231 rlen = conn->status_pb.amtUnreadData;
232 if (conn->ibuflen + rlen > sizeof(conn->ibuf))
233 rlen = sizeof(conn->ibuf) - conn->ibuflen;
234
235 rerror = _TCPRcv(&conn->rcv_pb, conn->stream,
236 (Ptr)(conn->ibuf + conn->ibuflen), &rlen, NULL, NULL, false);
237 if (rerror) {
238 chatter_printf(conn->chatter, conn, NULL,
239 "$B*!* TCPRecv failed (%d), disconnecting$0", error);
240 conn->state = IRC_STATE_DEAD;
241 return -1;
242 }
243
244 conn->ibuflen += rlen;
245 }
246
247 if (error ||
248 conn->status_pb.connectionState != ConnectionStateEstablished) {
249 /* let already-consumed buffer finish processing */
250 while (irc_process_server(conn))
251 SystemTask();
252 if (error)
253 chatter_printf(conn->chatter, conn, NULL,
254 "$B*!* TCPStatus failed: %d$0",
255 error);
256 else
257 chatter_printf(conn->chatter, conn, NULL,
258 "$B*!* Connection closed$0");
259 conn->state = IRC_STATE_DEAD;
260 return -1;
261 }
262
263 return rlen;
264 }
265
266 size_t
267 irc_send(struct irc_connection *conn, char *line, size_t size)
268 {
269 short error;
270
271 if (size > sizeof(conn->obuf))
272 panic("irc_send: too much data (%lu)", size);
273
274 if (conn->state < IRC_STATE_UNREGISTERED)
275 return 0;
276
277 while (!IRC_CAN_SEND(conn))
278 SystemTask();
279
280 if (conn->send_pb.ioResult < 0) {
281 chatter_printf(conn->chatter, conn, NULL,
282 "$B*!* TCPSend failed (%d), disconnecting$0",
283 conn->send_pb.ioResult);
284 conn->state = IRC_STATE_DEAD;
285 return 0;
286 }
287
288 memcpy(&conn->obuf, line, size);
289
290 /*
291 * _TCPSend only knows how many wds pointers were passed in when it
292 * reads the next one and its pointer is zero (or size is zero?), so
293 * even though we're only sending one wds, memory after wds[0]
294 * has to be zeroed out.
295 */
296 memset(&conn->wds, 0, sizeof(conn->wds));
297 conn->wds[0].ptr = (Ptr)&conn->obuf;
298 conn->wds[0].length = size;
299
300 error = _TCPSend(&conn->send_pb, conn->stream, conn->wds, NULL, NULL,
301 true);
302 if (error) {
303 chatter_printf(conn->chatter, conn, NULL,
304 "$B*!* TCPSend failed (%d), disconnecting$0", error);
305 conn->state = IRC_STATE_DEAD;
306 return 0;
307 }
308
309 return size;
310 }
311
312 size_t
313 irc_printf(struct irc_connection *conn, const char *format, ...)
314 {
315 static char buf[512];
316 va_list argptr;
317 size_t len = 0;
318
319 va_start(argptr, format);
320 len = vsnprintf(buf, sizeof(buf), format, argptr);
321 va_end(argptr);
322
323 if (len > sizeof(buf)) {
324 warn("irc_printf overflow!");
325 len = sizeof(buf);
326 buf[len - 1] = '\0';
327 }
328
329 irc_send(conn, buf, len);
330
331 return len;
332 }
333
334 struct irc_user *
335 irc_parse_user(char *str)
336 {
337 static struct irc_user parsed_user;
338 short ret;
339
340 memset(&parsed_user, 0, sizeof(parsed_user));
341 ret = sscanf(str, "%[^!]!%[^@]@%s", &parsed_user.nick,
342 &parsed_user.username, &parsed_user.hostname);
343 if (ret != 3) {
344 /* probably just a hostname */
345 strlcpy(parsed_user.nick, str, sizeof(parsed_user.nick));
346 strlcpy(parsed_user.username, str, sizeof(parsed_user.username));
347 strlcpy(parsed_user.hostname, str, sizeof(parsed_user.hostname));
348 }
349 return &parsed_user;
350 }
351
352 char *
353 irc_get_line(struct irc_connection *conn, size_t *retsize)
354 {
355 size_t n;
356
357 if (conn->ibuflen == 0) {
358 if (retsize != NULL)
359 *retsize = 0;
360 return NULL;
361 }
362
363 for (n = 0; n < conn->ibuflen - 1; n++) {
364 if (conn->ibuf[n] != '\r')
365 continue;
366 if (conn->ibuf[n + 1] != '\n')
367 continue;
368
369 if (conn->flushing_ibuf) {
370 conn->flushing_ibuf = false;
371 conn->line[0] = '\0';
372 } else if (n + 1 > sizeof(conn->line) - 1) {
373 /*
374 * Line is too long to process but we can't take part of it,
375 * so bail on it and hope it wasn't something important :(
376 */
377 chatter_printf(conn->chatter, NULL, NULL,
378 "*!* Line too long to process");
379 conn->line[0] = '\0';
380 } else {
381 memcpy(conn->line, conn->ibuf, n + 1);
382 conn->line[n] = '\0';
383 if (retsize != NULL)
384 *retsize = n + 1;
385 }
386
387 if (n == conn->ibuflen - 2) {
388 conn->ibuflen = 0;
389 } else {
390 conn->ibuflen -= n + 2;
391 if (conn->ibuflen < 0)
392 panic("irc_get_line: bogus ibuflen %d", conn->ibuflen);
393 memmove(conn->ibuf, conn->ibuf + n + 2, conn->ibuflen);
394 }
395 return conn->line;
396 }
397
398 if (n >= sizeof(conn->ibuf) - 1) {
399 /*
400 * ibuf is full with no newline but we can't empty the buffer and
401 * start the next iteration in the middle of this line, so
402 * consider ibuf bogus until we see a newline.
403 */
404 conn->flushing_ibuf = true;
405 conn->ibuflen = 0;
406 chatter_printf(conn->chatter, NULL, NULL,
407 "*!* ibuf with no newline, flushing");
408 }
409
410 return NULL;
411 }
412
413 struct irc_channel *
414 irc_find_channel(struct irc_connection *conn, char *channame)
415 {
416 struct irc_channel *channel;
417
418 if (channame == NULL || channame[0] == '\0')
419 return NULL;
420
421 SLIST_FOREACH(channel, &conn->channels_list, list) {
422 if (strcasecmp(channel->name, channame) == 0)
423 return channel;
424 }
425
426 return NULL;
427 }
428
429 void
430 irc_register(struct irc_connection *conn)
431 {
432 size_t len;
433
434 if (conn->state == IRC_STATE_CONNECTED)
435 return;
436
437 if (conn->password && conn->password[0]) {
438 len = snprintf(conn->line, sizeof(conn->line),
439 "PASS %s\r\n", conn->password);
440 irc_send(conn, conn->line, len);
441 }
442
443 len = snprintf(conn->line, sizeof(conn->line),
444 "NICK %s\r\n", conn->nick);
445 irc_send(conn, conn->line, len);
446
447 len = snprintf(conn->line, sizeof(conn->line),
448 "USER %s 0 * :%s\r\n", conn->ident, conn->realname);
449 irc_send(conn, conn->line, len);
450
451 if (conn->state == IRC_STATE_UNREGISTERED)
452 conn->state = IRC_STATE_CONNECTED;
453 }
454
455 bool
456 irc_process_server(struct irc_connection *conn)
457 {
458 struct irc_msg msg;
459 struct irc_user *user;
460 struct irc_channel *channel;
461 struct chatter_tab *tab;
462 char *line, *word;
463 size_t size, n, lastbreak;
464 short curarg;
465
466 line = irc_get_line(conn, &size);
467 if (size == 0 || line == NULL)
468 return false;
469
470 memset(&msg, 0, sizeof(msg));
471
472 word = strsep(&line, " ");
473 if (word == NULL)
474 /* server sent us a blank line? */
475 return false;
476
477 /* extract source before command */
478 if (word[0] == ':') {
479 /* ":server.name 001 jcs :Hi" -> msg.source=server.name */
480 strlcpy(msg.source, word + 1, sizeof(msg.source));
481 word = strsep(&line, " ");
482 }
483
484 /* code or a command name */
485 if (isdigit(word[0]) && isdigit(word[1]) && isdigit(word[2]))
486 /* ":server.name 001 jcs :Hi" -> msg.code=1 */
487 msg.code = atoi(word);
488 else
489 /* "PING :server.name" -> msg.cmd=PING */
490 /* ":jcs!~jcs@jcs JOIN #wallops" -> msg.cmd=JOIN */
491 strlcpy(msg.cmd, word, sizeof(msg.cmd));
492
493 /* parameters, put into args until we see one starting with : */
494 curarg = 0;
495 while (line != NULL && line[0] != '\0') {
496 if (line[0] == ':') {
497 strlcpy(msg.msg, line + 1, sizeof(msg.msg));
498
499 /* some irc servers put the first arg as the message */
500 if (curarg == 0)
501 strlcpy(msg.arg[0], line + 1, sizeof(msg.arg[0]));
502 break;
503 }
504
505 word = strsep(&line, " ");
506 if (word == NULL) {
507 strlcpy(msg.msg, line, sizeof(msg.msg));
508 break;
509 }
510
511 strlcpy(msg.arg[curarg], word, sizeof(msg.arg[curarg]));
512 curarg++;
513
514 if (curarg >= IRC_MSG_MAX_ARGS) {
515 /* just stick the rest in msg */
516 strlcpy(msg.msg, line, sizeof(msg.msg));
517 break;
518 }
519 }
520
521 if (msg.cmd[0]) {
522 /* this one will fire most often, keep at the top */
523 if (strcmp(msg.cmd, "PRIVMSG") == 0) {
524 user = irc_parse_user(msg.source);
525 size = strlen(msg.msg);
526
527 if (msg.msg[0] == '\1' && msg.msg[size - 1] == '\1') {
528 /* CTCP or a /me action */
529 msg.msg[size - 1] = '\0';
530 if (strncmp(msg.msg + 1, "ACTION", 6) == 0) {
531 chatter_printf(conn->chatter, conn, msg.arg[0],
532 "* %s$/ %s",
533 user->nick, msg.msg + 8);
534 } else if (strncmp(msg.msg + 1, "VERSION", 7) == 0) {
535 chatter_printf(conn->chatter, conn, msg.arg[0],
536 "$B*** %s ($0%s@%s$B)$0 requested CTCP $BVERSION$0 "
537 "from $B%s$0",
538 user->nick, user->username, user->hostname,
539 msg.arg[0]);
540
541 /* only respond if it was sent directly to us */
542 if (strcmp(msg.arg[0], conn->nick) == 0) {
543 irc_printf(conn,
544 "NOTICE %s :\1VERSION %s %s on a %s\1\r\n",
545 user->nick, PROGRAM_NAME, get_version(false),
546 gestalt_machine_type());
547 }
548 } else {
549 chatter_printf(conn->chatter, conn, msg.arg[0],
550 "$B*** %s ($0%s@%s$B)$0 requested unknown CTCP "
551 "$B%s$0 from $B%s$0",
552 user->nick, user->username, user->hostname,
553 msg.msg + 1, msg.arg[0]);
554 }
555 } else if (strcmp(msg.arg[0], conn->nick) == 0) {
556 /* message to us */
557 if (chatter_find_tab(conn->chatter, conn, user->nick))
558 /* query with this user open */
559 chatter_printf(conn->chatter, conn, user->nick,
560 "$/<%s> %s",
561 user->nick, msg.msg);
562 else
563 chatter_printf(conn->chatter, conn, user->nick,
564 "$B[%s($0%s@%s$B)]$0$/ %s",
565 user->nick, user->username, user->hostname, msg.msg);
566 if (!conn->chatter->focusable->visible)
567 notify();
568 } else {
569 /* message to channel we're in */
570 char *us;
571
572 us = strcasestr(msg.msg, conn->nick);
573 if (us == msg.msg || us[-1] == ' ') {
574 if (us[conn->nick_len] == ' ' ||
575 us[conn->nick_len] == ':' ||
576 us[conn->nick_len] == ',' ||
577 us[conn->nick_len] == '!' ||
578 us[conn->nick_len] == '?' ||
579 us[conn->nick_len] == '\0') {
580 /* /^nick[ :,?!$]/ or / nick[ :,?!$]/ */
581 } else
582 us = NULL;
583 } else
584 us = NULL;
585
586 if (us) {
587 /* mention to us, highlight */
588 chatter_printf(conn->chatter, conn, msg.arg[0],
589 "<$U%s$0>$/ %s",
590 user->nick, msg.msg);
591 if (!conn->chatter->focusable->visible)
592 notify();
593 } else
594 chatter_printf(conn->chatter, conn, msg.arg[0],
595 "$/<%s> %s",
596 user->nick, msg.msg);
597 }
598 return true;
599 }
600 if (strcmp(msg.cmd, "PING") == 0) {
601 irc_printf(conn, "PONG :%s\r\n", msg.msg);
602 return true;
603 }
604
605 if (strcmp(msg.cmd, "ERROR") == 0) {
606 chatter_printf(conn->chatter, conn, NULL,
607 "$B*!* Disconnected$0 from $B%s$0:$/ %s",
608 conn->hostname, msg.msg);
609 conn->state = IRC_STATE_DEAD;
610 return true;
611 }
612 if (strcmp(msg.cmd, "JOIN") == 0) {
613 user = irc_parse_user(msg.source);
614 if (strcmp(user->nick, conn->nick) == 0)
615 channel = irc_create_channel(conn, msg.arg[0]);
616 else {
617 channel = irc_find_channel(conn, msg.arg[0]);
618 if (channel)
619 irc_add_nick_to_channel(channel, user->nick, 0, 0);
620 }
621 if (!(settings.ignores & IGNORE_JOINS))
622 chatter_printf(channel->chatter, conn, msg.arg[0],
623 "$B*** %s ($0%s@%s$B)$0 has joined $B%s$0",
624 user->nick, user->username, user->hostname, msg.arg[0]);
625 return true;
626 }
627 if (strcmp(msg.cmd, "KICK") == 0) {
628 user = irc_parse_user(msg.source);
629 if ((channel = irc_find_channel(conn, msg.arg[0]))) {
630 if (strcmp(msg.arg[1], conn->nick) == 0) {
631 irc_dealloc_channel(channel);
632 channel = NULL;
633 } else
634 irc_remove_nick_from_channel(channel, msg.arg[1]);
635 }
636 if (!(settings.ignores & IGNORE_QUITS))
637 chatter_printf(conn->chatter, conn, msg.arg[0],
638 "$B*** %s$0 was kicked from $B%s$0 by $B%s$0:$/ %s",
639 msg.arg[0], msg.arg[1], user->nick, msg.msg);
640 return true;
641 }
642 if (strcmp(msg.cmd, "KILL") == 0) {
643 user = irc_parse_user(msg.source);
644 if (strcmp(user->nick, conn->nick) == 0) {
645 /* we died :( */
646 warn("%s (%s@%s) has been killed: %s",
647 user->nick, user->username, user->hostname, msg.msg);
648 conn->state = IRC_STATE_DEAD;
649 return false;
650 }
651 SLIST_FOREACH(channel, &conn->channels_list, list) {
652 if (!irc_nick_is_in_channel(channel, user->nick))
653 continue;
654
655 chatter_printf(conn->chatter, conn, channel->name,
656 "$B*** %s ($0%s@%s$B)$0 has been killed:$/ %s",
657 user->nick, user->username, user->hostname, msg.msg);
658 irc_remove_nick_from_channel(channel, user->nick);
659 }
660 return true;
661 }
662 if (strcmp(msg.cmd, "MODE") == 0) {
663 if (strcmp(msg.arg[0], conn->nick) == 0)
664 chatter_printf(conn->chatter, conn, NULL,
665 "$B***$0 Mode change ($B%s$0) for user $B%s$0",
666 msg.msg, msg.arg[0]);
667 else {
668 user = irc_parse_user(msg.source);
669 channel = irc_find_channel(conn, msg.arg[0]);
670 /* concatenate nicks */
671 msg.msg[0] = '\0';
672 if (msg.arg[2][0] != '\0')
673 strlcat(msg.msg, msg.arg[2], sizeof(msg.msg));
674 if (msg.arg[3][0] != '\0') {
675 strlcat(msg.msg, " ", sizeof(msg.msg));
676 strlcat(msg.msg, msg.arg[3], sizeof(msg.msg));
677 }
678 if (msg.arg[4][0] != '\0') {
679 strlcat(msg.msg, " ", sizeof(msg.msg));
680 strlcat(msg.msg, msg.arg[4], sizeof(msg.msg));
681 }
682 if (msg.arg[5][0] != '\0') {
683 strlcat(msg.msg, " ", sizeof(msg.msg));
684 strlcat(msg.msg, msg.arg[5], sizeof(msg.msg));
685 }
686
687 if (channel && channel->mode[0] == '\0')
688 /* probably initial mode on join, wait for 324 */
689 return true;
690
691 chatter_printf(conn->chatter, conn,
692 channel ? channel->name : NULL,
693 "$B***$0 Mode change ($B%s %s$0) on $B%s$0 by $B%s$0",
694 msg.arg[1], msg.msg, msg.arg[0], user->nick);
695 if (channel) {
696 bool others = irc_parse_channel_mode_change(channel,
697 msg.arg[1], msg.msg);
698 if (others)
699 /*
700 * To simplify parsing mode changes like "+ik blah",
701 * just ask for the full mode now to get a 324
702 */
703 irc_printf(conn, "MODE %s\r\n", channel->name);
704 }
705 }
706 return true;
707 }
708 if (strcmp(msg.cmd, "NICK") == 0) {
709 struct chatter_tab *tab;
710
711 user = irc_parse_user(msg.source);
712 if (strcasecmp(user->nick, conn->nick) == 0) {
713 chatter_printf(conn->chatter, conn, NULL,
714 "$B*** %s$0 is now known as $B%s$0",
715 user->nick, msg.msg);
716 irc_change_user_nick(conn, NULL, user, msg.msg);
717 }
718 SLIST_FOREACH(channel, &conn->channels_list, list) {
719 if (!irc_nick_is_in_channel(channel, user->nick))
720 continue;
721
722 if (!(settings.ignores & IGNORE_NICKS))
723 chatter_printf(conn->chatter, conn, channel->name,
724 "$B*** %s$0 is now known as $B%s$0",
725 user->nick, msg.msg);
726 irc_change_user_nick(conn, channel, user, msg.msg);
727 }
728 SLIST_FOREACH(tab, &conn->chatter->tabs_list, list) {
729 if (tab->query_nick[0] &&
730 strcasecmp(tab->query_nick, user->nick) == 0) {
731 strlcpy(tab->query_nick, user->nick,
732 sizeof(user->nick));
733 conn->chatter->need_tab_bar_redraw = true;
734 }
735 }
736 return true;
737 }
738 if (strcmp(msg.cmd, "NOTICE") == 0) {
739 if (strchr(msg.source, '@') == NULL) {
740 /* server notice */
741 if (strncmp(msg.msg, "*** ", 4) == 0)
742 chatter_printf(conn->chatter, conn, NULL,
743 "$B***$0 $/%s",
744 msg.msg + 4);
745 else
746 chatter_printf(conn->chatter, conn, NULL,
747 "$/%s",
748 msg.msg);
749 } else {
750 /* user notice */
751 user = irc_parse_user(msg.source);
752 chatter_printf(conn->chatter, conn, user->nick,
753 "$Bnotice:[%s($0%s@%s$B)]$0$/ %s",
754 user->nick, user->username, user->hostname, msg.msg);
755 }
756 return true;
757 }
758 if (strcmp(msg.cmd, "PART") == 0) {
759 user = irc_parse_user(msg.source);
760 if ((channel = irc_find_channel(conn, msg.arg[0]))) {
761 if (strcmp(user->nick, conn->nick) == 0) {
762 irc_dealloc_channel(channel);
763 channel = NULL;
764 /* we don't need to print anything */
765 } else {
766 irc_remove_nick_from_channel(channel, user->nick);
767 if (!(settings.ignores & IGNORE_QUITS))
768 chatter_printf(conn->chatter, conn, channel->name,
769 "$B*** %s ($0%s@%s$B)$0 has left $B%s$0",
770 user->nick, user->username, user->hostname,
771 msg.arg[0]);
772 }
773 }
774 return true;
775 }
776 if (strcmp(msg.cmd, "QUIT") == 0) {
777 user = irc_parse_user(msg.source);
778
779 SLIST_FOREACH(channel, &conn->channels_list, list) {
780 if (!irc_nick_is_in_channel(channel, user->nick))
781 continue;
782
783 irc_remove_nick_from_channel(channel, user->nick);
784 if (!(settings.ignores & IGNORE_QUITS))
785 chatter_printf(conn->chatter, conn, channel->name,
786 "$B*** %s ($0%s@%s$B)$0 has quit:$/ %s",
787 user->nick, user->username, user->hostname, msg.msg);
788 }
789
790 if (strcmp(user->nick, conn->nick) == 0) {
791 chatter_printf(conn->chatter, conn, NULL,
792 "$B*** %s ($0%s@%s$B)$0 has quit:$/ %s",
793 user->nick, user->username, user->hostname, msg.msg);
794 conn->state = IRC_STATE_DEAD;
795 return false;
796 }
797 return true;
798 }
799 if (strcmp(msg.cmd, "TOPIC") == 0) {
800 user = irc_parse_user(msg.source);
801 chatter_printf(conn->chatter, conn, msg.arg[0],
802 "$B***$0 Topic for $B%s$0 changed by $B%s$0:$/ %s",
803 msg.arg[0], user->nick, msg.msg);
804 if ((channel = irc_find_channel(conn, msg.arg[0])))
805 strlcpy(channel->topic, msg.msg, sizeof(channel->topic));
806 return true;
807 }
808 if (strcmp(msg.cmd, "WALLOPS") == 0) {
809 chatter_printf(conn->chatter, conn, NULL,
810 "$B***$0 Wallops from $B%s$0:$/ %s",
811 msg.source, msg.msg);
812 return true;
813 }
814
815 goto unknown;
816 }
817
818 switch (msg.code) {
819 case 0:
820 goto unknown;
821 case 1:
822 case 2:
823 /* welcome banners */
824 goto print_msg;
825 case 3:
826 case 4:
827 case 5:
828 case 250:
829 case 251:
830 case 252:
831 case 253:
832 case 254:
833 case 255:
834 case 265:
835 case 266:
836 /* server stats, unhelpful */
837 return true;
838 case 221:
839 /* user mode */
840 chatter_printf(conn->chatter, conn, NULL,
841 "$B***$0 Mode for $B%s$0:$/ %s",
842 msg.arg[0], msg.arg[1]);
843 return true;
844 case 307:
845 /* WHOIS regnick */
846 chatter_printf(conn->chatter, conn, conn->current_whois,
847 "$B*** |$0 Authenticated:$/ %s",
848 msg.msg);
849 return true;
850 case 311:
851 /* WHOIS nick: "nick :ident hostname * :name" */
852 strlcpy(conn->current_whois, msg.arg[1], sizeof(conn->current_whois));
853 chatter_printf(conn->chatter, conn, conn->current_whois,
854 "$B*** %s ($0%s@%s$B)$0",
855 msg.arg[1], msg.arg[2], msg.arg[3]);
856 chatter_printf(conn->chatter, conn, conn->current_whois,
857 "$B*** |$0 Name:$/ %s",
858 msg.msg);
859 return true;
860 case 312:
861 /* WHOIS server */
862 chatter_printf(conn->chatter, conn, conn->current_whois,
863 "$B*** |$0 Server:$/ %s (%s)",
864 msg.arg[2], msg.msg);
865 return true;
866 case 315:
867 /* end of WHO */
868 chatter_printf(conn->chatter, conn, NULL,
869 "$B***$0$/ %s",
870 msg.msg);
871 return true;
872 case 317:
873 /* WHOIS idle */
874 chatter_printf(conn->chatter, conn, conn->current_whois,
875 "$B*** |$0 Idle:$/ %s %s",
876 msg.arg[2], msg.msg);
877 return true;
878 case 318:
879 /* WHOIS end */
880 chatter_printf(conn->chatter, conn, conn->current_whois,
881 "$B*** `-------$0");
882 memset(conn->current_whois, 0, sizeof(conn->current_whois));
883 return true;
884 case 319:
885 /* WHOIS channels */
886 chatter_printf(conn->chatter, conn, conn->current_whois,
887 "$B*** |$0 Channels:$/ %s",
888 msg.msg);
889 return true;
890 case 324:
891 /* channel mode, concatenate args */
892 msg.msg[0] = '\0';
893 if (msg.arg[2][0] != '\0')
894 strlcat(msg.msg, msg.arg[2], sizeof(msg.msg));
895 if (msg.arg[3][0] != '\0') {
896 strlcat(msg.msg, " ", sizeof(msg.msg));
897 strlcat(msg.msg, msg.arg[3], sizeof(msg.msg));
898 }
899 if (msg.arg[4][0] != '\0') {
900 strlcat(msg.msg, " ", sizeof(msg.msg));
901 strlcat(msg.msg, msg.arg[4], sizeof(msg.msg));
902 }
903 if (msg.arg[5][0] != '\0') {
904 strlcat(msg.msg, " ", sizeof(msg.msg));
905 strlcat(msg.msg, msg.arg[5], sizeof(msg.msg));
906 }
907 if ((channel = irc_find_channel(conn, msg.arg[1]))) {
908 if (channel->chatter && channel->mode[0] == '\0') {
909 /* initial mode set from join is unimportant */
910 tab = chatter_find_tab(conn->chatter, conn, msg.arg[1]);
911 if (tab)
912 tab->ignore_activity = true;
913 }
914 strlcpy(channel->mode, msg.msg, sizeof(channel->mode));
915 if (channel->chatter)
916 channel->chatter->need_tab_bar_redraw = true;
917 }
918 chatter_printf(conn->chatter, conn, msg.arg[1],
919 "$B***$0 Mode for $B%s$0:$/ %s",
920 msg.arg[1], msg.msg);
921 if (tab)
922 tab->ignore_activity = false;
923 return true;
924 case 328:
925 /* channel URL, we probably can't do anything with it anyway */
926 return true;
927 case 329:
928 /* channel creation timestamp */
929 return true;
930 case 330:
931 /* WHOIS account */
932 chatter_printf(conn->chatter, conn, conn->current_whois,
933 "$B*** |$0 Account:$/ %s %s",
934 msg.msg, msg.arg[2]);
935 return true;
936 case 331:
937 /* no TOPIC */
938 chatter_printf(conn->chatter, conn, msg.arg[1],
939 "$B***$0 No topic is set for $B%s$0",
940 msg.arg[1]);
941 if ((channel = irc_find_channel(conn, msg.arg[1])))
942 channel->topic[0] = '\0';
943 return true;
944 case 332:
945 /* TOPIC */
946 chatter_printf(conn->chatter, conn, msg.arg[1],
947 "$B***$0 Topic for $B%s$0:$/ %s",
948 msg.arg[1], msg.msg);
949 if ((channel = irc_find_channel(conn, msg.arg[1])))
950 strlcpy(channel->topic, msg.msg, sizeof(channel->topic));
951 return true;
952 case 333: {
953 /* TOPIC creator */
954 unsigned long ts;
955
956 user = irc_parse_user(msg.arg[2]);
957
958 /* TODO: convert unix to mac time, strftime */
959 sscanf(msg.arg[3], "%ld", &ts);
960
961 chatter_printf(conn->chatter, conn, msg.arg[1],
962 "$B***$0 Topic set by $B%s$0",
963 user->nick);
964 return true;
965 }
966 case 338:
967 case 378:
968 /* WHOIS host */
969 chatter_printf(conn->chatter, conn, conn->current_whois,
970 "$B*** |$0 Host:$/ %s %s",
971 msg.msg, msg.arg[2]);
972 return true;
973 case 352:
974 /* WHO output */
975 chatter_printf(conn->chatter, conn, NULL,
976 "$B***$0$/ %s %s@%s (%s)",
977 msg.arg[1], msg.arg[2], msg.arg[3], msg.msg);
978 return true;
979 case 353:
980 /* NAMES output */
981 if ((channel = irc_find_channel(conn, msg.arg[2])))
982 irc_parse_names(channel, msg.msg);
983 return true;
984 case 366:
985 /* end of NAMES output */
986 if ((channel = irc_find_channel(conn, msg.arg[1]))) {
987 channel->parsing_nicks = false;
988 chatter_sync_nick_list(channel->chatter, channel);
989 }
990 return true;
991 case 372:
992 case 375:
993 /* MOTD */
994 if (conn->hide_motd)
995 return true;
996 goto print_msg;
997 case 376:
998 /* end of MOTD */
999 if (conn->channel_autojoin && conn->channel_autojoin[0] &&
1000 !conn->did_autojoin) {
1001 irc_printf(conn, "JOIN %s\r\n", conn->channel_autojoin);
1002 conn->did_autojoin = true;
1003 }
1004 if (conn->hide_motd)
1005 return true;
1006 goto print_msg;
1007 case 396:
1008 /* Cloak */
1009 chatter_printf(conn->chatter, conn, NULL,
1010 "$B***$0$/ %s %s",
1011 msg.arg[1], msg.msg);
1012 return true;
1013 case 433: {
1014 /* Nick in use, try appending a _ */
1015 char *new_nick = xmalloc(strlen(conn->nick) + 2);
1016 size_t len;
1017
1018 chatter_printf(conn->chatter, conn, NULL,
1019 "$B***$0$/ %s: %s",
1020 msg.arg[1], msg.msg);
1021
1022 sprintf(new_nick, "%s_", conn->nick);
1023 xfree(&conn->nick);
1024 conn->nick = new_nick;
1025 chatter_update_titlebar(conn->chatter);
1026
1027 len = snprintf(conn->line, sizeof(conn->line),
1028 "NICK %s\r\n", conn->nick);
1029 irc_send(conn, conn->line, len);
1030 return true;
1031 }
1032 case 461:
1033 /* not enough args */
1034 chatter_printf(conn->chatter, conn, NULL,
1035 "$B***$0 $B%s$0$/: %s",
1036 msg.arg[1], msg.msg);
1037 return true;
1038 case 475:
1039 /* can't join channel */
1040 chatter_printf(conn->chatter, conn, NULL,
1041 "$B***$0 Can't join $B%s$0$/: %s",
1042 msg.arg[1], msg.msg);
1043 return true;
1044 case 481:
1045 /* not oper */
1046 chatter_printf(conn->chatter, conn, NULL,
1047 "$B***$0 You can't do that thing if you ain't got that "
1048 "swing:$/: %s",
1049 msg.msg);
1050 return true;
1051 case 482:
1052 /* not op */
1053 chatter_printf(conn->chatter, conn, NULL,
1054 "$B***$0 Can't do that on $B%s$0$/: %s",
1055 msg.arg[1], msg.msg);
1056 return true;
1057 case 502:
1058 /* error */
1059 chatter_printf(conn->chatter, conn, NULL,
1060 "$B***$0$/ %s",
1061 msg.msg);
1062 return true;
1063 case 671:
1064 /* WHOIS server */
1065 chatter_printf(conn->chatter, conn, conn->current_whois,
1066 "$B*** |$0 Connection:$/ %s",
1067 msg.msg);
1068 return true;
1069 case 730:
1070 /* monitor online */
1071 chatter_printf(conn->chatter, conn, NULL,
1072 "$B***$0 $B%s$0$/ %s online",
1073 msg.msg, strstr(msg.msg, ",") ? "are" : "is");
1074 if (conn->chatter->focusable && !conn->chatter->focusable->visible)
1075 notify();
1076 return true;
1077 case 731:
1078 /* monitor offline */
1079 chatter_printf(conn->chatter, conn, NULL,
1080 "$B***$0 $B%s$0$/ %s offline",
1081 msg.msg, strstr(msg.msg, ",") ? "are" : "is");
1082 return true;
1083 case 732:
1084 /* monitor list */
1085 chatter_printf(conn->chatter, conn, NULL,
1086 "$B***$0 Monitor list: %s",
1087 msg.msg);
1088 return true;
1089 case 733:
1090 /* end of monitor list */
1091 return true;
1092 case 734:
1093 /* monitor error */
1094 chatter_printf(conn->chatter, conn, NULL,
1095 "$B***$0 Monitor error: %s",
1096 msg.msg);
1097 return true;
1098 case 900:
1099 /* nickserv login */
1100 chatter_printf(conn->chatter, conn, NULL,
1101 "$B***$0$/ %s",
1102 msg.msg);
1103 return true;
1104 default:
1105 goto unknown;
1106 }
1107
1108 print_msg:
1109 chatter_printf(conn->chatter, conn, NULL,
1110 "$B***$0$/ %s",
1111 msg.msg);
1112 return true;
1113
1114 unknown:
1115 chatter_printf(conn->chatter, conn, NULL,
1116 "$B[?]$0$/ code:%d cmd:%s source:%s arg0:%s arg1:%s arg2:%s arg3:%s "
1117 "arg4:%s msg:%s",
1118 msg.code, msg.cmd, msg.source, msg.arg[0], msg.arg[1], msg.arg[2],
1119 msg.arg[3], msg.arg[4], msg.msg);
1120 return true;
1121 }
1122
1123 void
1124 irc_process_input(struct irc_connection *conn, struct irc_channel *channel,
1125 char *query_nick, char *str)
1126 {
1127 struct chatter_tab *tab;
1128 char *arg0, *arg1, *channel_name;
1129 size_t n;
1130 bool say = false;
1131
1132 /* check for commands that don't require a connection */
1133 if (strcasecmp(str, "/quit") == 0 ||
1134 strncasecmp(str, "/quit ", 6) == 0)
1135 {
1136 str++;
1137 arg0 = strsep(&str, " ");
1138
1139 if (conn && conn->state == IRC_STATE_CONNECTED) {
1140 irc_quit_with_message(conn, str);
1141 conn->state = IRC_STATE_DEAD;
1142 }
1143 if (focusables_quit())
1144 ExitToShell();
1145 return;
1146 }
1147 if (strcasecmp(str, "/connect") == 0 ||
1148 strncasecmp(str, "/connect ", 9) == 0 ||
1149 strcasecmp(str, "/recon") == 0 ||
1150 strcasecmp(str, "/reconnect") == 0 ||
1151 strcasecmp(str, "/server") == 0 ||
1152 strncasecmp(str, "/server ", 8) == 0) {
1153 struct irc_connection *new;
1154 struct chatter_tab *tab;
1155 char *autojoin = NULL;
1156 size_t len;
1157
1158 str++;
1159 arg0 = strsep(&str, " ");
1160 if (strcasecmp(arg0, "server") == 0 && str == NULL)
1161 goto not_enough_params;
1162
1163 /* build a new list of autojoin channels based on what's joined */
1164 len = 0;
1165 SLIST_FOREACH(tab, &conn->chatter->tabs_list, list) {
1166 if (tab->conn == conn && tab->channel)
1167 len += strlen(tab->channel->name) + 1;
1168 }
1169 if (len) {
1170 autojoin = xmalloc(len);
1171 if (autojoin == NULL) {
1172 chatter_printf(conn->chatter, conn, NULL,
1173 "$B*!*$0 Can't allocate memory for autojoin");
1174 return;
1175 }
1176 autojoin[0] = '\0';
1177 SLIST_FOREACH(tab, &conn->chatter->tabs_list, list) {
1178 if (tab->conn == conn && tab->channel) {
1179 if (autojoin[0])
1180 strlcat(autojoin, ",", len);
1181 strlcat(autojoin, tab->channel->name, len);
1182 }
1183 }
1184 }
1185
1186 new = irc_connect(conn->chatter, str ? str : conn->hostname,
1187 conn->port, conn->password, conn->nick, conn->ident,
1188 conn->realname, conn->hide_motd, autojoin);
1189
1190 if (new->state == IRC_STATE_UNREGISTERED) {
1191 /* the new connection succeeded, kill the old one */
1192 if (conn->state == IRC_STATE_CONNECTED)
1193 irc_close_connection(conn);
1194
1195 SLIST_FOREACH(tab, &conn->chatter->tabs_list, list) {
1196 if (tab->conn == conn)
1197 tab->conn = new;
1198 }
1199
1200 irc_dealloc_connection(conn);
1201
1202 conn->chatter->need_tab_bar_redraw = true;
1203 } else {
1204 /* just keep the old one */
1205 irc_dealloc_connection(new);
1206 }
1207
1208 return;
1209 }
1210
1211 /* everything else requires an active connection */
1212
1213 if (conn == NULL || conn->state < IRC_STATE_CONNECTED)
1214 goto not_connected;
1215
1216 if (strncasecmp(str, "/say ", 5) == 0) {
1217 say = true;
1218 strsep(&str, " ");
1219 }
1220
1221 if (say || str[0] != '/') {
1222 if (channel != NULL) {
1223 irc_printf(conn, "PRIVMSG %s :%s\r\n", channel->name, str);
1224 chatter_printf(conn->chatter, conn, channel->name,
1225 "<$B%s$0>$/ %s",
1226 conn->nick, str);
1227 } else if (query_nick != NULL) {
1228 irc_printf(conn, "PRIVMSG %s :%s\r\n", query_nick, str);
1229 chatter_printf(conn->chatter, conn, query_nick,
1230 "<$B%s$0>$/ %s",
1231 conn->nick, str);
1232 } else
1233 goto not_in_channel;
1234 return;
1235 }
1236
1237 /* skip / */
1238 str++;
1239
1240 arg0 = strsep(&str, " ");
1241
1242 /* special cases: send as-is */
1243 if (strcasecmp(arg0, "quote") == 0 || strcasecmp(arg0, "raw") == 0) {
1244 if (str == NULL)
1245 goto not_enough_params;
1246 irc_printf(conn, "%s\r\n", str);
1247 return;
1248 }
1249
1250 if (str && (str[0] == '#' || str[0] == '&'))
1251 /* channel specified */
1252 channel_name = strsep(&str, " ");
1253 else if (channel)
1254 /* use channel of current tab */
1255 channel_name = channel->name;
1256 else
1257 channel_name = NULL;
1258
1259 if (strcasecmp(arg0, "clear") == 0) {
1260 chatter_clear_messages(conn->chatter, conn->chatter->current_tab);
1261 return;
1262 }
1263 if (strcasecmp(arg0, "close") == 0) {
1264 if (conn->chatter->current_tab->query_nick[0] ||
1265 conn->chatter->current_tab->channel)
1266 chatter_close_tab(conn->chatter, conn->chatter->current_tab);
1267 else
1268 chatter_printf(conn->chatter, conn, NULL,
1269 "cannot close server tab, use /disconnect",
1270 arg0);
1271 return;
1272 }
1273 if (strcasecmp(arg0, "deop") == 0) {
1274 if (channel_name == NULL)
1275 goto not_in_channel;
1276 irc_do_mode_to_users(conn, channel_name, "-o", str);
1277 return;
1278 }
1279 if (strcasecmp(arg0, "devoice") == 0) {
1280 if (channel_name == NULL)
1281 goto not_in_channel;
1282 irc_do_mode_to_users(conn, channel_name, "-v", str);
1283 return;
1284 }
1285 if (strcasecmp(arg0, "disco") == 0 ||
1286 strcasecmp(arg0, "discon") == 0 ||
1287 strcasecmp(arg0, "disconnect") == 0) {
1288 irc_quit_with_message(conn, str);
1289 return;
1290 }
1291 if (strcasecmp(arg0, "join") == 0) {
1292 if (channel_name == NULL)
1293 goto not_enough_params;
1294 /*
1295 * If we're already in this channel, we won't get any response
1296 * from the server to switch channels.
1297 */
1298 if ((channel = irc_find_channel(conn, channel_name)))
1299 /* this won't actually create, it'll just switch for us */
1300 irc_create_channel(conn, channel_name);
1301 else
1302 irc_printf(conn, "JOIN %s%s%s\r\n", channel_name,
1303 (str ? " " : ""), (str ? str : ""));
1304 return;
1305 }
1306 if (strcasecmp(arg0, "me") == 0) {
1307 if (str == NULL)
1308 goto not_enough_params;
1309 if (query_nick == NULL && channel == NULL)
1310 goto not_in_channel;
1311 chatter_printf(conn->chatter, conn,
1312 channel ? channel->name : query_nick,
1313 "* %s$/ %s",
1314 conn->nick, str);
1315 irc_printf(conn, "PRIVMSG %s :\1ACTION %s\1\r\n",
1316 channel ? channel->name : query_nick, str);
1317 return;
1318 }
1319 if (strcasecmp(arg0, "mode") == 0) {
1320 if (channel_name == NULL)
1321 goto not_in_channel;
1322 irc_printf(conn, "MODE %s%s%s\r\n", channel_name, (str ? " " : ""),
1323 (str ? str : ""));
1324 return;
1325 }
1326 if (strcasecmp(arg0, "monitor") == 0) {
1327 if (str == NULL)
1328 goto not_enough_params;
1329 irc_printf(conn, "MONITOR %s\r\n", str);
1330 return;
1331 }
1332 if (strcasecmp(arg0, "msg") == 0) {
1333 arg1 = strsep(&str, " ");
1334 if (arg1 == NULL || str == NULL)
1335 goto not_enough_params;
1336 if ((tab = chatter_find_tab(conn->chatter, conn, NULL)))
1337 tab->ignore_activity = true;
1338 chatter_printf(conn->chatter, conn, NULL,
1339 "$B[$0msg$B($0%s$B)]$0$/ %s",
1340 arg1, str);
1341 if (tab)
1342 tab->ignore_activity = false;
1343 irc_printf(conn, "PRIVMSG %s :%s\r\n", arg1, str);
1344 return;
1345 }
1346 if (strcasecmp(arg0, "nick") == 0) {
1347 if (str == NULL)
1348 goto not_enough_params;
1349 irc_printf(conn, "NICK %s\r\n", str);
1350 return;
1351 }
1352 if (strcasecmp(arg0, "op") == 0) {
1353 if (channel_name == NULL)
1354 goto not_in_channel;
1355 irc_do_mode_to_users(conn, channel_name, "+o", str);
1356 return;
1357 }
1358 if (strcasecmp(arg0, "part") == 0) {
1359 if (channel_name == NULL)
1360 goto not_in_channel;
1361 irc_printf(conn, "PART %s%s%s\r\n", channel_name,
1362 (str ? " :" : ""), (str ? str : ""));
1363 return;
1364 }
1365 if (strcasecmp(arg0, "query") == 0) {
1366 if (str == NULL)
1367 goto not_enough_params;
1368 chatter_add_tab(conn->chatter, NULL, conn, NULL, str);
1369 return;
1370 }
1371 if (strcasecmp(arg0, "topic") == 0) {
1372 if (channel_name == NULL)
1373 goto not_in_channel;
1374 if (str)
1375 irc_printf(conn, "TOPIC %s :%s\r\n", channel_name, str);
1376 else
1377 irc_printf(conn, "TOPIC %s\r\n", channel_name);
1378 return;
1379 }
1380 if (strcasecmp(arg0, "umode") == 0) {
1381 irc_printf(conn, "MODE %s%s%s\r\n", conn->nick, (str ? " :" : ""),
1382 (str ? str : ""));
1383 return;
1384 }
1385 if (strcasecmp(arg0, "voice") == 0) {
1386 if (channel_name == NULL)
1387 goto not_in_channel;
1388 irc_do_mode_to_users(conn, channel_name, "+v", str);
1389 return;
1390 }
1391 if (strcasecmp(arg0, "who") == 0) {
1392 if (channel_name == NULL)
1393 goto not_in_channel;
1394 irc_printf(conn, "WHO %s\r\n", channel_name);
1395 return;
1396 }
1397 if (strcasecmp(arg0, "whois") == 0) {
1398 if (str == NULL)
1399 goto not_enough_params;
1400 irc_printf(conn, "WHOIS %s\r\n", str);
1401 return;
1402 }
1403
1404 chatter_printf(conn->chatter, conn, NULL,
1405 "unrecognized command \"$B%s$0\"",
1406 arg0);
1407 return;
1408
1409 not_enough_params:
1410 chatter_printf(conn->chatter, conn, channel ? channel->name : NULL,
1411 "$B*!*$0 Not enough parameters given");
1412 return;
1413 not_connected:
1414 chatter_printf(conn->chatter, conn, channel ? channel->name : NULL,
1415 "$B*!*$0 Not connected");
1416 return;
1417 not_in_channel:
1418 chatter_printf(conn->chatter, conn, NULL,
1419 "$B*!*$0 Cannot send (not in a channel)");
1420 return;
1421 }
1422
1423 struct irc_channel *
1424 irc_create_channel(struct irc_connection *conn, char *channame)
1425 {
1426 struct irc_channel *channel, *tchannel;
1427 short n;
1428
1429 SLIST_FOREACH(channel, &conn->channels_list, list) {
1430 if (strcasecmp(channel->name, channame) == 0)
1431 return channel;
1432 }
1433
1434 channel = xmalloczero(sizeof(struct irc_channel));
1435 if (channel == NULL)
1436 panic("Out of memory for new channel");
1437 SLIST_APPEND(&conn->channels_list, channel, irc_channel, list);
1438 channel->connection = conn;
1439 strlcpy(channel->name, channame, sizeof(channel->name));
1440 channel->chatter = conn->chatter;
1441 chatter_add_tab(channel->chatter, NULL, conn, channel, NULL);
1442
1443 irc_printf(conn, "MODE %s\r\n", channel->name);
1444
1445 return channel;
1446 }
1447
1448 void
1449 irc_quit_with_message(struct irc_connection *conn, char *str)
1450 {
1451 if (str)
1452 irc_printf(conn, "QUIT :%s\r\n", str);
1453 else
1454 irc_printf(conn, "QUIT :%s %s on a %s\r\n",
1455 PROGRAM_NAME, get_version(false), gestalt_machine_type());
1456 }
1457
1458 void
1459 irc_part_channel(struct irc_connection *conn, struct irc_channel *channel)
1460 {
1461 if (conn->state >= IRC_STATE_CONNECTED)
1462 irc_printf(conn, "PART %s\r\n", channel->name);
1463 }
1464
1465 void
1466 irc_dealloc_channel(struct irc_channel *channel)
1467 {
1468 struct irc_connection *conn = channel->connection;
1469 struct chatter *chatter = channel->chatter;
1470 struct chatter_tab *tab;
1471
1472 if ((tab = chatter_find_tab(chatter, conn, channel->name))) {
1473 /* clear channel first so close_tab doesn't try to part */
1474 tab->channel = NULL;
1475 chatter_close_tab(chatter, tab);
1476 }
1477
1478 if (channel->nicks)
1479 xfree(&channel->nicks);
1480
1481 SLIST_REMOVE(&conn->channels_list, channel, irc_channel, list);
1482
1483 xfree(&channel);
1484 }
1485
1486 void
1487 irc_parse_names(struct irc_channel *channel, char *line)
1488 {
1489 char *nick, *tline;
1490 short flags, count;
1491
1492 if (!channel->parsing_nicks) {
1493 /* new names output, clear previous */
1494 if (channel->nicks_size) {
1495 memset(&channel->nicks, 0,
1496 sizeof(struct irc_channel_nick) * channel->nicks_size);
1497 }
1498
1499 channel->nnicks = 0;
1500 channel->parsing_nicks = true;
1501 }
1502
1503 /* get a count of nicks to pass as a hint to malloc */
1504 count = 1;
1505 for (tline = line; *tline != '\0'; tline++)
1506 if (*tline == ' ')
1507 count++;
1508
1509 for (;;) {
1510 nick = strsep(&line, " ");
1511
1512 if (nick[0] == '@') {
1513 flags = IRC_NICK_FLAG_OP;
1514 nick++;
1515 } else if (nick[0] == '+') {
1516 flags = IRC_NICK_FLAG_VOICE;
1517 nick++;
1518 } else if (nick[0] == '\0') {
1519 /* some servers send a trailing space */
1520 break;
1521 } else
1522 flags = 0;
1523
1524 irc_add_nick_to_channel(channel, nick, flags, count);
1525
1526 if (line == NULL)
1527 break;
1528 }
1529 }
1530
1531 void
1532 irc_add_nick_to_channel(struct irc_channel *channel, char *nick,
1533 short flags, short count_hint)
1534 {
1535 struct irc_channel_nick *anick, *cnick, *pnick;
1536 short aidx, cidx, ret;
1537
1538 if (channel->nnicks >= channel->nicks_size) {
1539 /* allocate a chunk at a time so we don't do this every iteration */
1540 if (count_hint)
1541 channel->nicks_size += count_hint;
1542 else
1543 channel->nicks_size += 5;
1544 channel->nicks = xreallocarray(channel->nicks,
1545 sizeof(struct irc_channel_nick),
1546 channel->nicks_size);
1547 if (channel->nicks == NULL)
1548 panic("Out of memory for %d nicks (%ld bytes)",
1549 channel->nnicks,
1550 sizeof(struct irc_channel_nick) * channel->nicks_size);
1551 memset(&channel->nicks[channel->nnicks], 0,
1552 sizeof(struct irc_channel_nick) *
1553 (channel->nicks_size - channel->nnicks));
1554 aidx = channel->nnicks;
1555 } else {
1556 if (channel->nicks[channel->nnicks].nick[0] == '\0')
1557 aidx = channel->nnicks;
1558 else {
1559 /* find an open slot */
1560 for (aidx = 0; aidx < channel->nicks_size; aidx++) {
1561 if (channel->nicks[aidx].nick[0] == '\0')
1562 break;
1563 }
1564 }
1565 }
1566
1567 if (aidx >= channel->nicks_size)
1568 panic("irc_add_nick_to_channel overflow");
1569
1570 channel->nnicks++;
1571 anick = &channel->nicks[aidx];
1572 strlcpy(anick->nick, nick, sizeof(anick->nick));
1573 anick->flags = flags;
1574
1575 if (channel->nnicks == 1) {
1576 anick->next_nick = -1;
1577 channel->first_nick = 0;
1578 cidx = 0;
1579 } else {
1580 /* sort it in the right place by flags descending, then by nick */
1581 cnick = &channel->nicks[channel->first_nick];
1582 pnick = NULL;
1583 cidx = 0;
1584 while (cnick) {
1585 if (cnick->nick[0] == '\0')
1586 ret = 1;
1587 if (cnick->flags == anick->flags)
1588 ret = strcasecmp(anick->nick, cnick->nick);
1589 else if (anick->flags > cnick->flags)
1590 ret = -1;
1591 else
1592 ret = 1;
1593
1594 if (ret <= 0) {
1595 /* new nick goes before this one */
1596 if (pnick) {
1597 anick->next_nick = pnick->next_nick;
1598 pnick->next_nick = aidx;
1599 } else {
1600 anick->next_nick = channel->first_nick;
1601 channel->first_nick = aidx;
1602 }
1603 break;
1604 }
1605
1606 cidx++;
1607
1608 if (cnick->next_nick == -1) {
1609 /* end of the line */
1610 cnick->next_nick = aidx;
1611 anick->next_nick = -1;
1612 break;
1613 }
1614
1615 pnick = cnick;
1616 cnick = &channel->nicks[cnick->next_nick];
1617 }
1618 }
1619
1620 if (!channel->parsing_nicks)
1621 chatter_insert_to_nick_list(channel->chatter, channel, anick,
1622 cidx);
1623 }
1624
1625 void
1626 irc_remove_nick_from_channel(struct irc_channel *channel, char *nick)
1627 {
1628 struct irc_channel_nick *cnick, *pnick;
1629 short cidx;
1630
1631 if (channel->first_nick == -1)
1632 return;
1633
1634 pnick = NULL;
1635 cnick = &channel->nicks[channel->first_nick];
1636 cidx = 0;
1637 while (cnick) {
1638 if (strcmp(cnick->nick, nick) != 0) {
1639 pnick = cnick;
1640 cidx++;
1641 if (cnick->next_nick == -1)
1642 return;
1643 cnick = &channel->nicks[cnick->next_nick];
1644 continue;
1645 }
1646
1647 /* connect the nick that pointed to this one and the one we point to */
1648 if (pnick)
1649 pnick->next_nick = cnick->next_nick;
1650 else
1651 channel->first_nick = cnick->next_nick;
1652
1653 channel->nnicks--;
1654 cnick->nick[0] = '\0';
1655 chatter_remove_from_nick_list(channel->chatter, channel,
1656 cnick, cidx);
1657 return;
1658 }
1659 }
1660
1661 bool
1662 irc_nick_is_in_channel(struct irc_channel *channel, char *nick)
1663 {
1664 struct irc_channel_nick *cnick, *pnick;
1665
1666 if (channel->first_nick == -1)
1667 return;
1668
1669 pnick = NULL;
1670 cnick = &channel->nicks[channel->first_nick];
1671 while (cnick) {
1672 if (strcmp(cnick->nick, nick) == 0)
1673 return true;
1674
1675 pnick = cnick;
1676 if (cnick->next_nick == -1)
1677 break;
1678 cnick = &channel->nicks[cnick->next_nick];
1679 }
1680
1681 return false;
1682 }
1683
1684 void
1685 irc_change_user_nick(struct irc_connection *conn,
1686 struct irc_channel *channel, struct irc_user *user, char *nick)
1687 {
1688 struct irc_channel_nick *cnick;
1689 short n, flags;
1690
1691 if (channel && channel->first_nick > -1) {
1692 /* preserve flags */
1693 cnick = &channel->nicks[channel->first_nick];
1694 while (cnick) {
1695 if (strcmp(cnick->nick, user->nick) == 0) {
1696 flags = cnick->flags;
1697 break;
1698 }
1699 if (cnick->next_nick == -1)
1700 break;
1701 cnick = &channel->nicks[cnick->next_nick];
1702 }
1703
1704 irc_remove_nick_from_channel(channel, user->nick);
1705 irc_add_nick_to_channel(channel, nick, flags, 0);
1706 }
1707
1708 if (strcasecmp(conn->nick, user->nick) == 0) {
1709 xfree(&conn->nick);
1710 conn->nick_len = strlen(nick);
1711 conn->nick = xstrdup(nick);
1712 if (conn->nick == NULL)
1713 panic("Out of memory changing nick");
1714 chatter_update_titlebar(conn->chatter);
1715 }
1716 }
1717
1718 bool
1719 irc_parse_channel_mode_change(struct irc_channel *channel, char *mode,
1720 char *args)
1721 {
1722 size_t len;
1723 struct irc_channel_nick *cnick;
1724 char *user;
1725 short n, j, flags;
1726 bool add = false;
1727 bool others = false;
1728
1729 len = strlen(mode);
1730
1731 /* mode:"+mvo-vo" args:"nick1 nick2 nick3 nick4" */
1732 for (n = 0; n < len; n++) {
1733 switch (mode[n]) {
1734 case '+':
1735 add = true;
1736 break;
1737 case '-':
1738 add = false;
1739 break;
1740 case 'v':
1741 case 'o':
1742 user = strsep(&args, " ");
1743 if (user == NULL)
1744 user = args;
1745
1746 cnick = &channel->nicks[channel->first_nick];
1747 while (cnick) {
1748 if (strcmp(cnick->nick, user) != 0) {
1749 if (cnick->next_nick == -1)
1750 break;
1751 cnick = &channel->nicks[cnick->next_nick];
1752 continue;
1753 }
1754
1755 flags = cnick->flags;
1756 if (mode[n] == 'o') {
1757 if (add)
1758 flags |= IRC_NICK_FLAG_OP;
1759 else
1760 flags &= ~IRC_NICK_FLAG_OP;
1761 } else if (mode[n] == 'v') {
1762 if (add)
1763 flags |= IRC_NICK_FLAG_VOICE;
1764 else
1765 flags &= ~IRC_NICK_FLAG_VOICE;
1766 }
1767
1768 irc_remove_nick_from_channel(channel, cnick->nick);
1769 /* cnick is probably invalid now */
1770 irc_add_nick_to_channel(channel, user, flags, 0);
1771 break;
1772 }
1773
1774 break;
1775 default:
1776 /* some other channel mode */
1777 others = true;
1778 break;
1779 }
1780 }
1781
1782 return others;
1783 }
1784
1785 void
1786 irc_do_mode_to_users(struct irc_connection *conn, char *channel_name,
1787 char *mode, char *nicks)
1788 {
1789 #define MAX_MODES_AT_ONCE 4
1790 /* assume we can only do 4 at a time */
1791 char modes[(2 * MAX_MODES_AT_ONCE) + 1];
1792 char mnicks[((member_size(struct irc_user, nick) + 1) *
1793 MAX_MODES_AT_ONCE) + 1];
1794 char *nick;
1795 short count;
1796
1797 /* TODO: support +b by looking up user in nick table for hostmask */
1798
1799 modes[0] = '\0';
1800 mnicks[0] = '\0';
1801 count = 0;
1802
1803 while (nicks != NULL) {
1804 strlcat(modes, mode, sizeof(modes));
1805
1806 nick = strsep(&nicks, " ");
1807 strlcat(mnicks, nick, sizeof(mnicks));
1808 count++;
1809
1810 if (nicks != NULL && count != MAX_MODES_AT_ONCE)
1811 strlcat(mnicks, " ", sizeof(mnicks));
1812
1813 if (nicks == NULL || count == 4) {
1814 irc_printf(conn, "MODE %s %s %s\r\n", channel_name, modes,
1815 mnicks);
1816 mnicks[0] = '\0';
1817 modes[0] = '\0';
1818 count = 0;
1819 }
1820 }
1821 }