AmendHub

Download

jcs

/

wallops

/

irc.c

 

(View History)

jcs   focusable: Let each dictate the minimum sleep time for WaitNextEvent Latest amendment: 28 on 2022-02-10

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