AmendHub

Download

jcs

/

wallops

/

irc.c

 

(View History)

jcs   *: Smoother screen/tab updates, fix close/quit/dealloc sequences Latest amendment: 47 on 2023-01-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 "strnatcmp.h"
24
25 short irc_verify_state(struct irc_connection *conn, short state);
26 short irc_recv(struct irc_connection *conn);
27 size_t irc_send(struct irc_connection *conn, char *line, size_t size);
28 size_t irc_printf(struct irc_connection *conn, const char *format, ...);
29 char * irc_get_line(struct irc_connection *conn, size_t *retsize);
30 struct irc_user * irc_parse_user(char *str);
31 bool irc_can_send(struct irc_connection *conn);
32 struct irc_channel * irc_find_channel(struct irc_connection *conn,
33 char *channame);
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_dealloc_channel(struct irc_channel *channel);
38 void irc_set_active_channel(struct irc_connection *conn, char *channame);
39 void irc_parse_names(struct irc_channel *channel, char *line);
40 void irc_add_nick_to_channel(struct irc_channel *channel, char *nick,
41 short flags);
42 void irc_remove_nick_from_channel(struct irc_channel *channel, char *nick);
43 bool irc_nick_is_in_channel(struct irc_channel *channel, char *nick);
44 void irc_change_user_nick(struct irc_channel *channel,
45 struct irc_user *user, char *nick);
46 void irc_parse_channel_mode_change(struct irc_channel *channel, char *mode,
47 char *args);
48
49 #define IRC_CAN_SEND(conn) ((conn)->send_pb.ioResult <= 0)
50
51 struct irc_connections_head irc_connections_list =
52 SLIST_HEAD_INITIALIZER(irc_connections_list);
53
54 struct irc_connection *
55 irc_connect(struct chatter *chatter, const char *server,
56 const unsigned short port, const char *password, const char *nick,
57 const char *ident, const char *realname, const char *channel)
58 {
59 struct irc_connection *conn;
60 char ip_str[] = "255.255.255.255";
61 char *retname;
62 ip_addr ip = 0, local_ip = 0;
63 ip_port local_port = 0;
64 size_t len;
65 short err;
66
67 if ((err = _TCPInit()) != 0)
68 panic("TCPInit failed (%d)", err);
69
70 conn = xmalloczero(sizeof(struct irc_connection), "irc_connection");
71 SLIST_APPEND(&irc_connections_list, conn, irc_connection, list);
72 SLIST_INIT(&conn->channels_list);
73 conn->chatter = chatter;
74 conn->state = IRC_STATE_DISCONNECTED;
75 conn->hostname = xstrdup(server, "server");
76 conn->port = port;
77 if (password && password[0])
78 conn->password = xstrdup(password, "password");
79 conn->nick = xstrdup(nick, "nick");
80 conn->ident = xstrdup(ident, "ident");
81 conn->realname = xstrdup(realname, "realname");
82 if (channel && channel[0])
83 conn->channel_autojoin = xstrdup(channel, "chan");
84
85 chatter_printf(conn->chatter, conn, NULL,
86 "$B***$0 Connecting to $B%s:%d$0...", conn->hostname, conn->port);
87
88 if ((err = _TCPCreate(&conn->send_pb, &conn->stream, (Ptr)conn->tcp_buf,
89 sizeof(conn->tcp_buf), nil, nil, nil, false)) != noErr) {
90 chatter_printf(conn->chatter, conn, NULL,
91 "%B*!* TCPCreate failed: %d$0", err);
92 return conn;
93 }
94
95 if ((err = TCPResolveName(conn->hostname, &ip)) != 0) {
96 chatter_printf(conn->chatter, conn, NULL,
97 "$B*!* Couldn't resolve host %s (%d)$0", conn->hostname, err);
98 conn->state = IRC_STATE_DEAD;
99 return conn;
100 }
101
102 long2ip(ip, ip_str);
103
104 if ((err = _TCPActiveOpen(&conn->send_pb, conn->stream, ip,
105 conn->port, &local_ip, &local_port, nil, nil, false)) != noErr) {
106 chatter_printf(conn->chatter, conn, NULL,
107 "$B*!* Failed connecting to %s (%s) port %d: %d$0",
108 conn->hostname, ip_str, conn->port, err);
109 conn->state = IRC_STATE_DEAD;
110 return conn;
111 }
112
113 chatter_printf(conn->chatter, conn, NULL,
114 "$B***$0 Connected to $B%s$0 (%s) port %d", conn->hostname, ip_str,
115 conn->port);
116
117 conn->state = IRC_STATE_CONNECTED;
118
119 if (conn->password && conn->password[0]) {
120 len = snprintf(conn->line, sizeof(conn->line),
121 "PASS %s\r\n", conn->password);
122 irc_send(conn, conn->line, len);
123 }
124
125 len = snprintf(conn->line, sizeof(conn->line),
126 "NICK %s\r\n", conn->nick);
127 irc_send(conn, conn->line, len);
128
129 len = snprintf(conn->line, sizeof(conn->line),
130 "USER %s 0 * :%s\r\n", conn->ident, conn->realname);
131 irc_send(conn, conn->line, len);
132
133 return conn;
134 }
135
136 void
137 irc_close_connection(struct irc_connection *conn)
138 {
139 if (conn->stream) {
140 _TCPClose(&conn->close_pb, conn->stream, nil, nil, false);
141 _TCPAbort(&conn->close_pb, conn->stream, nil, nil, false);
142 _TCPRelease(&conn->close_pb, conn->stream, nil, nil, false);
143 conn->stream = 0;
144 }
145
146 conn->state = IRC_STATE_DISCONNECTED;
147 }
148
149 void
150 irc_dealloc_connection(struct irc_connection *conn)
151 {
152 struct irc_connection *tconn;
153 short n;
154
155 irc_close_connection(conn);
156
157 while (!SLIST_EMPTY(&conn->channels_list))
158 irc_dealloc_channel(SLIST_FIRST(&conn->channels_list));
159
160 if (conn->hostname != NULL)
161 xfree(&conn->hostname);
162 if (conn->password != NULL)
163 xfree(&conn->password);
164 if (conn->nick != NULL)
165 xfree(&conn->nick);
166 if (conn->ident != NULL)
167 xfree(&conn->ident);
168 if (conn->realname != NULL)
169 xfree(&conn->realname);
170 if (conn->channel_autojoin != NULL)
171 xfree(&conn->channel_autojoin);
172
173 SLIST_REMOVE(&irc_connections_list, conn, irc_connection, list);
174
175 xfree(&conn);
176 }
177
178 void
179 irc_process(struct irc_connection *conn)
180 {
181 short was_state = conn->state;
182
183 if (conn->state >= IRC_STATE_CONNECTED)
184 irc_recv(conn);
185
186 switch (conn->state) {
187 case IRC_STATE_DISCONNECTED:
188 break;
189 case IRC_STATE_DEAD:
190 irc_close_connection(conn);
191 break;
192 case IRC_STATE_CONNECTED:
193 irc_process_server(conn);
194 break;
195 }
196 }
197
198 short
199 irc_verify_state(struct irc_connection *conn, short state)
200 {
201 if (conn->state != state) {
202 warn("Bad IRC state (in %d, expected %d)", conn->state, state);
203 conn->state = IRC_STATE_DEAD;
204 return 0;
205 }
206
207 return 1;
208 }
209
210 short
211 irc_recv(struct irc_connection *conn)
212 {
213 unsigned short rlen;
214 short error, rerror, n;
215
216 if (conn->state < IRC_STATE_CONNECTING)
217 return 0;
218
219 error = _TCPStatus(&conn->rcv_pb, conn->stream, &conn->status_pb, nil,
220 nil, false);
221
222 if (conn->status_pb.amtUnreadData > 0 &&
223 conn->ibuflen < sizeof(conn->ibuf)) {
224 rlen = conn->status_pb.amtUnreadData;
225 if (conn->ibuflen + rlen > sizeof(conn->ibuf))
226 rlen = sizeof(conn->ibuf) - conn->ibuflen;
227
228 rerror = _TCPRcv(&conn->rcv_pb, conn->stream,
229 (Ptr)(conn->ibuf + conn->ibuflen), &rlen, nil, nil, false);
230 if (rerror) {
231 chatter_printf(conn->chatter, conn, NULL,
232 "$B*!* TCPRecv failed (%d), disconnecting$0", error);
233 conn->state = IRC_STATE_DEAD;
234 return -1;
235 }
236
237 conn->ibuflen += rlen;
238 }
239
240 if (error) {
241 /* let already-consumed buffer finish processing */
242 while (irc_process_server(conn))
243 SystemTask();
244 chatter_printf(conn->chatter, conn, NULL,
245 "$B*!* TCPStatus failed: %d$0",
246 error);
247 conn->state = IRC_STATE_DEAD;
248 return -1;
249 }
250
251 return rlen;
252 }
253
254 size_t
255 irc_send(struct irc_connection *conn, char *line, size_t size)
256 {
257 short error;
258
259 if (size > sizeof(conn->obuf))
260 panic("irc_send: too much data %lu", size);
261
262 if (!irc_verify_state(conn, IRC_STATE_CONNECTED))
263 return;
264
265 while (!IRC_CAN_SEND(conn))
266 SystemTask();
267
268 if (conn->send_pb.ioResult < 0) {
269 chatter_printf(conn->chatter, conn, NULL,
270 "$B*!* TCPSend failed (%d), disconnecting$0",
271 conn->send_pb.ioResult);
272 conn->state = IRC_STATE_DEAD;
273 return 0;
274 }
275
276 memcpy(&conn->obuf, line, size);
277
278 /*
279 * _TCPSend only knows how many wds pointers were passed in when it
280 * reads the next one and its pointer is zero (or size is zero?), so
281 * even though we're only sending one wds, memory after wds[0]
282 * has to be zeroed out.
283 */
284 memset(&conn->wds, 0, sizeof(conn->wds));
285 conn->wds[0].ptr = (Ptr)&conn->obuf;
286 conn->wds[0].length = size;
287
288 error = _TCPSend(&conn->send_pb, conn->stream, conn->wds, nil, nil,
289 true);
290 if (error) {
291 chatter_printf(conn->chatter, conn, NULL,
292 "$B*!* TCPSend failed (%d), disconnecting$0", error);
293 conn->state = IRC_STATE_DEAD;
294 return 0;
295 }
296
297 return size;
298 }
299
300 size_t
301 irc_printf(struct irc_connection *conn, const char *format, ...)
302 {
303 static char buf[512];
304 va_list argptr;
305 size_t len = 0;
306
307 va_start(argptr, format);
308 len = vsnprintf(buf, sizeof(buf), format, argptr);
309 va_end(argptr);
310
311 if (len > sizeof(buf)) {
312 warn("irc_printf overflow!");
313 len = sizeof(buf);
314 buf[len - 1] = '\0';
315 }
316
317 irc_send(conn, buf, len);
318
319 return len;
320 }
321
322 struct irc_user *
323 irc_parse_user(char *str)
324 {
325 static struct irc_user parsed_user;
326 short ret;
327
328 memset(&parsed_user, 0, sizeof(parsed_user));
329 ret = sscanf(str, "%[^!]!%[^@]@%s", &parsed_user.nick,
330 &parsed_user.username, &parsed_user.hostname);
331 if (ret != 3) {
332 /* probably just a hostname */
333 strlcpy(parsed_user.nick, str, sizeof(parsed_user.nick));
334 strlcpy(parsed_user.username, str, sizeof(parsed_user.username));
335 strlcpy(parsed_user.hostname, str, sizeof(parsed_user.hostname));
336 }
337 return &parsed_user;
338 }
339
340 char *
341 irc_get_line(struct irc_connection *conn, size_t *retsize)
342 {
343 size_t n;
344
345 if (conn->ibuflen == 0) {
346 if (retsize != NULL)
347 *retsize = 0;
348 return NULL;
349 }
350
351 for (n = 0; n < conn->ibuflen - 1; n++) {
352 if (conn->ibuf[n] != '\r')
353 continue;
354 if (conn->ibuf[n + 1] != '\n')
355 continue;
356
357 memcpy(conn->line, conn->ibuf, n + 1);
358 conn->line[n] = '\0';
359 if (retsize != NULL)
360 *retsize = n + 1;
361
362 if (n == conn->ibuflen - 2) {
363 conn->ibuflen = 0;
364 } else {
365 conn->ibuflen -= n + 2;
366 if (conn->ibuflen < 0)
367 panic("bogus ibuflen %d", conn->ibuflen);
368 memmove(conn->ibuf, conn->ibuf + n + 2, conn->ibuflen);
369 }
370 return conn->line;
371 }
372
373 return NULL;
374 }
375
376 struct irc_channel *
377 irc_find_channel(struct irc_connection *conn, char *channame)
378 {
379 struct irc_channel *channel;
380 short n;
381
382 if (channame == NULL || channame[0] == '\0')
383 return NULL;
384
385 SLIST_FOREACH(channel, &conn->channels_list, list) {
386 if (strcasecmp(channel->name, channame) == 0)
387 return channel;
388 }
389
390 return NULL;
391 }
392
393 bool
394 irc_process_server(struct irc_connection *conn)
395 {
396 struct irc_msg msg;
397 struct irc_user *user;
398 struct irc_channel *channel;
399 char *line, *word;
400 size_t size, n, lastbreak;
401 short state, curarg;
402
403 line = irc_get_line(conn, &size);
404 if (size == 0 || line == NULL)
405 return false;
406
407 memset(&msg, 0, sizeof(msg));
408
409 word = strsep(&line, " ");
410
411 /* extract source before command */
412 if (word[0] == ':') {
413 /* ":server.name 001 jcs :Hi" -> msg.source=server.name */
414 strlcpy(msg.source, word + 1, sizeof(msg.source));
415 word = strsep(&line, " ");
416 }
417
418 /* code or a command name */
419 if (isdigit(word[0]) && isdigit(word[1]) && isdigit(word[2]))
420 /* ":server.name 001 jcs :Hi" -> msg.code=1 */
421 msg.code = atoi(word);
422 else
423 /* "PING :server.name" -> msg.cmd=PING */
424 /* ":jcs!~jcs@jcs JOIN #wallops" -> msg.cmd=JOIN */
425 strlcpy(msg.cmd, word, sizeof(msg.cmd));
426
427 /* parameters, put into args until we see one starting with : */
428 curarg = 0;
429 while (line != NULL && line[0] != '\0') {
430 if (line[0] == ':') {
431 strlcpy(msg.msg, line + 1, sizeof(msg.msg));
432
433 /* some irc servers put the first arg as the message */
434 if (curarg == 0)
435 strlcpy(msg.arg[0], line + 1, sizeof(msg.arg[0]));
436 break;
437 }
438
439 word = strsep(&line, " ");
440 if (word == NULL) {
441 strlcpy(msg.msg, line, sizeof(msg.msg));
442 break;
443 }
444
445 strlcpy(msg.arg[curarg], word, sizeof(msg.arg[curarg]));
446 curarg++;
447
448 if (curarg >= IRC_MSG_MAX_ARGS) {
449 /* just stick the rest in msg */
450 strlcpy(msg.msg, line, sizeof(msg.msg));
451 break;
452 }
453 }
454
455 if (msg.cmd[0]) {
456 /* this one will fire most often, keep at the top */
457 if (strcmp(msg.cmd, "PRIVMSG") == 0) {
458 user = irc_parse_user(msg.source);
459 size = strlen(msg.msg);
460 channel = irc_find_channel(conn, msg.arg[0]);
461
462 if (msg.msg[0] == '\1' && msg.msg[size - 1] == '\1') {
463 /* CTCP or a /me action */
464 msg.msg[size - 1] = '\0';
465 if (strncmp(msg.msg + 1, "ACTION", 6) == 0) {
466 chatter_printf(channel ? channel->chatter :
467 conn->chatter, conn, channel,
468 "* %s$/ %s",
469 user->nick, msg.msg + 8);
470 } else if (strncmp(msg.msg + 1, "VERSION", 7) == 0) {
471 chatter_printf(conn->chatter, conn, NULL,
472 "$B*** %s ($0%s@%s$B)$0 requested CTCP $BVERSION$0 "
473 "from $B%s$0",
474 user->nick, user->username, user->hostname,
475 msg.arg[0]);
476
477 /* only respond if it was sent directly to us */
478 if (strcmp(msg.arg[0], conn->nick) == 0) {
479 irc_printf(conn,
480 "NOTICE %s :\1VERSION %s %s on a %s\1\r\n",
481 user->nick, PROGRAM_NAME, get_version(false),
482 gestalt_machine_type());
483 }
484 } else {
485 goto unknown;
486 }
487 } else if (strcmp(msg.arg[0], conn->nick) == 0) {
488 chatter_printf(channel ? channel->chatter : conn->chatter,
489 conn, channel,
490 "$B[%s($0%s@%s$B)]$0$/ %s",
491 user->nick, user->username, user->hostname, msg.msg);
492 } else if (channel != NULL) {
493 size = strlen(conn->nick);
494 if (strncmp(msg.msg, conn->nick, size) == 0 &&
495 (msg.msg[size] == ' ' || msg.msg[size] == ':' ||
496 msg.msg[size] == ',' || msg.msg[size] == '\0')) {
497 /* highlight message */
498 chatter_printf(channel->chatter, conn, channel,
499 "<$U%s$0>$/ %s", user->nick,
500 msg.msg);
501 if (!conn->chatter->focusable->visible)
502 notify();
503 } else
504 chatter_printf(channel->chatter, conn, channel,
505 "$/<%s> %s",
506 user->nick, msg.msg);
507 } else
508 chatter_printf(conn->chatter, conn, NULL,
509 "$B[%s($0%s@%s$B):%s]$0$/ %s",
510 user->nick, user->username, user->hostname, msg.arg[0],
511 msg.msg);
512 return true;
513 }
514 if (strcmp(msg.cmd, "ERROR") == 0) {
515 chatter_printf(conn->chatter, conn, NULL,
516 "$B*!* Disconnected$0 from $B%s$0:$/ %s",
517 conn->hostname, msg.msg);
518 conn->state = IRC_STATE_DEAD;
519 return true;
520 }
521 if (strcmp(msg.cmd, "JOIN") == 0) {
522 user = irc_parse_user(msg.source);
523 if (strcmp(user->nick, conn->nick) == 0)
524 channel = irc_create_channel(conn, msg.arg[0]);
525 else {
526 channel = irc_find_channel(conn, msg.arg[0]);
527 irc_add_nick_to_channel(channel, user->nick, 0);
528 }
529 chatter_printf(channel->chatter, conn, channel,
530 "$B*** %s ($0%s@%s$B)$0 has joined $B%s$0",
531 user->nick, user->username, user->hostname, msg.arg[0]);
532 return true;
533 }
534 if (strcmp(msg.cmd, "KICK") == 0) {
535 user = irc_parse_user(msg.source);
536 channel = irc_find_channel(conn, msg.arg[0]);
537 if (strcmp(msg.arg[1], conn->nick) == 0) {
538 irc_dealloc_channel(channel);
539 channel = NULL;
540 } else
541 irc_remove_nick_from_channel(channel, msg.arg[1]);
542 chatter_printf(channel ? channel->chatter : conn->chatter,
543 conn, channel,
544 "$B*** %s$0 was kicked from $B%s$0 by $B%s%0:$/ %s",
545 msg.arg[1], msg.arg[0], user->nick, msg.msg);
546 return true;
547 }
548 if (strcmp(msg.cmd, "KILL") == 0) {
549 user = irc_parse_user(msg.source);
550 if (strcmp(user->nick, conn->nick) == 0) {
551 /* we died :( */
552 warn("%s (%s@%s) has been killed: %s",
553 user->nick, user->username, user->hostname, msg.msg);
554 conn->state = IRC_STATE_DEAD;
555 return false;
556 }
557 SLIST_FOREACH(channel, &conn->channels_list, list) {
558 if (!irc_nick_is_in_channel(channel, user->nick))
559 continue;
560
561 chatter_printf(conn->chatter, conn, channel,
562 "$B*** %s ($0%s@%s$B)$0 has been killed:$/ %s",
563 user->nick, user->username, user->hostname, msg.msg);
564 irc_remove_nick_from_channel(channel, user->nick);
565 }
566 return true;
567 }
568 if (strcmp(msg.cmd, "MODE") == 0) {
569 if (strcmp(msg.arg[0], conn->nick) == 0)
570 chatter_printf(conn->chatter, conn, NULL,
571 "$B***$0 Mode change ($B%s$0) for user $B%s$0",
572 msg.msg, msg.arg[0]);
573 else {
574 user = irc_parse_user(msg.source);
575 channel = irc_find_channel(conn, msg.arg[0]);
576 /* concatenate nicks */
577 msg.msg[0] = '\0';
578 if (msg.arg[2][0] != '\0')
579 strlcat(msg.msg, msg.arg[2], sizeof(msg.msg));
580 if (msg.arg[3][0] != '\0') {
581 strlcat(msg.msg, " ", sizeof(msg.msg));
582 strlcat(msg.msg, msg.arg[3], sizeof(msg.msg));
583 }
584 if (msg.arg[4][0] != '\0') {
585 strlcat(msg.msg, " ", sizeof(msg.msg));
586 strlcat(msg.msg, msg.arg[4], sizeof(msg.msg));
587 }
588 if (msg.arg[5][0] != '\0') {
589 strlcat(msg.msg, " ", sizeof(msg.msg));
590 strlcat(msg.msg, msg.arg[5], sizeof(msg.msg));
591 }
592 chatter_printf(channel ? channel->chatter : conn->chatter,
593 conn, channel,
594 "$B***$0 Mode change ($B%s %s$0) on $B%s$0 by $B%s%0",
595 msg.arg[1], msg.msg, msg.arg[0], user->nick);
596 if (channel)
597 irc_parse_channel_mode_change(channel, msg.arg[1],
598 msg.msg);
599 }
600 return true;
601 }
602 if (strcmp(msg.cmd, "NICK") == 0) {
603 user = irc_parse_user(msg.source);
604 SLIST_FOREACH(channel, &conn->channels_list, list) {
605 if (!irc_nick_is_in_channel(channel, user->nick))
606 continue;
607
608 chatter_printf(channel->chatter, conn, channel,
609 "$B*** %s$0 is now known as $B%s$0",
610 user->nick, msg.msg);
611 irc_change_user_nick(channel, user, msg.msg);
612 }
613 return true;
614 }
615 if (strcmp(msg.cmd, "NOTICE") == 0) {
616 if (strncmp(msg.msg, "*** ", 4) == 0)
617 chatter_printf(conn->chatter, conn, NULL,
618 "$B***$0 $/%s",
619 msg.msg + 4);
620 else
621 chatter_printf(conn->chatter, conn, NULL,
622 "$/%s",
623 msg.msg);
624 return true;
625 }
626 if (strcmp(msg.cmd, "PART") == 0) {
627 user = irc_parse_user(msg.source);
628 channel = irc_find_channel(conn, msg.arg[0]);
629 if (strcmp(user->nick, conn->nick) == 0) {
630 irc_dealloc_channel(channel);
631 /* we don't need to print anything */
632 } else {
633 irc_remove_nick_from_channel(channel, user->nick);
634 chatter_printf(channel ? channel->chatter : conn->chatter,
635 conn, channel,
636 "$B*** %s ($0%s@%s$B)$0 has left $B%s$0",
637 user->nick, user->username, user->hostname, msg.arg[0]);
638 }
639 return true;
640 }
641 if (strcmp(msg.cmd, "PING") == 0) {
642 irc_printf(conn, "PONG :%s\r\n", msg.msg);
643 return true;
644 }
645 if (strcmp(msg.cmd, "QUIT") == 0) {
646 user = irc_parse_user(msg.source);
647 SLIST_FOREACH(channel, &conn->channels_list, list) {
648 if (!irc_nick_is_in_channel(channel, user->nick))
649 continue;
650
651 irc_remove_nick_from_channel(channel, user->nick);
652 chatter_printf(conn->chatter, conn, channel,
653 "$B*** %s ($0%s@%s$B)$0 has quit:$/ %s",
654 user->nick, user->username, user->hostname, msg.msg);
655 }
656 if (strcmp(user->nick, conn->nick) == 0) {
657 chatter_printf(conn->chatter, conn, NULL,
658 "$B*** %s ($0%s@%s$B)$0 has quit:$/ %s",
659 user->nick, user->username, user->hostname, msg.msg);
660 conn->state = IRC_STATE_DEAD;
661 return false;
662 }
663 return true;
664 }
665 goto unknown;
666 }
667
668 switch (msg.code) {
669 case 0:
670 goto unknown;
671 case 1:
672 case 2:
673 /* welcome banners */
674 goto print_msg;
675 case 3:
676 case 4:
677 case 5:
678 case 250:
679 case 251:
680 case 252:
681 case 253:
682 case 254:
683 case 255:
684 case 265:
685 case 266:
686 /* server stats, unhelpful */
687 return true;
688 case 307:
689 /* WHOIS regnick */
690 chatter_printf(conn->chatter, conn, NULL,
691 "$B*** |$0 Authenticated:$/ %s",
692 msg.msg);
693 return true;
694 case 311:
695 /* WHOIS nick: "nick :ident hostname * :name" */
696 chatter_printf(conn->chatter, conn, NULL,
697 "$B*** %s ($0%s@%s$B)$0",
698 msg.arg[1], msg.arg[2], msg.arg[3]);
699 chatter_printf(conn->chatter, conn, NULL,
700 "$B*** |$0 Name:$/ %s",
701 msg.msg);
702 return true;
703 case 312:
704 /* WHOIS server */
705 chatter_printf(conn->chatter, conn, NULL,
706 "$B*** |$0 Server:$/ %s (%s)",
707 msg.arg[2], msg.msg);
708 return true;
709 case 671:
710 /* WHOIS server */
711 chatter_printf(conn->chatter, conn, NULL,
712 "$B*** |$0 Connection:$/ %s",
713 msg.msg);
714 return true;
715 case 317:
716 /* WHOIS idle */
717 chatter_printf(conn->chatter, conn, NULL,
718 "$B*** |$0 Idle:$/ %s %s",
719 msg.arg[2], msg.msg);
720 return true;
721 case 318:
722 /* WHOIS end */
723 chatter_printf(conn->chatter, conn, NULL,
724 "$B*** `-------$0");
725 return true;
726 case 319:
727 /* WHOIS channels */
728 chatter_printf(conn->chatter, conn, NULL,
729 "$B*** |$0 Channels:$/ %s",
730 msg.msg);
731 return true;
732 case 330:
733 /* WHOIS account */
734 chatter_printf(conn->chatter, conn, NULL,
735 "$B*** |$0 Account:$/ %s %s",
736 msg.msg, msg.arg[2]);
737 return true;
738 case 338:
739 case 378:
740 /* WHOIS host */
741 chatter_printf(conn->chatter, conn, NULL,
742 "$B*** |$0 Host:$/ %s %s",
743 msg.msg, msg.arg[2]);
744 return true;
745 case 328:
746 /* channel URL, we probably can't do anything with it anyway */
747 return true;
748 case 332:
749 /* TOPIC */
750 channel = irc_find_channel(conn, msg.arg[1]);
751 chatter_printf(channel ? channel->chatter : conn->chatter, conn,
752 channel,
753 "$B***$0 Topic for $B%s$0:$/ %s",
754 msg.arg[1], msg.msg);
755 if (channel)
756 strlcpy(channel->topic, msg.msg, sizeof(channel->topic));
757 return true;
758 case 333:
759 /* TOPIC creator */
760 return true;
761 case 352:
762 /* WHO output */
763 goto unknown;
764 case 315:
765 /* end of WHO */
766 goto unknown;
767 case 353:
768 /* NAMES output */
769 channel = irc_find_channel(conn, msg.arg[2]);
770 if (channel)
771 irc_parse_names(channel, msg.msg);
772 return true;
773 case 366:
774 /* end of NAMES output */
775 channel = irc_find_channel(conn, msg.arg[1]);
776 if (channel)
777 chatter_sync_nick_list(channel->chatter, channel, true);
778 return true;
779 case 372:
780 case 375:
781 /* MOTD */
782 goto print_msg;
783 case 376:
784 /* end of MOTD */
785 if (conn->channel_autojoin && conn->channel_autojoin[0] &&
786 !conn->did_autojoin) {
787 irc_printf(conn, "JOIN %s\r\n", conn->channel_autojoin);
788 conn->did_autojoin = true;
789 }
790 goto print_msg;
791 case 396:
792 /* Cloak */
793 chatter_printf(conn->chatter, conn, NULL,
794 "$B***$0$/ %s %s",
795 msg.arg[1], msg.msg);
796 return true;
797 case 433: {
798 /* Nick in use, try appending a _ */
799 char *new_nick = xmalloc(strlen(conn->nick) + 2, "new nick");
800 size_t len;
801
802 chatter_printf(conn->chatter, conn, NULL,
803 "$B***$0$/ %s: %s",
804 msg.arg[1], msg.msg);
805
806 sprintf(new_nick, "%s_", conn->nick);
807 xfree(&conn->nick);
808 conn->nick = new_nick;
809 chatter_update_titlebar(conn->chatter);
810
811 len = snprintf(conn->line, sizeof(conn->line),
812 "NICK %s\r\n", conn->nick);
813 irc_send(conn, conn->line, len);
814 return true;
815 }
816 default:
817 goto unknown;
818 }
819
820 print_msg:
821 chatter_printf(conn->chatter, conn, NULL,
822 "$B***$0$/ %s",
823 msg.msg);
824 return true;
825
826 unknown:
827 chatter_printf(conn->chatter, conn, NULL,
828 "$B[?]$0$/ code:%d cmd:%s source:%s arg0:%s arg1:%s arg2:%s arg3:%s "
829 "arg4:%s msg:%s",
830 msg.code, msg.cmd, msg.source, msg.arg[0], msg.arg[1], msg.arg[2],
831 msg.arg[3], msg.arg[4], msg.msg);
832 return true;
833 }
834
835 void
836 irc_process_input(struct irc_connection *conn, struct irc_channel *channel,
837 char *str)
838 {
839 char *arg0, *arg1;
840 size_t n;
841
842 if (conn == NULL || conn->state < IRC_STATE_CONNECTED)
843 goto not_connected;
844
845 if (str[0] != '/') {
846 if (channel == NULL)
847 goto not_in_channel;
848 irc_printf(conn, "PRIVMSG %s :%s\r\n", channel->name, str);
849 chatter_printf(conn->chatter, conn, channel,
850 "<$B%s$0>$/ %s",
851 conn->nick, str);
852 return;
853 }
854
855 /* skip / */
856 str++;
857
858 arg0 = strsep(&str, " ");
859
860 if (strcasecmp(arg0, "join") == 0) {
861 if (conn == NULL)
862 goto not_connected;
863 if (str == NULL)
864 goto not_enough_params;
865 /*
866 * If we're already in this channel, we won't get any response
867 * from the server to switch channels.
868 */
869 channel = irc_find_channel(conn, str);
870 if (channel)
871 /* this won't actually create, it'll just switch for us */
872 irc_create_channel(conn, str);
873 else
874 irc_printf(conn, "JOIN %s\r\n", str);
875 return;
876 }
877 if (strcasecmp(arg0, "me") == 0) {
878 if (conn == NULL)
879 goto not_connected;
880 if (str == NULL)
881 goto not_enough_params;
882 if (channel == NULL)
883 goto not_in_channel;
884 chatter_printf(conn->chatter, conn, channel,
885 "* %s$/ %s",
886 conn->nick, str);
887 irc_printf(conn, "PRIVMSG %s :\1ACTION %s\1\r\n", channel->name,
888 str);
889 return;
890 }
891 if (strcasecmp(arg0, "msg") == 0) {
892 if (conn == NULL)
893 goto not_connected;
894 arg1 = strsep(&str, " ");
895 if (arg1 == NULL || str == NULL)
896 goto not_enough_params;
897 chatter_printf(conn->chatter, conn, NULL,
898 "$B[$0msg$B($0%s$B)]$0$/ %s",
899 arg1, str);
900 irc_printf(conn, "PRIVMSG %s :%s\r\n", arg1, str);
901 return;
902 }
903 if (strcasecmp(arg0, "nick") == 0) {
904 if (conn == NULL)
905 goto not_connected;
906 if (str == NULL)
907 goto not_enough_params;
908 irc_printf(conn, "NICK %s\r\n", str);
909 return;
910 }
911 if (strcasecmp(arg0, "part") == 0) {
912 if (conn == NULL)
913 goto not_connected;
914 if (str == NULL && channel)
915 irc_printf(conn, "PART %s\r\n", channel->name);
916 else if (str)
917 irc_printf(conn, "PART %s\r\n", str);
918 else
919 goto not_in_channel;
920 return;
921 }
922 if (strcasecmp(arg0, "quit") == 0) {
923 if (conn == NULL)
924 goto not_connected;
925 irc_printf(conn, "QUIT :%s\r\n", str == NULL ? "Quitting" : str);
926 return;
927 }
928 if (strcasecmp(arg0, "quote") == 0) {
929 if (conn == NULL)
930 goto not_connected;
931 if (str == NULL)
932 goto not_enough_params;
933 irc_printf(conn, "%s\r\n", str);
934 return;
935 }
936 if (strcasecmp(arg0, "whois") == 0) {
937 if (conn == NULL)
938 goto not_connected;
939 if (str == NULL)
940 goto not_enough_params;
941 irc_printf(conn, "WHOIS %s\r\n", str);
942 return;
943 }
944 chatter_printf(conn->chatter, conn, NULL,
945 "unrecognized command \"$B%s$0\"",
946 arg0);
947 return;
948
949 not_enough_params:
950 chatter_printf(conn->chatter, conn, NULL,
951 "$B*!*$0 Not enough parameters given");
952 return;
953 not_connected:
954 chatter_printf(conn->chatter, conn, NULL,
955 "$B*!*$0 Not connected");
956 return;
957 not_in_channel:
958 chatter_printf(conn->chatter, conn, NULL,
959 "$B*!*$0 Cannot send (not in a channel)");
960 return;
961 }
962
963 struct irc_channel *
964 irc_create_channel(struct irc_connection *conn, char *channame)
965 {
966 struct irc_channel *channel = NULL, *tchannel;
967 short n;
968
969 SLIST_FOREACH(channel, &conn->channels_list, list) {
970 if (strcasecmp(channel->name, channame) == 0) {
971 /* TODO: chatter_switch_to_channel(channel) */
972 #if 0
973 channel->chatter->cur_conn = channel->connection;
974 channel->chatter->cur_channel = channel;
975 chatter_sync_nick_list(channel->chatter, channel, false);
976 chatter_update_titlebar(channel->chatter);
977 #endif
978 return channel;
979 }
980 }
981
982 channel = xmalloczero(sizeof(struct irc_channel), "irc_channel");
983 SLIST_APPEND(&conn->channels_list, channel, irc_channel, list);
984 channel->connection = conn;
985 strlcpy(channel->name, channame, sizeof(channel->name));
986 channel->chatter = conn->chatter;
987 chatter_add_tab(channel->chatter, NULL, conn, channel);
988
989 return channel;
990 }
991
992 void
993 irc_dealloc_channel(struct irc_channel *channel)
994 {
995 struct irc_connection *conn = channel->connection;
996 struct irc_channel *tchannel;
997 struct chatter *chatter = channel->chatter;
998
999 chatter_remove_channel(chatter, channel);
1000
1001 if (channel->nicks)
1002 xfree(&channel->nicks);
1003
1004 SLIST_REMOVE(&conn->channels_list, channel, irc_channel, list);
1005
1006 chatter_update_titlebar(chatter);
1007 }
1008
1009 void
1010 irc_parse_names(struct irc_channel *channel, char *line)
1011 {
1012 size_t n;
1013 char *nick;
1014 bool last = false;
1015 short flags;
1016
1017 // LDoDraw(false, channel->chatter->nick_list);
1018
1019 for (;;) {
1020 nick = strsep(&line, " ");
1021
1022 if (nick[0] == '@') {
1023 flags = IRC_NICK_FLAG_OP;
1024 nick++;
1025 } else if (nick[0] == '+') {
1026 flags = IRC_NICK_FLAG_VOICE;
1027 nick++;
1028 } else if (nick[0] == '\0') {
1029 /* some servers send a trailing space */
1030 break;
1031 } else
1032 flags = 0;
1033
1034 irc_add_nick_to_channel(channel, nick, flags);
1035
1036 if (line == NULL)
1037 break;
1038 }
1039
1040 // LDoDraw(true, channel->chatter->nick_list);
1041 // InvalRect(&(*(channel->chatter->nick_list))->rView);
1042 }
1043
1044 void
1045 irc_add_nick_to_channel(struct irc_channel *channel, char *nick,
1046 short flags)
1047 {
1048 struct irc_channel_nick *anick, *cnick, *pnick;
1049 short aidx, cidx, ret;
1050
1051 if (channel->nnicks >= channel->nicks_size) {
1052 /* allocate a chunk at a time so we don't do this every iteration */
1053 channel->nicks_size += 5;
1054 channel->nicks = xreallocarray(channel->nicks,
1055 sizeof(struct irc_channel_nick),
1056 channel->nicks_size);
1057 memset(&channel->nicks[channel->nnicks], 0,
1058 sizeof(struct irc_channel_nick) *
1059 (channel->nicks_size - channel->nnicks));
1060 aidx = channel->nnicks;
1061 } else {
1062 /* find an open slot */
1063 for (aidx = 0; aidx < channel->nicks_size; aidx++) {
1064 if (channel->nicks[aidx].nick[0] == '\0')
1065 break;
1066 }
1067
1068 if (aidx >= channel->nicks_size)
1069 panic("irc_add_nick_to_channel overflow");
1070 }
1071
1072 channel->nnicks++;
1073 anick = &channel->nicks[aidx];
1074 strlcpy(anick->nick, nick, sizeof(anick->nick));
1075 anick->flags = flags;
1076
1077 if (channel->nnicks == 1) {
1078 anick->next_nick = -1;
1079 channel->first_nick = 0;
1080 cidx = 0;
1081 } else {
1082 /* sort it in the right place by flags descending, then by nick */
1083 cnick = &channel->nicks[channel->first_nick];
1084 pnick = NULL;
1085 cidx = 0;
1086 while (cnick) {
1087 if (cnick->nick[0] == '\0')
1088 ret = 1;
1089 if (cnick->flags == anick->flags)
1090 ret = strnatcasecmp(anick->nick, cnick->nick);
1091 else if (anick->flags > cnick->flags)
1092 ret = -1;
1093 else
1094 ret = 1;
1095
1096 if (ret <= 0) {
1097 /* new nick goes before this one */
1098 if (pnick) {
1099 anick->next_nick = pnick->next_nick;
1100 pnick->next_nick = aidx;
1101 } else {
1102 anick->next_nick = channel->first_nick;
1103 channel->first_nick = aidx;
1104 }
1105 break;
1106 }
1107
1108 cidx++;
1109
1110 if (cnick->next_nick == -1) {
1111 /* end of the line */
1112 cnick->next_nick = aidx;
1113 anick->next_nick = -1;
1114 break;
1115 }
1116
1117 pnick = cnick;
1118 cnick = &channel->nicks[cnick->next_nick];
1119 }
1120 }
1121
1122 chatter_insert_to_nick_list(channel->chatter, channel, anick, cidx);
1123 }
1124
1125 void
1126 irc_remove_nick_from_channel(struct irc_channel *channel, char *nick)
1127 {
1128 struct irc_channel_nick *cnick, *pnick;
1129 short cidx;
1130
1131 if (channel->first_nick == -1)
1132 return;
1133
1134 pnick = NULL;
1135 cnick = &channel->nicks[channel->first_nick];
1136 cidx = 0;
1137 while (cnick) {
1138 if (strcmp(cnick->nick, nick) != 0) {
1139 pnick = cnick;
1140 cidx++;
1141 if (cnick->next_nick == -1)
1142 return;
1143 cnick = &channel->nicks[cnick->next_nick];
1144 continue;
1145 }
1146
1147 /* connect the nick that pointed to this one and the one we point to */
1148 if (pnick)
1149 pnick->next_nick = cnick->next_nick;
1150 else
1151 channel->first_nick = cnick->next_nick;
1152
1153 channel->nnicks--;
1154 cnick->nick[0] = '\0';
1155 // LDelRow(1, cidx, channel->chatter->nick_list);
1156 return;
1157 }
1158 }
1159
1160 bool
1161 irc_nick_is_in_channel(struct irc_channel *channel, char *nick)
1162 {
1163 struct irc_channel_nick *cnick, *pnick;
1164
1165 if (channel->first_nick == -1)
1166 return;
1167
1168 pnick = NULL;
1169 cnick = &channel->nicks[channel->first_nick];
1170 while (cnick) {
1171 if (strcmp(cnick->nick, nick) == 0)
1172 return true;
1173
1174 pnick = cnick;
1175 if (cnick->next_nick == -1)
1176 break;
1177 cnick = &channel->nicks[cnick->next_nick];
1178 }
1179
1180 return false;
1181 }
1182
1183 void
1184 irc_change_user_nick(struct irc_channel *channel, struct irc_user *user,
1185 char *nick)
1186 {
1187 struct irc_channel_nick *cnick;
1188 short n, flags;
1189
1190 if (channel && channel->first_nick > -1) {
1191 /* preserve flags */
1192 cnick = &channel->nicks[channel->first_nick];
1193 while (cnick) {
1194 if (strcmp(cnick->nick, user->nick) == 0) {
1195 flags = cnick->flags;
1196 break;
1197 }
1198 if (cnick->next_nick == -1)
1199 break;
1200 cnick = &channel->nicks[cnick->next_nick];
1201 }
1202
1203 irc_remove_nick_from_channel(channel, user->nick);
1204 irc_add_nick_to_channel(channel, nick, flags);
1205 }
1206
1207 if (strcmp(channel->connection->nick, user->nick) == 0) {
1208 xfree(&channel->connection->nick);
1209 channel->connection->nick = xstrdup(nick, "nick");
1210 chatter_update_titlebar(channel->chatter);
1211 }
1212 }
1213
1214 void
1215 irc_parse_channel_mode_change(struct irc_channel *channel, char *mode,
1216 char *args)
1217 {
1218 size_t len;
1219 struct irc_channel_nick *cnick;
1220 char *user;
1221 short n, j, flags;
1222 bool add = false;
1223
1224 len = strlen(mode);
1225
1226 /* mode:"+mvo-vo" args:"nick1 nick2 nick3 nick4" */
1227 for (n = 0; n < len; n++) {
1228 switch (mode[n]) {
1229 case '+':
1230 add = true;
1231 break;
1232 case '-':
1233 add = false;
1234 break;
1235 case 'v':
1236 case 'o':
1237 user = strsep(&args, " ");
1238 if (user == NULL)
1239 user = args;
1240
1241 cnick = &channel->nicks[channel->first_nick];
1242 while (cnick) {
1243 if (strcmp(cnick->nick, user) != 0) {
1244 if (cnick->next_nick == -1)
1245 break;
1246 cnick = &channel->nicks[cnick->next_nick];
1247 continue;
1248 }
1249
1250 flags = cnick->flags;
1251 if (mode[n] == 'o') {
1252 if (add)
1253 flags |= IRC_NICK_FLAG_OP;
1254 else
1255 flags &= ~IRC_NICK_FLAG_OP;
1256 } else if (mode[n] == 'v') {
1257 if (add)
1258 flags |= IRC_NICK_FLAG_VOICE;
1259 else
1260 flags &= ~IRC_NICK_FLAG_VOICE;
1261 }
1262
1263 irc_remove_nick_from_channel(channel, cnick->nick);
1264 /* cnick is probably invalid now */
1265 irc_add_nick_to_channel(channel, user, flags);
1266 break;
1267 }
1268
1269 break;
1270 default:
1271 /* some other channel mode */
1272 break;
1273 }
1274 }
1275 }