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 | } |