AmendHub

Download

jcs

/

wallops

/

irc.c

 

(View History)

jcs   irc: Minor nits Latest amendment: 143 on 2025-12-17

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_buffer_names(struct irc_channel *channel, char *line);
41 void irc_parse_buffered_names(struct irc_channel *channel);
42 void irc_add_nick_to_channel(struct irc_channel *channel, char *nick,
43 short flags, short count_hint);
44 void irc_remove_nick_from_channel(struct irc_channel *channel, char *nick);
45 bool irc_nick_is_in_channel(struct irc_channel *channel, char *nick);
46 void irc_change_user_nick(struct irc_connection *conn,
47 struct irc_channel *channel, struct irc_user *user, char *nick);
48 bool irc_parse_channel_mode_change(struct irc_channel *channel, char *mode,
49 char *args);
50 void irc_do_mode_to_users(struct irc_connection *conn, char *channel_name,
51 char *mode, char *nicks);
52
53 #define IRC_CAN_SEND(conn) ((conn)->send_pb.ioResult <= 0)
54
55 struct irc_connections_head irc_connections_list =
56 SLIST_HEAD_INITIALIZER(irc_connections_list);
57
58 struct irc_connection *
59 irc_connect(struct chatter *chatter, const char *server,
60 const unsigned short port, const char *password, const char *nick,
61 const char *ident, const char *realname, bool hide_motd,
62 const char *channel)
63 {
64 struct irc_connection *conn;
65 char ip_str[] = "255.255.255.255";
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 notify(conn->chatter->focusable, msg.msg,
567 "Private message from %s", user->nick);
568 } else {
569 /* message to channel we're in */
570 char *us;
571
572 us = strcasestr(msg.msg, conn->nick);
573 if (us != NULL && (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 notify(conn->chatter->focusable, msg.msg,
592 "Mention in %s by %s", msg.arg[0], user->nick);
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_buffer_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 SetCursor(*(GetCursor(watchCursor)));
988 irc_parse_buffered_names(channel);
989 chatter_sync_nick_list(channel->chatter, channel);
990 SetCursor(&arrow);
991 }
992 return true;
993 case 372:
994 case 375:
995 /* MOTD */
996 if (conn->hide_motd)
997 return true;
998 goto print_msg;
999 case 376:
1000 /* end of MOTD */
1001 if (conn->channel_autojoin && conn->channel_autojoin[0] &&
1002 !conn->did_autojoin) {
1003 irc_printf(conn, "JOIN %s\r\n", conn->channel_autojoin);
1004 conn->did_autojoin = true;
1005 }
1006 if (conn->hide_motd)
1007 return true;
1008 goto print_msg;
1009 case 396:
1010 /* Cloak */
1011 chatter_printf(conn->chatter, conn, NULL,
1012 "$B***$0$/ %s %s",
1013 msg.arg[1], msg.msg);
1014 return true;
1015 case 433: {
1016 /* Nick in use, try appending a _ */
1017 char *new_nick = xmalloc(strlen(conn->nick) + 2);
1018 size_t len;
1019
1020 chatter_printf(conn->chatter, conn, NULL,
1021 "$B***$0$/ %s: %s",
1022 msg.arg[1], msg.msg);
1023
1024 sprintf(new_nick, "%s_", conn->nick);
1025 xfree(&conn->nick);
1026 conn->nick = new_nick;
1027 chatter_update_titlebar(conn->chatter);
1028
1029 len = snprintf(conn->line, sizeof(conn->line),
1030 "NICK %s\r\n", conn->nick);
1031 irc_send(conn, conn->line, len);
1032 return true;
1033 }
1034 case 461:
1035 /* not enough args */
1036 chatter_printf(conn->chatter, conn, NULL,
1037 "$B***$0 $B%s$0$/: %s",
1038 msg.arg[1], msg.msg);
1039 return true;
1040 case 475:
1041 /* can't join channel */
1042 chatter_printf(conn->chatter, conn, NULL,
1043 "$B***$0 Can't join $B%s$0$/: %s",
1044 msg.arg[1], msg.msg);
1045 return true;
1046 case 481:
1047 /* not oper */
1048 chatter_printf(conn->chatter, conn, NULL,
1049 "$B***$0 You can't do that thing if you ain't got that "
1050 "swing:$/: %s",
1051 msg.msg);
1052 return true;
1053 case 482:
1054 /* not op */
1055 chatter_printf(conn->chatter, conn, NULL,
1056 "$B***$0 Can't do that on $B%s$0$/: %s",
1057 msg.arg[1], msg.msg);
1058 return true;
1059 case 502:
1060 /* error */
1061 chatter_printf(conn->chatter, conn, NULL,
1062 "$B***$0$/ %s",
1063 msg.msg);
1064 return true;
1065 case 671:
1066 /* WHOIS server */
1067 chatter_printf(conn->chatter, conn, conn->current_whois,
1068 "$B*** |$0 Connection:$/ %s",
1069 msg.msg);
1070 return true;
1071 case 730:
1072 /* monitor online */
1073 chatter_printf(conn->chatter, conn, NULL,
1074 "$B***$0 $B%s$0$/ %s online",
1075 msg.msg, strstr(msg.msg, ",") ? "are" : "is");
1076 if (conn->chatter->focusable)
1077 notify(conn->chatter->focusable, NULL, NULL);
1078 return true;
1079 case 731:
1080 /* monitor offline */
1081 chatter_printf(conn->chatter, conn, NULL,
1082 "$B***$0 $B%s$0$/ %s offline",
1083 msg.msg, strstr(msg.msg, ",") ? "are" : "is");
1084 return true;
1085 case 732:
1086 /* monitor list */
1087 chatter_printf(conn->chatter, conn, NULL,
1088 "$B***$0 Monitor list: %s",
1089 msg.msg);
1090 return true;
1091 case 733:
1092 /* end of monitor list */
1093 return true;
1094 case 734:
1095 /* monitor error */
1096 chatter_printf(conn->chatter, conn, NULL,
1097 "$B***$0 Monitor error: %s",
1098 msg.msg);
1099 return true;
1100 case 900:
1101 /* nickserv login */
1102 chatter_printf(conn->chatter, conn, NULL,
1103 "$B***$0$/ %s",
1104 msg.msg);
1105 return true;
1106 default:
1107 goto unknown;
1108 }
1109
1110 print_msg:
1111 chatter_printf(conn->chatter, conn, NULL,
1112 "$B***$0$/ %s",
1113 msg.msg);
1114 return true;
1115
1116 unknown:
1117 chatter_printf(conn->chatter, conn, NULL,
1118 "$B[?]$0$/ code:%d cmd:%s source:%s arg0:%s arg1:%s arg2:%s arg3:%s "
1119 "arg4:%s msg:%s",
1120 msg.code, msg.cmd, msg.source, msg.arg[0], msg.arg[1], msg.arg[2],
1121 msg.arg[3], msg.arg[4], msg.msg);
1122 return true;
1123 }
1124
1125 void
1126 irc_process_input(struct irc_connection *conn, struct irc_channel *channel,
1127 char *query_nick, char *str)
1128 {
1129 struct chatter_tab *tab;
1130 char *arg0, *arg1, *channel_name;
1131 size_t n;
1132 bool say = false;
1133
1134 /* check for commands that don't require a connection */
1135 if (strcasecmp(str, "/quit") == 0 ||
1136 strncasecmp(str, "/quit ", 6) == 0)
1137 {
1138 str++;
1139 arg0 = strsep(&str, " ");
1140
1141 if (conn && conn->state == IRC_STATE_CONNECTED) {
1142 irc_quit_with_message(conn, str);
1143 conn->state = IRC_STATE_DEAD;
1144 }
1145 if (focusables_quit())
1146 ExitToShell();
1147 return;
1148 }
1149 if (strcasecmp(str, "/connect") == 0 ||
1150 strncasecmp(str, "/connect ", 9) == 0 ||
1151 strcasecmp(str, "/recon") == 0 ||
1152 strcasecmp(str, "/reconnect") == 0 ||
1153 strcasecmp(str, "/server") == 0 ||
1154 strncasecmp(str, "/server ", 8) == 0) {
1155 struct irc_connection *new;
1156 struct chatter_tab *tab;
1157 char *autojoin = NULL;
1158 size_t len;
1159
1160 str++;
1161 arg0 = strsep(&str, " ");
1162 if (strcasecmp(arg0, "server") == 0 && str == NULL)
1163 goto not_enough_params;
1164
1165 /* build a new list of autojoin channels based on what's joined */
1166 len = 0;
1167 SLIST_FOREACH(tab, &conn->chatter->tabs_list, list) {
1168 if (tab->conn == conn && tab->channel)
1169 len += strlen(tab->channel->name) + 1;
1170 }
1171 if (len) {
1172 autojoin = xmalloc(len);
1173 if (autojoin == NULL) {
1174 chatter_printf(conn->chatter, conn, NULL,
1175 "$B*!*$0 Can't allocate memory for autojoin");
1176 return;
1177 }
1178 autojoin[0] = '\0';
1179 SLIST_FOREACH(tab, &conn->chatter->tabs_list, list) {
1180 if (tab->conn == conn && tab->channel) {
1181 if (autojoin[0])
1182 strlcat(autojoin, ",", len);
1183 strlcat(autojoin, tab->channel->name, len);
1184 }
1185 }
1186 }
1187
1188 new = irc_connect(conn->chatter, str ? str : conn->hostname,
1189 conn->port, conn->password, conn->nick, conn->ident,
1190 conn->realname, conn->hide_motd, autojoin);
1191
1192 if (new->state == IRC_STATE_UNREGISTERED) {
1193 /* the new connection succeeded, kill the old one */
1194 if (conn->state == IRC_STATE_CONNECTED)
1195 irc_close_connection(conn);
1196
1197 SLIST_FOREACH(tab, &conn->chatter->tabs_list, list) {
1198 if (tab->conn == conn)
1199 tab->conn = new;
1200 }
1201
1202 /* move channels to new connection */
1203 SLIST_FOREACH(channel, &conn->channels_list, list)
1204 channel->connection = new;
1205
1206 /* otherwise irc_dealloc_connection will take them out */
1207 SLIST_INIT(&conn->channels_list);
1208
1209 irc_dealloc_connection(conn);
1210
1211 new->chatter->need_tab_bar_redraw = true;
1212 } else {
1213 /* just keep the old one */
1214 irc_dealloc_connection(new);
1215 }
1216
1217 return;
1218 }
1219
1220 /* everything else requires an active connection */
1221
1222 if (conn == NULL || conn->state < IRC_STATE_CONNECTED)
1223 goto not_connected;
1224
1225 if (strncasecmp(str, "/say ", 5) == 0) {
1226 say = true;
1227 strsep(&str, " ");
1228 }
1229
1230 if (say || str[0] != '/') {
1231 if (channel != NULL) {
1232 irc_printf(conn, "PRIVMSG %s :%s\r\n", channel->name, str);
1233 chatter_printf(conn->chatter, conn, channel->name,
1234 "<$B%s$0>$/ %s",
1235 conn->nick, str);
1236 } else if (query_nick != NULL) {
1237 irc_printf(conn, "PRIVMSG %s :%s\r\n", query_nick, str);
1238 chatter_printf(conn->chatter, conn, query_nick,
1239 "<$B%s$0>$/ %s",
1240 conn->nick, str);
1241 } else
1242 goto not_in_channel;
1243 return;
1244 }
1245
1246 /* skip / */
1247 str++;
1248
1249 arg0 = strsep(&str, " ");
1250
1251 /* special cases: send as-is */
1252 if (strcasecmp(arg0, "quote") == 0 || strcasecmp(arg0, "raw") == 0) {
1253 if (str == NULL)
1254 goto not_enough_params;
1255 irc_printf(conn, "%s\r\n", str);
1256 return;
1257 }
1258
1259 if (str && (str[0] == '#' || str[0] == '&'))
1260 /* channel specified */
1261 channel_name = strsep(&str, " ");
1262 else if (channel)
1263 /* use channel of current tab */
1264 channel_name = channel->name;
1265 else
1266 channel_name = NULL;
1267
1268 if (strcasecmp(arg0, "clear") == 0) {
1269 chatter_clear_messages(conn->chatter, conn->chatter->current_tab);
1270 return;
1271 }
1272 if (strcasecmp(arg0, "close") == 0) {
1273 if (conn->chatter->current_tab->query_nick[0] ||
1274 conn->chatter->current_tab->channel)
1275 chatter_close_tab(conn->chatter, conn->chatter->current_tab);
1276 else
1277 chatter_printf(conn->chatter, conn, NULL,
1278 "cannot close server tab, use /disconnect",
1279 arg0);
1280 return;
1281 }
1282 if (strcasecmp(arg0, "deop") == 0) {
1283 if (channel_name == NULL)
1284 goto not_in_channel;
1285 irc_do_mode_to_users(conn, channel_name, "-o", str);
1286 return;
1287 }
1288 if (strcasecmp(arg0, "devoice") == 0) {
1289 if (channel_name == NULL)
1290 goto not_in_channel;
1291 irc_do_mode_to_users(conn, channel_name, "-v", str);
1292 return;
1293 }
1294 if (strcasecmp(arg0, "disco") == 0 ||
1295 strcasecmp(arg0, "discon") == 0 ||
1296 strcasecmp(arg0, "disconnect") == 0) {
1297 irc_quit_with_message(conn, str);
1298 return;
1299 }
1300 if (strcasecmp(arg0, "join") == 0) {
1301 if (channel_name == NULL)
1302 goto not_enough_params;
1303 /*
1304 * If we're already in this channel, we won't get any response
1305 * from the server to switch channels.
1306 */
1307 if ((channel = irc_find_channel(conn, channel_name)))
1308 /* this won't actually create, it'll just switch for us */
1309 irc_create_channel(conn, channel_name);
1310 else
1311 irc_printf(conn, "JOIN %s%s%s\r\n", channel_name,
1312 (str ? " " : ""), (str ? str : ""));
1313 return;
1314 }
1315 if (strcasecmp(arg0, "me") == 0) {
1316 if (str == NULL)
1317 goto not_enough_params;
1318 if (query_nick == NULL && channel == NULL)
1319 goto not_in_channel;
1320 chatter_printf(conn->chatter, conn,
1321 channel ? channel->name : query_nick,
1322 "* %s$/ %s",
1323 conn->nick, str);
1324 irc_printf(conn, "PRIVMSG %s :\1ACTION %s\1\r\n",
1325 channel ? channel->name : query_nick, str);
1326 return;
1327 }
1328 if (strcasecmp(arg0, "mode") == 0) {
1329 if (channel_name == NULL)
1330 goto not_in_channel;
1331 irc_printf(conn, "MODE %s%s%s\r\n", channel_name, (str ? " " : ""),
1332 (str ? str : ""));
1333 return;
1334 }
1335 if (strcasecmp(arg0, "monitor") == 0) {
1336 if (str == NULL)
1337 goto not_enough_params;
1338 irc_printf(conn, "MONITOR %s\r\n", str);
1339 return;
1340 }
1341 if (strcasecmp(arg0, "msg") == 0) {
1342 arg1 = strsep(&str, " ");
1343 if (arg1 == NULL || str == NULL)
1344 goto not_enough_params;
1345 if ((tab = chatter_find_tab(conn->chatter, conn, NULL)))
1346 tab->ignore_activity = true;
1347 chatter_printf(conn->chatter, conn, NULL,
1348 "$B[$0msg$B($0%s$B)]$0$/ %s",
1349 arg1, str);
1350 if (tab)
1351 tab->ignore_activity = false;
1352 irc_printf(conn, "PRIVMSG %s :%s\r\n", arg1, str);
1353 return;
1354 }
1355 if (strcasecmp(arg0, "nick") == 0) {
1356 if (str == NULL)
1357 goto not_enough_params;
1358 irc_printf(conn, "NICK %s\r\n", str);
1359 return;
1360 }
1361 if (strcasecmp(arg0, "op") == 0) {
1362 if (channel_name == NULL)
1363 goto not_in_channel;
1364 irc_do_mode_to_users(conn, channel_name, "+o", str);
1365 return;
1366 }
1367 if (strcasecmp(arg0, "part") == 0) {
1368 if (channel_name == NULL)
1369 goto not_in_channel;
1370 irc_printf(conn, "PART %s%s%s\r\n", channel_name,
1371 (str ? " :" : ""), (str ? str : ""));
1372 return;
1373 }
1374 if (strcasecmp(arg0, "query") == 0) {
1375 if (str == NULL)
1376 goto not_enough_params;
1377 chatter_add_tab(conn->chatter, NULL, conn, NULL, str);
1378 return;
1379 }
1380 if (strcasecmp(arg0, "topic") == 0) {
1381 if (channel_name == NULL)
1382 goto not_in_channel;
1383 if (str)
1384 irc_printf(conn, "TOPIC %s :%s\r\n", channel_name, str);
1385 else
1386 irc_printf(conn, "TOPIC %s\r\n", channel_name);
1387 return;
1388 }
1389 if (strcasecmp(arg0, "umode") == 0) {
1390 irc_printf(conn, "MODE %s%s%s\r\n", conn->nick, (str ? " :" : ""),
1391 (str ? str : ""));
1392 return;
1393 }
1394 if (strcasecmp(arg0, "voice") == 0) {
1395 if (channel_name == NULL)
1396 goto not_in_channel;
1397 irc_do_mode_to_users(conn, channel_name, "+v", str);
1398 return;
1399 }
1400 if (strcasecmp(arg0, "who") == 0) {
1401 if (channel_name == NULL)
1402 goto not_in_channel;
1403 irc_printf(conn, "WHO %s\r\n", channel_name);
1404 return;
1405 }
1406 if (strcasecmp(arg0, "whois") == 0) {
1407 if (str == NULL)
1408 goto not_enough_params;
1409 irc_printf(conn, "WHOIS %s\r\n", str);
1410 return;
1411 }
1412
1413 chatter_printf(conn->chatter, conn, NULL,
1414 "unrecognized command \"$B%s$0\"",
1415 arg0);
1416 return;
1417
1418 not_enough_params:
1419 chatter_printf(conn->chatter, conn, channel ? channel->name : NULL,
1420 "$B*!*$0 Not enough parameters given");
1421 return;
1422 not_connected:
1423 chatter_printf(conn->chatter, conn, channel ? channel->name : NULL,
1424 "$B*!*$0 Not connected");
1425 return;
1426 not_in_channel:
1427 chatter_printf(conn->chatter, conn, NULL,
1428 "$B*!*$0 Cannot send (not in a channel)");
1429 return;
1430 }
1431
1432 struct irc_channel *
1433 irc_create_channel(struct irc_connection *conn, char *channame)
1434 {
1435 struct irc_channel *channel, *tchannel;
1436 short n;
1437
1438 SLIST_FOREACH(channel, &conn->channels_list, list) {
1439 if (strcasecmp(channel->name, channame) == 0)
1440 return channel;
1441 }
1442
1443 channel = xmalloczero(sizeof(struct irc_channel));
1444 if (channel == NULL)
1445 panic("Out of memory for new channel");
1446 SLIST_APPEND(&conn->channels_list, channel, irc_channel, list);
1447 channel->connection = conn;
1448 strlcpy(channel->name, channame, sizeof(channel->name));
1449 channel->chatter = conn->chatter;
1450 chatter_add_tab(channel->chatter, NULL, conn, channel, NULL);
1451
1452 irc_printf(conn, "MODE %s\r\n", channel->name);
1453
1454 return channel;
1455 }
1456
1457 void
1458 irc_quit_with_message(struct irc_connection *conn, char *str)
1459 {
1460 if (str)
1461 irc_printf(conn, "QUIT :%s\r\n", str);
1462 else
1463 irc_printf(conn, "QUIT :%s %s on a %s\r\n",
1464 PROGRAM_NAME, get_version(false), gestalt_machine_type());
1465 }
1466
1467 void
1468 irc_part_channel(struct irc_connection *conn, struct irc_channel *channel)
1469 {
1470 if (conn->state >= IRC_STATE_CONNECTED)
1471 irc_printf(conn, "PART %s\r\n", channel->name);
1472 }
1473
1474 void
1475 irc_dealloc_channel(struct irc_channel *channel)
1476 {
1477 struct irc_connection *conn = channel->connection;
1478 struct chatter *chatter = channel->chatter;
1479 struct chatter_tab *tab;
1480
1481 if ((tab = chatter_find_tab(chatter, conn, channel->name))) {
1482 /* clear channel first so close_tab doesn't try to part */
1483 tab->channel = NULL;
1484 chatter_close_tab(chatter, tab);
1485 }
1486
1487 if (channel->nicks)
1488 xfree(&channel->nicks);
1489
1490 SLIST_REMOVE(&conn->channels_list, channel, irc_channel, list);
1491
1492 xfree(&channel);
1493 }
1494
1495 void
1496 irc_buffer_names(struct irc_channel *channel, char *line)
1497 {
1498 size_t len, s;
1499
1500 len = strlen(line);
1501
1502 if (channel->nicks_buf_len + len >= channel->nicks_buf_size) {
1503 s = (IRC_MAX_MSG_SIZE * 5) + 1;
1504 channel->nicks_buf = xrealloc(channel->nicks_buf,
1505 channel->nicks_buf_size + s);
1506 if (channel->nicks_buf == NULL)
1507 panic("Out of memory allocating %ld+%ld bytes for more nicks",
1508 channel->nicks_buf_size, s);
1509 channel->nicks_buf_size += s;
1510 }
1511
1512 if (channel->nicks_buf_len) {
1513 channel->nicks_buf[channel->nicks_buf_len] = ' ';
1514 channel->nicks_buf_len++;
1515 }
1516
1517 memcpy(channel->nicks_buf + channel->nicks_buf_len, line, len);
1518 channel->nicks_buf_len += len;
1519 channel->nicks_buf[channel->nicks_buf_len] = '\0';
1520 }
1521
1522 void
1523 irc_parse_buffered_names(struct irc_channel *channel)
1524 {
1525 char *nick, *tline;
1526 short flags, count;
1527 long start = Ticks;
1528
1529 if (channel->nicks_size)
1530 memset(&channel->nicks, 0,
1531 sizeof(struct irc_channel_nick) * channel->nicks_size);
1532
1533 channel->nnicks = 0;
1534
1535 /* get a count of nicks to pass as a hint to malloc */
1536 count = 1;
1537 for (tline = channel->nicks_buf; *tline != '\0'; tline++)
1538 if (*tline == ' ')
1539 count++;
1540
1541 for (tline = channel->nicks_buf; tline != NULL; ) {
1542 nick = strsep(&tline, " ");
1543
1544 if (nick[0] == '@') {
1545 flags = IRC_NICK_FLAG_OP;
1546 nick++;
1547 } else if (nick[0] == '+') {
1548 flags = IRC_NICK_FLAG_VOICE;
1549 nick++;
1550 } else if (nick[0] == '\0') {
1551 /* some servers send a trailing space */
1552 break;
1553 } else
1554 flags = 0;
1555
1556 irc_add_nick_to_channel(channel, nick, flags, count);
1557 }
1558
1559 xfree(&channel->nicks_buf);
1560 channel->nicks_buf_size = 0;
1561 channel->nicks_buf_len = 0;
1562 }
1563
1564 void
1565 irc_add_nick_to_channel(struct irc_channel *channel, char *nick,
1566 short flags, short count_hint)
1567 {
1568 struct irc_channel_nick *anick, *cnick, *pnick;
1569 size_t new_size;
1570 short aidx, cidx, ret;
1571
1572 if (channel->nnicks >= channel->nicks_size) {
1573 if (count_hint < 10)
1574 count_hint = 10;
1575 new_size = channel->nicks_size + count_hint;
1576 /* 15% buffer */
1577 new_size += ((new_size * 100) / 666);
1578
1579 channel->nicks_size = new_size;
1580 channel->nicks = xreallocarray(channel->nicks,
1581 sizeof(struct irc_channel_nick), channel->nicks_size);
1582 if (channel->nicks == NULL)
1583 panic("Out of memory for %ld nicks (%ld bytes)",
1584 channel->nnicks,
1585 sizeof(struct irc_channel_nick) * channel->nicks_size);
1586 memset(&channel->nicks[channel->nnicks], 0,
1587 sizeof(struct irc_channel_nick) *
1588 (channel->nicks_size - channel->nnicks));
1589 aidx = channel->nnicks;
1590 } else {
1591 if (channel->nicks[channel->nnicks].nick[0] == '\0')
1592 aidx = channel->nnicks;
1593 else {
1594 /* find an open slot */
1595 for (aidx = 0; aidx < channel->nicks_size; aidx++) {
1596 if (channel->nicks[aidx].nick[0] == '\0')
1597 break;
1598 }
1599 }
1600 }
1601
1602 if (aidx >= channel->nicks_size)
1603 panic("irc_add_nick_to_channel overflow");
1604
1605 channel->nnicks++;
1606 anick = &channel->nicks[aidx];
1607 strlcpy(anick->nick, nick, sizeof(anick->nick));
1608 anick->flags = flags;
1609
1610 if (channel->nnicks == 1) {
1611 anick->next_nick = -1;
1612 channel->first_nick = 0;
1613 cidx = 0;
1614 } else {
1615 /* sort it in the right place by flags descending, then by nick */
1616 cnick = &channel->nicks[channel->first_nick];
1617 pnick = NULL;
1618 cidx = 0;
1619 while (cnick) {
1620 if (cnick->nick[0] == '\0')
1621 ret = 1;
1622 else if (cnick->flags == anick->flags)
1623 ret = strcasecmp(anick->nick, cnick->nick);
1624 else if (anick->flags > cnick->flags)
1625 ret = -1;
1626 else
1627 ret = 1;
1628
1629 if (ret <= 0) {
1630 /* new nick goes before this one */
1631 if (pnick) {
1632 anick->next_nick = pnick->next_nick;
1633 pnick->next_nick = aidx;
1634 } else {
1635 anick->next_nick = channel->first_nick;
1636 channel->first_nick = aidx;
1637 }
1638 break;
1639 }
1640
1641 cidx++;
1642
1643 if (cnick->next_nick == -1) {
1644 /* end of the line */
1645 cnick->next_nick = aidx;
1646 anick->next_nick = -1;
1647 break;
1648 }
1649
1650 pnick = cnick;
1651 cnick = &channel->nicks[cnick->next_nick];
1652 }
1653 }
1654
1655 if (!channel->nicks_buf_size)
1656 chatter_insert_to_nick_list(channel->chatter, channel, anick,
1657 cidx);
1658 }
1659
1660 void
1661 irc_remove_nick_from_channel(struct irc_channel *channel, char *nick)
1662 {
1663 struct irc_channel_nick *cnick, *pnick;
1664 short cidx;
1665
1666 if (channel->first_nick == -1)
1667 return;
1668
1669 pnick = NULL;
1670 cnick = &channel->nicks[channel->first_nick];
1671 cidx = 0;
1672 while (cnick) {
1673 if (strcmp(cnick->nick, nick) != 0) {
1674 pnick = cnick;
1675 cidx++;
1676 if (cnick->next_nick == -1)
1677 return;
1678 cnick = &channel->nicks[cnick->next_nick];
1679 continue;
1680 }
1681
1682 /* connect the nick that pointed to this one and the one we point to */
1683 if (pnick)
1684 pnick->next_nick = cnick->next_nick;
1685 else
1686 channel->first_nick = cnick->next_nick;
1687
1688 channel->nnicks--;
1689 cnick->nick[0] = '\0';
1690 chatter_remove_from_nick_list(channel->chatter, channel,
1691 cnick, cidx);
1692 return;
1693 }
1694 }
1695
1696 bool
1697 irc_nick_is_in_channel(struct irc_channel *channel, char *nick)
1698 {
1699 struct irc_channel_nick *cnick, *pnick;
1700
1701 if (channel->first_nick == -1)
1702 return false;
1703
1704 pnick = NULL;
1705 cnick = &channel->nicks[channel->first_nick];
1706 while (cnick) {
1707 if (strcmp(cnick->nick, nick) == 0)
1708 return true;
1709
1710 pnick = cnick;
1711 if (cnick->next_nick == -1)
1712 break;
1713 cnick = &channel->nicks[cnick->next_nick];
1714 }
1715
1716 return false;
1717 }
1718
1719 void
1720 irc_change_user_nick(struct irc_connection *conn,
1721 struct irc_channel *channel, struct irc_user *user, char *nick)
1722 {
1723 struct irc_channel_nick *cnick;
1724 short n, flags;
1725
1726 if (channel && channel->first_nick > -1) {
1727 /* preserve flags */
1728 cnick = &channel->nicks[channel->first_nick];
1729 while (cnick) {
1730 if (strcmp(cnick->nick, user->nick) == 0) {
1731 flags = cnick->flags;
1732 break;
1733 }
1734 if (cnick->next_nick == -1)
1735 break;
1736 cnick = &channel->nicks[cnick->next_nick];
1737 }
1738
1739 irc_remove_nick_from_channel(channel, user->nick);
1740 irc_add_nick_to_channel(channel, nick, flags, 0);
1741 }
1742
1743 if (strcasecmp(conn->nick, user->nick) == 0) {
1744 xfree(&conn->nick);
1745 conn->nick_len = strlen(nick);
1746 conn->nick = xstrdup(nick);
1747 if (conn->nick == NULL)
1748 panic("Out of memory changing nick");
1749 chatter_update_titlebar(conn->chatter);
1750 }
1751 }
1752
1753 bool
1754 irc_parse_channel_mode_change(struct irc_channel *channel, char *mode,
1755 char *args)
1756 {
1757 size_t len;
1758 struct irc_channel_nick *cnick;
1759 char *user;
1760 short n, j, flags;
1761 bool add = false;
1762 bool others = false;
1763
1764 len = strlen(mode);
1765
1766 /* mode:"+mvo-vo" args:"nick1 nick2 nick3 nick4" */
1767 for (n = 0; n < len; n++) {
1768 switch (mode[n]) {
1769 case '+':
1770 add = true;
1771 break;
1772 case '-':
1773 add = false;
1774 break;
1775 case 'v':
1776 case 'o':
1777 user = strsep(&args, " ");
1778 if (user == NULL)
1779 user = args;
1780
1781 cnick = &channel->nicks[channel->first_nick];
1782 while (cnick) {
1783 if (strcmp(cnick->nick, user) != 0) {
1784 if (cnick->next_nick == -1)
1785 break;
1786 cnick = &channel->nicks[cnick->next_nick];
1787 continue;
1788 }
1789
1790 flags = cnick->flags;
1791 if (mode[n] == 'o') {
1792 if (add)
1793 flags |= IRC_NICK_FLAG_OP;
1794 else
1795 flags &= ~IRC_NICK_FLAG_OP;
1796 } else if (mode[n] == 'v') {
1797 if (add)
1798 flags |= IRC_NICK_FLAG_VOICE;
1799 else
1800 flags &= ~IRC_NICK_FLAG_VOICE;
1801 }
1802
1803 irc_remove_nick_from_channel(channel, cnick->nick);
1804 /* cnick is probably invalid now */
1805 irc_add_nick_to_channel(channel, user, flags, 0);
1806 break;
1807 }
1808
1809 break;
1810 default:
1811 /* some other channel mode */
1812 others = true;
1813 break;
1814 }
1815 }
1816
1817 return others;
1818 }
1819
1820 void
1821 irc_do_mode_to_users(struct irc_connection *conn, char *channel_name,
1822 char *mode, char *nicks)
1823 {
1824 #define MAX_MODES_AT_ONCE 4
1825 /* assume we can only do 4 at a time */
1826 char modes[(2 * MAX_MODES_AT_ONCE) + 1];
1827 char mnicks[((member_size(struct irc_user, nick) + 1) *
1828 MAX_MODES_AT_ONCE) + 1];
1829 char *nick;
1830 short count;
1831
1832 /* TODO: support +b by looking up user in nick table for hostmask */
1833
1834 modes[0] = '\0';
1835 mnicks[0] = '\0';
1836 count = 0;
1837
1838 while (nicks != NULL) {
1839 strlcat(modes, mode, sizeof(modes));
1840
1841 nick = strsep(&nicks, " ");
1842 strlcat(mnicks, nick, sizeof(mnicks));
1843 count++;
1844
1845 if (nicks != NULL && count != MAX_MODES_AT_ONCE)
1846 strlcat(mnicks, " ", sizeof(mnicks));
1847
1848 if (nicks == NULL || count == 4) {
1849 irc_printf(conn, "MODE %s %s %s\r\n", channel_name, modes,
1850 mnicks);
1851 mnicks[0] = '\0';
1852 modes[0] = '\0';
1853 count = 0;
1854 }
1855 }
1856 }