AmendHub

Download

jcs

/

wallops

/

irc.c

 

(View History)

jcs   irc: Update titlebar after we change to nick_ Latest amendment: 37 on 2022-09-06

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