1 /* $Id: icb.c,v 1.3 2015/08/21 19:01:12 dhartmei Exp $ */
4 * Copyright (c) 2023 Omar Polo <op@openbsd.org>
5 * Copyright (c) 2003-2004 Daniel Hartmeier
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
12 * - Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * - Redistributions in binary form must reproduce the above
15 * copyright notice, this list of conditions and the following
16 * disclaimer in the documentation and/or other materials provided
17 * with the distribution.
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
24 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
25 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
29 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
42 extern void icbirc_quit(void);
43 extern int sync_write(int, const char *, int);
45 static unsigned char icb_args(const char *, unsigned char, char [255][255]);
46 static void icb_cmd(const char *, unsigned char, int, int);
47 static void icb_ico(int, const char *);
48 static void icb_iwl(int, const char *, const char *, long,
49 long, const char *, const char *);
50 static void icb_send_hw(int, const char *);
54 static char icb_protolevel[256];
55 static char icb_hostid[256];
56 static char icb_serverid[256];
57 static char icb_moderator[256];
58 enum { imode_none, imode_list, imode_names, imode_whois, imode_who };
59 static int imode = imode_none;
60 static char icurgroup[256];
61 static char igroup[256];
62 static char inick[256];
63 static char ihostmask[256];
67 * A single ICB packet consists of a length byte, a command byte and
68 * variable arguments. The length includes command and arguments, but
69 * not the length byte itself. Since length is at most 255, the entire
70 * packet is at most 256 bytes long.
72 * icb_recv() gets passed read(2) chunks and assembles a complete packet
73 * (including the length byte) in cmd. Once complete, the packet is
74 * passed to icb_cmd() without the length byte. Hence, arguments to
75 * icb_cmd() are at most 255 bytes long.
77 * icb_cmd() skips the command byte and passes only the variable
78 * arguments to icb_args(). Hence, arguments to icb_args() are at most
81 * Variable arguments consist of zero or more strings separated by
82 * \001 characters. The strings need not be null-terminated and may
83 * be empty. Hence, there can be at most 255 strings and a string can
84 * be at most 254 bytes long. icb_args() fills the array argument,
85 * null-terminating each argument.
87 * This (together with the comments below) should be convincing proof
88 * that the char [255][255] as well as the unsigned char variables
91 * Further argument parsing in icb_cmd() and icb_ico() relies on the
92 * fact that any argument can be at most 255 bytes long (including
95 * The icb_send_*() functions may get arbitrarily long arguments from
96 * IRC, they may generate packets of at most 256 bytes size. Overlong
97 * arguments are truncated, except for open and personal messages,
98 * which are split across multiple packets, if needed (generating
99 * separate messages on ICB).
101 * The ICB protocol definition is not very clear about null-termination
102 * of arguments for packets generated by the client. Without any
103 * termination, at least one common server implementation shows a
104 * buffer re-use bug. Terminating all arguments, however, causes
105 * another server implementation to refuse certain commands. The
106 * best approach seems to be to null-terminate only the last
107 * argument. Where the code below violates that rule, that was done
108 * intentionally after testing.
113 scan(const char **s, char *d, size_t siz, const char *skip, const char *term)
115 while (**s && strchr(skip, **s) != NULL)
117 while (**s && strchr(term, **s) == NULL) {
131 memset(icb_protolevel, 0, sizeof(icb_protolevel));
132 memset(icb_hostid, 0, sizeof(icb_hostid));
133 memset(icb_serverid, 0, sizeof(icb_serverid));
134 memset(icb_moderator, 0, sizeof(icb_moderator));
136 memset(icurgroup, 0, sizeof(icurgroup));
137 memset(igroup, 0, sizeof(igroup));
138 memset(inick, 0, sizeof(inick));
139 memset(ihostmask, 0, sizeof(ihostmask));
144 icb_recv(const char *buf, unsigned len, int fd, int server_fd)
146 static unsigned char cmd[256];
151 /* 0 < cmd[0] <= 255 */
154 /* off > 0, 0 < cmd[0] <= 255 */
155 while (len > 0 && (off - 1) < cmd[0]) {
159 /* len == 0 || (off - 1) == cmd[0] */
160 if ((off - 1) == cmd[0]) {
161 icb_cmd(cmd + 1, off - 1 /* <= 255 */, fd, server_fd);
168 icb_args(const char *data, unsigned char len, char args[255][255])
170 unsigned char i = 0, j = 0, k = 0;
174 /* 0 <= i, j, k < 255 */
175 if (data[i] == '\001') {
178 } else if (data[i] == '\r' || data[i] == '\n')
181 args[j][k++] = data[i];
188 for (i = j; i < 255; ++i)
194 icb_cmd(const char *cmd, unsigned char len, int fd, int server_fd)
197 const char *a = args[1];
205 i = icb_args(cmd + 1, len - 1 /* < 255 */, args);
208 case 'a': /* Login OK */
209 irc_send_code(fd, icb_hostid, irc_nick, "001",
210 "Welcome to icbirc %s", irc_nick);
211 irc_send_code(fd, icb_hostid, irc_nick, "002",
212 "Your host is %s running %s protocol %s",
213 icb_hostid, icb_serverid, icb_protolevel);
214 /* some clients really want to see a MOTD */
215 irc_send_code(fd, icb_hostid, irc_nick, "375",
216 "ICB server: %s", icb_serverid);
217 irc_send_code(fd, icb_hostid, irc_nick, "376",
221 case 'b': /* Open Message */
222 if (!in_irc_channel) {
223 irc_send_join(fd, irc_nick, irc_channel);
224 icb_send_names(server_fd, irc_channel);
226 irc_send_msg(fd, args[0], irc_channel, args[1]);
228 case 'c': /* Personal Message */
229 irc_send_msg(fd, args[0], irc_nick, args[1]);
231 case 'd': /* Status Message */
232 if (!strcmp(args[0], "Status") && !strncmp(args[1],
233 "You are now in group ", 21)) {
235 irc_send_part(fd, irc_nick, irc_channel);
236 irc_channel[0] = '#';
238 scan(&a, irc_channel + 1, sizeof(irc_channel) - 1,
240 irc_send_join(fd, irc_nick, irc_channel);
241 icb_send_names(server_fd, irc_channel);
242 } else if (!strcmp(args[0], "Arrive") ||
243 !strcmp(args[0], "Sign-on")) {
244 char nick[256], host[256];
246 scan(&a, nick, sizeof(nick), " ", " ");
247 scan(&a, host, sizeof(host), " (", ")");
248 snprintf(s, sizeof(s), "%s!%s", nick, host);
249 irc_send_join(fd, s, irc_channel);
250 } else if (!strcmp(args[0], "Depart")) {
251 char nick[256], host[256];
253 scan(&a, nick, sizeof(nick), " ", " ");
254 scan(&a, host, sizeof(host), " (", ")");
255 snprintf(s, sizeof(s), "%s!%s", nick, host);
256 irc_send_part(fd, s, irc_channel);
257 } else if (!strcmp(args[0], "Sign-off")) {
258 char nick[256], host[256], reason[256];
260 scan(&a, nick, sizeof(nick), " ", " ");
261 scan(&a, host, sizeof(host), " (", ")");
262 scan(&a, reason, sizeof(reason), " )", "");
263 if (strlen(reason) > 0 &&
264 reason[strlen(reason) - 1] == '.')
265 reason[strlen(reason) - 1] = 0;
266 snprintf(s, sizeof(s), ":%s!%s QUIT :%s\r\n",
268 sync_write(fd, s, strlen(s));
269 } else if (!strcmp(args[0], "Name")) {
270 char old_nick[256], new_nick[256];
272 scan(&a, old_nick, sizeof(old_nick), " ", " ");
273 if (strncmp(a, " changed nickname to ", 21))
276 scan(&a, new_nick, sizeof(new_nick), " ", " ");
277 snprintf(s, sizeof(s), ":%s NICK :%s\r\n",
279 sync_write(fd, s, strlen(s));
280 if (!strcmp(old_nick, irc_nick))
281 strlcpy(irc_nick, new_nick,
283 } else if (!strcmp(args[0], "Topic")) {
284 char nick[256], topic[256];
286 scan(&a, nick, sizeof(nick), " ", " ");
287 if (strncmp(a, " changed the topic to \"", 23))
290 scan(&a, topic, sizeof(topic), "", "\"");
291 snprintf(s, sizeof(s), ":%s TOPIC %s :%s\r\n",
292 nick, irc_channel, topic);
293 sync_write(fd, s, strlen(s));
294 } else if (!strcmp(args[0], "Pass")) {
295 char old_mod[256], new_mod[256];
297 scan(&a, old_mod, sizeof(old_mod), " ", " ");
298 if (!strncmp(a, " has passed moderation to ", 26)) {
300 scan(&a, new_mod, sizeof(new_mod), " ", " ");
301 snprintf(s, sizeof(s),
302 ":%s MODE %s -o+o %s %s\r\n",
303 old_mod, irc_channel, old_mod, new_mod);
304 } else if (!strcmp(a, " is now mod.")) {
305 snprintf(s, sizeof(s),
306 ":%s MODE %s +o %s\r\n",
307 icb_hostid, irc_channel, old_mod);
310 sync_write(fd, s, strlen(s));
311 strlcpy(icb_moderator, new_mod, sizeof(icb_moderator));
312 } else if (!strcmp(args[0], "Boot")) {
315 scan(&a, nick, sizeof(nick), " ", " ");
316 if (strcmp(a, " was booted."))
318 snprintf(s, sizeof(s), ":%s KICK %s %s :booted\r\n",
319 icb_moderator, irc_channel, nick);
320 sync_write(fd, s, strlen(s));
322 irc_send_notice(fd, "ICB Status Message: %s: %s",
325 case 'e': /* Error Message */
326 irc_send_notice(fd, "ICB Error Message: %s", args[0]);
328 case 'f': /* Important Message */
329 irc_send_notice(fd, "ICB Important Message: %s: %s",
333 irc_send_notice(fd, "ICB Exit");
334 warnx("server Exit");
337 case 'i': /* Command Output */
338 if (!strcmp(args[0], "co")) {
339 for (j = 1; j < i; ++j)
340 icb_ico(fd, args[j]);
341 } else if (!strcmp(args[0], "wl")) {
342 icb_iwl(fd, args[1], args[2], atol(args[3]),
343 atol(args[5]), args[6], args[7]);
344 } else if (!strcmp(args[0], "wh")) {
345 /* display whois header, deprecated */
347 irc_send_notice(fd, "ICB Command Output: %s: %u args",
350 case 'j': /* Protocol */
351 strlcpy(icb_protolevel, args[0], sizeof(icb_protolevel));
352 strlcpy(icb_hostid, args[1], sizeof(icb_hostid));
353 strlcpy(icb_serverid, args[2], sizeof(icb_serverid));
356 irc_send_notice(fd, "ICB Beep from %s", args[0]);
359 irc_send_notice(fd, "ICB Ping '%s'", args[0]);
362 irc_send_notice(fd, "ICB Pong '%s'", args[0]);
364 case 'n': /* No-op */
365 irc_send_notice(fd, "ICB No-op");
368 irc_send_notice(fd, "ICB unknown command %d: %u args",
374 icb_iwl(int fd, const char *flags, const char *nick, long idle,
375 long signon, const char *ident, const char *host)
378 int chanop = strchr(flags, 'm') != NULL;
380 if (imode == imode_whois && !strcmp(nick, inick)) {
381 snprintf(s, sizeof(s), ":%s 311 %s %s %s %s * :\r\n",
382 icb_hostid, irc_nick, nick, ident, host);
383 sync_write(fd, s, strlen(s));
385 snprintf(s, sizeof(s), ":%s 319 %s %s :%s%s\r\n",
386 icb_hostid, irc_nick, nick, chanop ? "@" : "",
388 sync_write(fd, s, strlen(s));
390 snprintf(s, sizeof(s), ":%s 312 %s %s %s :\r\n",
391 icb_hostid, irc_nick, nick, icb_hostid);
392 sync_write(fd, s, strlen(s));
393 snprintf(s, sizeof(s), ":%s 317 %s %s %ld %ld :seconds idle, "
395 icb_hostid, irc_nick, nick, idle, signon);
396 sync_write(fd, s, strlen(s));
397 snprintf(s, sizeof(s), ":%s 318 %s %s :End of /WHOIS list.\r\n",
398 icb_hostid, irc_nick, nick);
399 sync_write(fd, s, strlen(s));
400 } else if (imode == imode_names && !strcmp(icurgroup, igroup)) {
401 snprintf(s, sizeof(s), ":%s 353 %s @ %s :%s%s \r\n",
402 icb_hostid, irc_nick, icurgroup, chanop ? "@" : "", nick);
403 sync_write(fd, s, strlen(s));
404 snprintf(s, sizeof(s), ":%s 352 %s %s %s %s %s %s H :5 %s\r\n",
405 icb_hostid, irc_nick, icurgroup, nick, host, icb_hostid,
407 sync_write(fd, s, strlen(s));
408 } else if (imode == imode_who) {
411 if (ihostmask[0] == '#')
412 match = !strcmp(icurgroup, ihostmask);
416 snprintf(hostmask, sizeof(hostmask), "%s!%s@%s",
418 match = strstr(hostmask, ihostmask) != NULL;
421 snprintf(s, sizeof(s), ":%s 352 %s %s %s %s %s %s "
423 icb_hostid, irc_nick, icurgroup, nick, host,
424 icb_hostid, nick, ident);
425 sync_write(fd, s, strlen(s));
429 if (chanop && !strcmp(icurgroup, irc_channel))
430 strlcpy(icb_moderator, nick, sizeof(icb_moderator));
434 icb_ico(int fd, const char *arg)
438 if (!strncmp(arg, "Group: ", 7)) {
445 while (*arg && *arg != ' ')
448 strlcpy(icurgroup, group, sizeof(icurgroup));
449 topic = strstr(arg, "Topic: ");
454 if (imode == imode_list) {
455 snprintf(s, sizeof(s), ":%s 322 %s %s 1 :%s\r\n",
456 icb_hostid, irc_nick, group, topic);
457 sync_write(fd, s, strlen(s));
458 } else if (imode == imode_names &&
459 !strcmp(icurgroup, igroup)) {
460 snprintf(s, sizeof(s), ":%s 332 %s %s :%s\r\n",
461 icb_hostid, irc_nick, icurgroup, topic);
462 sync_write(fd, s, strlen(s));
464 } else if (!strncmp(arg, "Total: ", 7)) {
465 if (imode == imode_list) {
466 snprintf(s, sizeof(s), ":%s 323 %s :End of /LIST\r\n",
467 icb_hostid, irc_nick);
468 sync_write(fd, s, strlen(s));
469 } else if (imode == imode_names) {
470 snprintf(s, sizeof(s), ":%s 366 %s %s :End of "
472 icb_hostid, irc_nick, igroup);
473 sync_write(fd, s, strlen(s));
474 } else if (imode == imode_who) {
475 snprintf(s, sizeof(s), ":%s 315 %s %s :End of "
477 icb_hostid, irc_nick, ihostmask);
478 sync_write(fd, s, strlen(s));
481 } else if (strcmp(arg, " "))
482 irc_send_notice(fd, "*** Unknown ico: %s", arg);
485 #define MAX_MSG_SIZE 246
488 icb_send_login(int fd, const char *nick, const char *ident, const char *group)
492 const char *login_cmd = "login";
495 while (*ident && off < MAX_MSG_SIZE)
496 cmd[off++] = *ident++;
498 while (*nick && off < MAX_MSG_SIZE)
499 cmd[off++] = *nick++;
501 while (*group && off < MAX_MSG_SIZE)
502 cmd[off++] = *group++;
505 cmd[off++] = *login_cmd++;
510 sync_write(fd, cmd, off);
514 icb_send_openmsg(int fd, const char *msg)
516 unsigned char cmd[256];
522 while (*msg && off < MAX_MSG_SIZE)
526 /* cmd[0] <= MAX_MSG_SIZE */
527 sync_write(fd, cmd, off);
532 icb_send_privmsg(int fd, const char *nick, const char *msg)
534 unsigned char cmd[256];
538 const char *n = nick;
544 while (*n && off < MAX_MSG_SIZE)
547 while (*msg && off < MAX_MSG_SIZE)
551 /* cmd[0] <= MAX_MSG_SIZE */
552 sync_write(fd, cmd, off);
557 icb_send_group(int fd, const char *group)
565 while (*group && off < MAX_MSG_SIZE)
566 cmd[off++] = *group++;
569 sync_write(fd, cmd, off);
573 icb_send_hw(int fd, const char *arg)
582 while (*arg && off < MAX_MSG_SIZE)
586 sync_write(fd, cmd, off);
590 icb_send_list(int fd)
592 if (imode != imode_none)
595 icb_send_hw(fd, "-g");
599 icb_send_names(int fd, const char *group)
601 if (imode != imode_none)
604 strlcpy(igroup, group, sizeof(igroup));
609 icb_send_whois(int fd, const char *nick)
611 if (imode != imode_none)
614 strlcpy(inick, nick, sizeof(inick));
619 icb_send_who(int fd, const char *hostmask)
621 if (imode != imode_none)
624 strlcpy(ihostmask, hostmask, sizeof(ihostmask));
629 icb_send_pass(int fd, const char *nick)
633 const char *pass_cmd = "pass";
637 cmd[off++] = *pass_cmd++;
639 while (*nick && off < MAX_MSG_SIZE)
640 cmd[off++] = *nick++;
643 sync_write(fd, cmd, off);
647 icb_send_topic(int fd, const char *topic)
651 const char *topic_cmd = "topic";
655 cmd[off++] = *topic_cmd++;
657 while (*topic && off < MAX_MSG_SIZE)
658 cmd[off++] = *topic++;
661 sync_write(fd, cmd, off);
665 icb_send_boot(int fd, const char *nick)
669 const char *boot_cmd = "boot";
673 cmd[off++] = *boot_cmd++;
675 while (*nick && off < MAX_MSG_SIZE)
676 cmd[off++] = *nick++;
679 sync_write(fd, cmd, off);
683 icb_send_name(int fd, const char *nick)
687 const char *name_cmd = "name";
691 cmd[off++] = *name_cmd++;
693 while (*nick && off < MAX_MSG_SIZE)
694 cmd[off++] = *nick++;
697 sync_write(fd, cmd, off);
701 icb_send_raw(int fd, const char *data)
706 while (*data && off < MAX_MSG_SIZE) {
709 else if (*data == '\\')
717 sync_write(fd, cmd, off);
721 icb_send_noop(int fd)
729 sync_write(fd, cmd, off);