commit d23d2886439eadc49bfefc895d3c1d0640a9b217 from: Omar Polo date: Sun Jul 30 14:48:58 2023 UTC initial import of icbirc commit - /dev/null commit + d23d2886439eadc49bfefc895d3c1d0640a9b217 blob - /dev/null blob + 60b96d5b0dabc99f459a753f4d8454b4261b9931 (mode 644) --- /dev/null +++ Makefile @@ -0,0 +1,9 @@ +# $Id: Makefile,v 1.1.1.1 2007/01/11 15:55:53 dhartmei Exp $ + +PROG= icbirc +SRCS= icbirc.c icb.c irc.c +MAN= icbirc.8 + +CFLAGS+= -Wall -Werror -Wstrict-prototypes -ansi + +.include blob - /dev/null blob + b66554a807563240095345aeb2ca1bad88e38099 (mode 644) --- /dev/null +++ icb.c @@ -0,0 +1,729 @@ +/* $Id: icb.c,v 1.3 2015/08/21 19:01:12 dhartmei Exp $ */ + +/* + * Copyright (c) 2003-2004 Daniel Hartmeier + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +static const char rcsid[] = "$Id: icb.c,v 1.3 2015/08/21 19:01:12 dhartmei Exp $"; + +#include +#include +#include +#include "icb.h" +#include "irc.h" + +extern int sync_write(int, const char *, int); + +static unsigned char icb_args(const char *, unsigned char, char [255][255]); +static void icb_cmd(const char *, unsigned char, int, int); +static void icb_ico(int, const char *); +static void icb_iwl(int, const char *, const char *, long, + long, const char *, const char *); +static void icb_send_hw(int, const char *); + +extern int terminate_client; +int icb_logged_in = 0; + +static char icb_protolevel[256]; +static char icb_hostid[256]; +static char icb_serverid[256]; +static char icb_moderator[256]; +enum { imode_none, imode_list, imode_names, imode_whois, imode_who }; +static int imode = imode_none; +static char icurgroup[256]; +static char igroup[256]; +static char inick[256]; +static char ihostmask[256]; +static unsigned off; + +/* + * A single ICB packet consists of a length byte, a command byte and + * variable arguments. The length includes command and arguments, but + * not the length byte itself. Since length is at most 255, the entire + * packet is at most 256 bytes long. + * + * icb_recv() gets passed read(2) chunks and assembles a complete packet + * (including the length byte) in cmd. Once complete, the packet is + * passed to icb_cmd() without the length byte. Hence, arguments to + * icb_cmd() are at most 255 bytes long. + * + * icb_cmd() skips the command byte and passes only the variable + * arguments to icb_args(). Hence, arguments to icb_args() are at most + * 254 octects long. + * + * Variable arguments consist of zero or more strings separated by + * \001 characters. The strings need not be null-terminated and may + * be empty. Hence, there can be at most 255 strings and a string can + * be at most 254 bytes long. icb_args() fills the array argument, + * null-terminating each argument. + * + * This (together with the comments below) should be convincing proof + * that the char [255][255] as well as the unsigned char variables + * cannot overflow. + * + * Further argument parsing in icb_cmd() and icb_ico() relies on the + * fact that any argument can be at most 255 bytes long (including + * null-termination). + * + * The icb_send_*() functions may get arbitrarily long arguments from + * IRC, they may generate packets of at most 256 bytes size. Overlong + * arguments are truncated, except for open and personal messages, + * which are split across multiple packets, if needed (generating + * separate messages on ICB). + * + * The ICB protocol definition is not very clear about null-termination + * of arguments for packets generated by the client. Without any + * termination, at least one common server implementation shows a + * buffer re-use bug. Terminating all arguments, however, causes + * another server implementation to refuse certain commands. The + * best approach seems to be to null-terminate only the last + * argument. Where the code below violates that rule, that was done + * intentionally after testing. + * + */ + +void +scan(const char **s, char *d, size_t siz, const char *skip, const char *term) +{ + while (**s && strchr(skip, **s) != NULL) + (*s)++; + while (**s && strchr(term, **s) == NULL) { + if (siz > 1) { + *d++ = **s; + siz--; + } + (*s)++; + } + if (siz > 0) + *d = 0; +} + +void +icb_init(void) +{ + memset(icb_protolevel, 0, sizeof(icb_protolevel)); + memset(icb_hostid, 0, sizeof(icb_hostid)); + memset(icb_serverid, 0, sizeof(icb_serverid)); + memset(icb_moderator, 0, sizeof(icb_moderator)); + imode = imode_none; + memset(icurgroup, 0, sizeof(icurgroup)); + memset(igroup, 0, sizeof(igroup)); + memset(inick, 0, sizeof(inick)); + memset(ihostmask, 0, sizeof(ihostmask)); + off = 0; +} + +void +icb_recv(const char *buf, unsigned len, int fd, int server_fd) +{ + static unsigned char cmd[256]; + + while (len > 0) { + if (off == 0) { + cmd[off++] = *buf++; + /* 0 < cmd[0] <= 255 */ + len--; + } + /* off > 0, 0 < cmd[0] <= 255 */ + while (len > 0 && (off - 1) < cmd[0]) { + cmd[off++] = *buf++; + len--; + } + /* len == 0 || (off - 1) == cmd[0] */ + if ((off - 1) == cmd[0]) { + icb_cmd(cmd + 1, off - 1 /* <= 255 */, fd, server_fd); + off = 0; + } + } +} + +static unsigned char +icb_args(const char *data, unsigned char len, char args[255][255]) +{ + unsigned char i = 0, j = 0, k = 0; + + /* 0 < len < 255 */ + while (i < len) { + /* 0 <= i, j, k < 255 */ + if (data[i] == '\001') { + args[j++][k] = 0; + k = 0; + } else if (data[i] == '\r' || data[i] == '\n') + args[j][k++] = '?'; + else + args[j][k++] = data[i]; + i++; + } + /* i, j, k < 255 */ + if (k > 0) + args[j++][k] = 0; + /* j <= 255 */ + for (i = j; i < 255; ++i) + args[i][0] = 0; + return (j); +} + +static void +icb_cmd(const char *cmd, unsigned char len, int fd, int server_fd) +{ + char args[255][255]; + const char *a = args[1]; + unsigned char i, j; + char s[8192]; + + if (len == 0) + return; + + /* 0 < len <= 255 */ + i = icb_args(cmd + 1, len - 1 /* < 255 */, args); + /* 0 <= i <= 255 */ + switch (cmd[0]) { + case 'a': /* Login OK */ + irc_send_code(fd, icb_hostid, irc_nick, "001", + "Welcome to icbirc %s", irc_nick); + irc_send_code(fd, icb_hostid, irc_nick, "002", + "Your host is %s running %s protocol %s", + icb_hostid, icb_serverid, icb_protolevel); + /* some clients really want to see a MOTD */ + irc_send_code(fd, icb_hostid, irc_nick, "375", + "ICB server: %s", icb_serverid); + irc_send_code(fd, icb_hostid, irc_nick, "376", + "End of MOTD"); + icb_logged_in = 1; + break; + case 'b': /* Open Message */ + if (!in_irc_channel) { + irc_send_join(fd, irc_nick, irc_channel); + icb_send_names(server_fd, irc_channel); + } + irc_send_msg(fd, args[0], irc_channel, args[1]); + break; + case 'c': /* Personal Message */ + irc_send_msg(fd, args[0], irc_nick, args[1]); + break; + case 'd': /* Status Message */ + if (!strcmp(args[0], "Status") && !strncmp(args[1], + "You are now in group ", 21)) { + if (irc_channel[0]) + irc_send_part(fd, irc_nick, irc_channel); + irc_channel[0] = '#'; + a += 21; + scan(&a, irc_channel + 1, sizeof(irc_channel) - 1, + " ", " "); + irc_send_join(fd, irc_nick, irc_channel); + icb_send_names(server_fd, irc_channel); + } else if (!strcmp(args[0], "Arrive") || + !strcmp(args[0], "Sign-on")) { + char nick[256], host[256]; + + scan(&a, nick, sizeof(nick), " ", " "); + scan(&a, host, sizeof(host), " (", ")"); + snprintf(s, sizeof(s), "%s!%s", nick, host); + irc_send_join(fd, s, irc_channel); + } else if (!strcmp(args[0], "Depart")) { + char nick[256], host[256]; + + scan(&a, nick, sizeof(nick), " ", " "); + scan(&a, host, sizeof(host), " (", ")"); + snprintf(s, sizeof(s), "%s!%s", nick, host); + irc_send_part(fd, s, irc_channel); + } else if (!strcmp(args[0], "Sign-off")) { + char nick[256], host[256], reason[256]; + + scan(&a, nick, sizeof(nick), " ", " "); + scan(&a, host, sizeof(host), " (", ")"); + scan(&a, reason, sizeof(reason), " )", ""); + if (strlen(reason) > 0 && + reason[strlen(reason) - 1] == '.') + reason[strlen(reason) - 1] = 0; + snprintf(s, sizeof(s), ":%s!%s QUIT :%s\r\n", + nick, host, reason); + sync_write(fd, s, strlen(s)); + } else if (!strcmp(args[0], "Name")) { + char old_nick[256], new_nick[256]; + + scan(&a, old_nick, sizeof(old_nick), " ", " "); + if (strncmp(a, " changed nickname to ", 21)) + return; + a += 21; + scan(&a, new_nick, sizeof(new_nick), " ", " "); + snprintf(s, sizeof(s), ":%s NICK :%s\r\n", + old_nick, new_nick); + sync_write(fd, s, strlen(s)); + if (!strcmp(old_nick, irc_nick)) + strlcpy(irc_nick, new_nick, + sizeof(irc_nick)); + } else if (!strcmp(args[0], "Topic")) { + char nick[256], topic[256]; + + scan(&a, nick, sizeof(nick), " ", " "); + if (strncmp(a, " changed the topic to \"", 23)) + return; + a += 23; + scan(&a, topic, sizeof(topic), "", "\""); + snprintf(s, sizeof(s), ":%s TOPIC %s :%s\r\n", + nick, irc_channel, topic); + sync_write(fd, s, strlen(s)); + } else if (!strcmp(args[0], "Pass")) { + char old_mod[256], new_mod[256]; + + scan(&a, old_mod, sizeof(old_mod), " ", " "); + if (!strncmp(a, " has passed moderation to ", 26)) { + a += 26; + scan(&a, new_mod, sizeof(new_mod), " ", " "); + snprintf(s, sizeof(s), + ":%s MODE %s -o+o %s %s\r\n", + old_mod, irc_channel, old_mod, new_mod); + } else if (!strcmp(a, " is now mod.")) { + snprintf(s, sizeof(s), + ":%s MODE %s +o %s\r\n", + icb_hostid, irc_channel, old_mod); + } else + return; + sync_write(fd, s, strlen(s)); + strlcpy(icb_moderator, new_mod, sizeof(icb_moderator)); + } else if (!strcmp(args[0], "Boot")) { + char nick[256]; + + scan(&a, nick, sizeof(nick), " ", " "); + if (strcmp(a, " was booted.")) + return; + snprintf(s, sizeof(s), ":%s KICK %s %s :booted\r\n", + icb_moderator, irc_channel, nick); + sync_write(fd, s, strlen(s)); + } else + irc_send_notice(fd, "ICB Status Message: %s: %s", + args[0], args[1]); + break; + case 'e': /* Error Message */ + irc_send_notice(fd, "ICB Error Message: %s", args[0]); + break; + case 'f': /* Important Message */ + irc_send_notice(fd, "ICB Important Message: %s: %s", + args[0], args[1]); + break; + case 'g': /* Exit */ + irc_send_notice(fd, "ICB Exit"); + printf("server Exit\n"); + terminate_client = 1; + break; + case 'i': /* Command Output */ + if (!strcmp(args[0], "co")) { + for (j = 1; j < i; ++j) + icb_ico(fd, args[j]); + } else if (!strcmp(args[0], "wl")) { + icb_iwl(fd, args[1], args[2], atol(args[3]), + atol(args[5]), args[6], args[7]); + } else if (!strcmp(args[0], "wh")) { + /* display whois header, deprecated */ + } else + irc_send_notice(fd, "ICB Command Output: %s: %u args", + args[0], i - 1); + break; + case 'j': /* Protocol */ + strlcpy(icb_protolevel, args[0], sizeof(icb_protolevel)); + strlcpy(icb_hostid, args[1], sizeof(icb_hostid)); + strlcpy(icb_serverid, args[2], sizeof(icb_serverid)); + break; + case 'k': /* Beep */ + irc_send_notice(fd, "ICB Beep from %s", args[0]); + break; + case 'l': /* Ping */ + irc_send_notice(fd, "ICB Ping '%s'", args[0]); + break; + case 'm': /* Pong */ + irc_send_notice(fd, "ICB Pong '%s'", args[0]); + break; + case 'n': /* No-op */ + irc_send_notice(fd, "ICB No-op"); + break; + default: + irc_send_notice(fd, "ICB unknown command %d: %u args", + (int)cmd[0], i); + } +} + +static void +icb_iwl(int fd, const char *flags, const char *nick, long idle, + long signon, const char *ident, const char *host) +{ + char s[8192]; + int chanop = strchr(flags, 'm') != NULL; + + if (imode == imode_whois && !strcmp(nick, inick)) { + snprintf(s, sizeof(s), ":%s 311 %s %s %s %s * :\r\n", + icb_hostid, irc_nick, nick, ident, host); + sync_write(fd, s, strlen(s)); + if (icurgroup[0]) { + snprintf(s, sizeof(s), ":%s 319 %s %s :%s%s\r\n", + icb_hostid, irc_nick, nick, chanop ? "@" : "", + icurgroup); + sync_write(fd, s, strlen(s)); + } + snprintf(s, sizeof(s), ":%s 312 %s %s %s :\r\n", + icb_hostid, irc_nick, nick, icb_hostid); + sync_write(fd, s, strlen(s)); + snprintf(s, sizeof(s), ":%s 317 %s %s %ld %ld :seconds idle, " + "signon time\r\n", + icb_hostid, irc_nick, nick, idle, signon); + sync_write(fd, s, strlen(s)); + snprintf(s, sizeof(s), ":%s 318 %s %s :End of /WHOIS list.\r\n", + icb_hostid, irc_nick, nick); + sync_write(fd, s, strlen(s)); + } else if (imode == imode_names && !strcmp(icurgroup, igroup)) { + snprintf(s, sizeof(s), ":%s 353 %s @ %s :%s%s \r\n", + icb_hostid, irc_nick, icurgroup, chanop ? "@" : "", nick); + sync_write(fd, s, strlen(s)); + snprintf(s, sizeof(s), ":%s 352 %s %s %s %s %s %s H :5 %s\r\n", + icb_hostid, irc_nick, icurgroup, nick, host, icb_hostid, + nick, ident); + sync_write(fd, s, strlen(s)); + } else if (imode == imode_who) { + int match; + + if (ihostmask[0] == '#') + match = !strcmp(icurgroup, ihostmask); + else { + char hostmask[1024]; + + snprintf(hostmask, sizeof(hostmask), "%s!%s@%s", + nick, ident, host); + match = strstr(hostmask, ihostmask) != NULL; + } + if (match) { + snprintf(s, sizeof(s), ":%s 352 %s %s %s %s %s %s " + "H :5 %s\r\n", + icb_hostid, irc_nick, icurgroup, nick, host, + icb_hostid, nick, ident); + sync_write(fd, s, strlen(s)); + } + } + + if (chanop && !strcmp(icurgroup, irc_channel)) + strlcpy(icb_moderator, nick, sizeof(icb_moderator)); +} + +static void +icb_ico(int fd, const char *arg) +{ + char s[8192]; + + if (!strncmp(arg, "Group: ", 7)) { + char group[256]; + int i = 0; + char *topic; + + arg += 7; + group[i++] = '#'; + while (*arg && *arg != ' ') + group[i++] = *arg++; + group[i] = 0; + strlcpy(icurgroup, group, sizeof(icurgroup)); + topic = strstr(arg, "Topic: "); + if (topic == NULL) + topic = "(None)"; + else + topic += 7; + if (imode == imode_list) { + snprintf(s, sizeof(s), ":%s 322 %s %s 1 :%s\r\n", + icb_hostid, irc_nick, group, topic); + sync_write(fd, s, strlen(s)); + } else if (imode == imode_names && + !strcmp(icurgroup, igroup)) { + snprintf(s, sizeof(s), ":%s 332 %s %s :%s\r\n", + icb_hostid, irc_nick, icurgroup, topic); + sync_write(fd, s, strlen(s)); + } + } else if (!strncmp(arg, "Total: ", 7)) { + if (imode == imode_list) { + snprintf(s, sizeof(s), ":%s 323 %s :End of /LIST\r\n", + icb_hostid, irc_nick); + sync_write(fd, s, strlen(s)); + } else if (imode == imode_names) { + snprintf(s, sizeof(s), ":%s 366 %s %s :End of " + "/NAMES list.\r\n", + icb_hostid, irc_nick, igroup); + sync_write(fd, s, strlen(s)); + } else if (imode == imode_who) { + snprintf(s, sizeof(s), ":%s 315 %s %s :End of " + "/WHO list.\r\n", + icb_hostid, irc_nick, ihostmask); + sync_write(fd, s, strlen(s)); + } + imode = imode_none; + } else if (strcmp(arg, " ")) + irc_send_notice(fd, "*** Unknown ico: %s", arg); +} + +#define MAX_MSG_SIZE 246 + +void +icb_send_login(int fd, const char *nick, const char *ident, const char *group) +{ + char cmd[256]; + unsigned off = 1; + const char *login_cmd = "login"; + + cmd[off++] = 'a'; + while (*ident && off < MAX_MSG_SIZE) + cmd[off++] = *ident++; + cmd[off++] = '\001'; + while (*nick && off < MAX_MSG_SIZE) + cmd[off++] = *nick++; + cmd[off++] = '\001'; + while (*group && off < MAX_MSG_SIZE) + cmd[off++] = *group++; + cmd[off++] = '\001'; + while (*login_cmd) + cmd[off++] = *login_cmd++; + cmd[off++] = '\001'; + cmd[off++] = '\001'; + cmd[off++] = '\001'; + cmd[0] = off - 1; + sync_write(fd, cmd, off); +} + +void +icb_send_openmsg(int fd, const char *msg) +{ + unsigned char cmd[256]; + unsigned off; + + while (*msg) { + off = 1; + cmd[off++] = 'b'; + while (*msg && off < MAX_MSG_SIZE) + cmd[off++] = *msg++; + cmd[off++] = 0; + cmd[0] = off - 1; + /* cmd[0] <= MAX_MSG_SIZE */ + sync_write(fd, cmd, off); + } +} + +void +icb_send_privmsg(int fd, const char *nick, const char *msg) +{ + unsigned char cmd[256]; + unsigned off; + + while (*msg) { + const char *n = nick; + + off = 1; + cmd[off++] = 'h'; + cmd[off++] = 'm'; + cmd[off++] = '\001'; + while (*n && off < MAX_MSG_SIZE) + cmd[off++] = *n++; + cmd[off++] = ' '; + while (*msg && off < MAX_MSG_SIZE) + cmd[off++] = *msg++; + cmd[off++] = 0; + cmd[0] = off - 1; + /* cmd[0] <= MAX_MSG_SIZE */ + sync_write(fd, cmd, off); + } +} + +void +icb_send_group(int fd, const char *group) +{ + char cmd[256]; + unsigned off = 1; + + cmd[off++] = 'h'; + cmd[off++] = 'g'; + cmd[off++] = '\001'; + while (*group && off < MAX_MSG_SIZE) + cmd[off++] = *group++; + cmd[off++] = 0; + cmd[0] = off - 1; + sync_write(fd, cmd, off); +} + +static void +icb_send_hw(int fd, const char *arg) +{ + char cmd[256]; + unsigned off = 1; + + icurgroup[0] = 0; + cmd[off++] = 'h'; + cmd[off++] = 'w'; + cmd[off++] = '\001'; + while (*arg && off < MAX_MSG_SIZE) + cmd[off++] = *arg++; + cmd[off++] = 0; + cmd[0] = off - 1; + sync_write(fd, cmd, off); +} + +void +icb_send_list(int fd) +{ + if (imode != imode_none) + return; + imode = imode_list; + icb_send_hw(fd, "-g"); +} + +void +icb_send_names(int fd, const char *group) +{ + if (imode != imode_none) + return; + imode = imode_names; + strlcpy(igroup, group, sizeof(igroup)); + icb_send_hw(fd, ""); +} + +void +icb_send_whois(int fd, const char *nick) +{ + if (imode != imode_none) + return; + imode = imode_whois; + strlcpy(inick, nick, sizeof(inick)); + icb_send_hw(fd, ""); +} + +void +icb_send_who(int fd, const char *hostmask) +{ + if (imode != imode_none) + return; + imode = imode_who; + strlcpy(ihostmask, hostmask, sizeof(ihostmask)); + icb_send_hw(fd, ""); +} + +void +icb_send_pass(int fd, const char *nick) +{ + char cmd[256]; + unsigned off = 1; + const char *pass_cmd = "pass"; + + cmd[off++] = 'h'; + while (*pass_cmd) + cmd[off++] = *pass_cmd++; + cmd[off++] = '\001'; + while (*nick && off < MAX_MSG_SIZE) + cmd[off++] = *nick++; + cmd[off++] = 0; + cmd[0] = off - 1; + sync_write(fd, cmd, off); +} + +void +icb_send_topic(int fd, const char *topic) +{ + char cmd[256]; + unsigned off = 1; + const char *topic_cmd = "topic"; + + cmd[off++] = 'h'; + while (*topic_cmd) + cmd[off++] = *topic_cmd++; + cmd[off++] = '\001'; + while (*topic && off < MAX_MSG_SIZE) + cmd[off++] = *topic++; + cmd[off++] = 0; + cmd[0] = off - 1; + sync_write(fd, cmd, off); +} + +void +icb_send_boot(int fd, const char *nick) +{ + char cmd[256]; + unsigned off = 1; + const char *boot_cmd = "boot"; + + cmd[off++] = 'h'; + while (*boot_cmd) + cmd[off++] = *boot_cmd++; + cmd[off++] = '\001'; + while (*nick && off < MAX_MSG_SIZE) + cmd[off++] = *nick++; + cmd[off++] = 0; + cmd[0] = off - 1; + sync_write(fd, cmd, off); +} + +void +icb_send_name(int fd, const char *nick) +{ + char cmd[256]; + unsigned off = 1; + const char *name_cmd = "name"; + + cmd[off++] = 'h'; + while (*name_cmd) + cmd[off++] = *name_cmd++; + cmd[off++] = '\001'; + while (*nick && off < MAX_MSG_SIZE) + cmd[off++] = *nick++; + cmd[off++] = 0; + cmd[0] = off - 1; + sync_write(fd, cmd, off); +} + +void +icb_send_raw(int fd, const char *data) +{ + char cmd[256]; + unsigned off = 1; + + while (*data && off < MAX_MSG_SIZE) { + if (*data == ',') + cmd[off++] = '\001'; + else if (*data == '\\') + cmd[off++] = 0; + else + cmd[off++] = *data; + data++; + } + cmd[off++] = 0; + cmd[0] = off - 1; + sync_write(fd, cmd, off); +} + +void +icb_send_noop(int fd) +{ + char cmd[256]; + unsigned off = 1; + + cmd[off++] = 'n'; + cmd[off++] = 0; + cmd[0] = off - 1; + sync_write(fd, cmd, off); +} blob - /dev/null blob + 79d965f1b1df0381f85832f94d36f788ac8074a2 (mode 644) --- /dev/null +++ icb.h @@ -0,0 +1,25 @@ +/* $Id: icb.h,v 1.3 2015/08/21 19:01:12 dhartmei Exp $ */ + +#ifndef _ICB_H_ +#define _ICB_H_ + +void icb_init(void); +void icb_recv(const char *, unsigned, int, int); +void icb_send_login(int, const char *, const char *, const char *); +void icb_send_openmsg(int, const char *); +void icb_send_privmsg(int, const char *, const char *); +void icb_send_group(int, const char *); +void icb_send_list(int); +void icb_send_names(int, const char *); +void icb_send_whois(int, const char *); +void icb_send_who(int, const char *); +void icb_send_pass(int, const char *); +void icb_send_topic(int, const char *); +void icb_send_boot(int, const char *); +void icb_send_name(int, const char *); +void icb_send_raw(int, const char *); +void icb_send_noop(int); + +extern int icb_logged_in; + +#endif blob - /dev/null blob + 6e6429dbf444230ed5a4529cefc19d57c153ee45 (mode 644) --- /dev/null +++ icbirc.8 @@ -0,0 +1,186 @@ +.\" $Id: icbirc.8,v 1.1.1.1 2007/01/11 15:55:54 dhartmei Exp $ +.\" +.\" Copyright (c) 2003-2004 Daniel Hartmeier. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd Aug 6, 2003 +.Dt ICBIRC 8 +.Os +.Sh NAME +.Nm icbirc +.Nd proxy IRC client and ICB server +.Sh SYNOPSIS +.Nm icbirc +.Op Fl d +.Op Fl l Ar listen-address +.Op Fl p Ar listen-port +.Op Fl s Ar server-name +.Op Fl P Ar server-port +.Sh DESCRIPTION +.Nm +is a proxy that allows to connect an IRC client to an ICB server. +The proxy accepts client connections, connects to the server, and forwards +data between those two connections. +.Pp +Commands from the IRC client are translated to ICB commands and forwarded +to the ICB server. +Messages from the ICB server are translated to IRC messages and forwarded +to the IRC client. +.Pp +The options are as follows: +.Pp +.Bl -tag -width xlxlistenxaddress +.It Fl d +Do not daemonize (detach from controlling terminal) and produce debugging +output on stdout/stderr. +.It Fl l Ar listen-address +Bind to the specified address when listening for client connections. +If not specified, connections to any address are accepted. +.It Fl p Ar listen-port +Bind to the specified port when listening for client connections. +Defaults to 6667 when not specified. +.It Fl s Ar server-name +Hostname or numerical address of the ICB server to connect to. +.It Fl P Ar server-port +Port of the ICB server to connect to. +Defaults to 7326 when not specified. +.El +.Pp +Example: +.Bd -literal -offset indent +$ icbirc -s default.icb.net +.Ed +.Pp +IRC (Internet Relay Chat) and ICB (Internet Citizen's Band) are two separate +chat protocols. +ICB is an older and simpler protocol, basically a subset of IRC. +The two most significant differences (from the client's perspective) are: +.Pp +An ICB client can only join a single channel (called group). +Joining a second channel automatically parts the first channel. +.Pp +An ICB channel can only have a single operator (called moderator). +Giving operator status to a second client automatically removes +operator status from the first client. +.Pp +.Sh SUPPORTED COMMANDS +.Nm +supports the following IRC commands: +.Pp +.Bl -tag -width MODExoxnickx +.It PASS +Set the default group, used during login. +.It NICK +Set or change nickname. +.It USER +Supply additional user information (like ident), used during login. +.It LIST +List all groups. +.It WHOIS +Shows information about a user. +.It WHO +Lists matching users. +Arguments starting with '#' are interpreted as channel names +(listing all users in the specified channel), anything else +is used for a simple string search within users' 'nick!ident@host'. +.It JOIN +Join a group. +.It PRIVMSG +Send an open or personal message. +.It NOTICE +Same as PRIVMSG. +.It TOPIC +Set group topic. +.It KICK nick +Boot nick from group. +.It MODE +o nick +Pass moderation to nick. +.It QUIT +Close client and server connection, wait for next client connection. +.El +.Pp +Additionally, the command RAWICB can be used to send custom ICB +commands. +The proxy automatically prefixes the correct command length and +replaces commas with ICB argument separators. +For example: +.Bl -tag -width RAWICBxhmxnickxmsg +.It RAWICB hm,nick,msg +Send msg to nick. +.El +.Pp +.Sh SEE ALSO +.Rs +.%T Internet Relay Chat Protocol +.%O RFC 1459 +.Re +.Rs +.%T Internet Relay Chat: Client Protocol +.%O RFC 2812 +.Re +.Rs +.%T Internet Relay Chat: Channel Management +.%O RFC 2811 +.Re +.Rs +.%T ICB Protocol +.%O ftp://ftp.icb.net/pub/icb/src/icbd/Protocol.html +.Re +.Rs +.%T The History of ICB +.%O http://www.icb.net/history.html +.Re +.Rs +.%T General guide to Netiquette on ICB +.%O http://www.icb.net/_jrudd/icb/netiquette.html +.Re +.Sh HISTORY +The first version of +.Nm +was written in 2003. +.Sh AUTHORS +Daniel Hartmeier +.Aq daniel@benzedrine.cx +.Sh CAVEATS +ICB is not IRC. +Depending on the ICB community on a particular server, netiquette rules +vary greatly from common IRC rules (or lack thereof). +.Pp +Client scripts or other forms of automated client actions might generate +noise or violate ICB community policies, and lacking support for some +commands might confuse the script. +Clients should be properly configured and tested on a dedicated server +before connecting to a public server. +.Pp +In particular, WHOIS and WHO filtering is done on the proxy. Each such +request causes the proxy to fetch the entire user list from the ICB +server (there are no ICB commands that take filters), hence automatic +WHOIS requests from the IRC client can cause unwanted load on the ICB +server (turn off 'WHOIS on JOIN' in the IRC client, if enabled). +.Sh BUGS +On ICB, a moderator (channel operator) can leave the group (channel) and +rejoin later, preserving his status, as compared to IRC, where the channel +would be left operator-less in this case. +The proxy does not currently detect the operator status on rejoin in this +case, and the IRC client will (temporarily) show the channel op-less. +.Pp +IPv6 is not supported yet. blob - /dev/null blob + b4ae91067e5b2bac72f261099da7074c04436d52 (mode 644) --- /dev/null +++ icbirc.c @@ -0,0 +1,367 @@ +/* $Id: icbirc.c,v 1.3 2016/04/25 08:17:01 dhartmei Exp $ */ + +/* + * Copyright (c) 2003-2004 Daniel Hartmeier + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +static const char rcsid[] = "$Id: icbirc.c,v 1.3 2016/04/25 08:17:01 dhartmei Exp $"; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "icb.h" +#include "irc.h" + +int sync_write(int, const char *, int); +static void usage(void); +static void handle_client(int); + +int terminate_client; +static struct sockaddr_in sa_connect; + +static void +usage(void) +{ + extern char *__progname; + + fprintf(stderr, "usage: %s [-d] [-l address] [-p port] " + "-s server [-P port]\n", __progname); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + int debug = 0; + const char *addr_listen = NULL, *addr_connect = NULL; + unsigned port_listen = 6667, port_connect = 7326; + int ch; + int listen_fd = -1; + struct sockaddr_in sa; + socklen_t len; + int val; + + while ((ch = getopt(argc, argv, "dl:p:s:P:")) != -1) { + switch (ch) { + case 'd': + debug++; + break; + case 'l': + addr_listen = optarg; + break; + case 'p': + port_listen = atoi(optarg); + break; + case 's': + addr_connect = optarg; + break; + case 'P': + port_connect = atoi(optarg); + break; + default: + usage(); + } + } + argc -= optind; + if (argc || addr_connect == NULL) + usage(); + + memset(&sa_connect, 0, sizeof(sa_connect)); + sa_connect.sin_family = AF_INET; + sa_connect.sin_addr.s_addr = inet_addr(addr_connect); + if (sa_connect.sin_addr.s_addr == INADDR_NONE) { + struct hostent *h; + + if ((h = gethostbyname(addr_connect)) == NULL) { + fprintf(stderr, "gethostbyname: %s: %s\n", + addr_connect, hstrerror(h_errno)); + goto error; + } + memcpy(&sa_connect.sin_addr.s_addr, h->h_addr, + sizeof(in_addr_t)); + } + sa_connect.sin_port = htons(port_connect); + + if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + perror("socket"); + goto error; + } + + if (fcntl(listen_fd, F_SETFL, fcntl(listen_fd, F_GETFL) | + O_NONBLOCK)) { + perror("fcntl"); + goto error; + } + + val = 1; + if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, + (const char *)&val, sizeof(val))) { + perror("setsockopt"); + goto error; + } + + memset(&sa, 0, sizeof(sa)); + sa.sin_family = AF_INET; + if (addr_listen != NULL) + sa.sin_addr.s_addr = inet_addr(addr_listen); + else + sa.sin_addr.s_addr = INADDR_ANY; + sa.sin_port = htons(port_listen); + if (bind(listen_fd, (const struct sockaddr *)&sa, sizeof(sa))) { + fprintf(stderr, "bind %s:%u: %s\n", inet_ntoa(sa.sin_addr), + ntohs(sa.sin_port), strerror(errno)); + goto error; + } + + if (listen(listen_fd, 1)) { + perror("listen"); + goto error; + } + + if (!debug && daemon(0, 0)) { + perror("daemon"); + goto error; + } + signal(SIGPIPE, SIG_IGN); + +#ifdef __OpenBSD__ + if (pledge("stdio inet dns", NULL) == -1) { + perror("pledge"); + goto error; + } +#endif /* __OpenBSD__ */ + + /* handle incoming client connections */ + while (1) { + fd_set readfds; + struct timeval tv; + int r; + + FD_ZERO(&readfds); + FD_SET(listen_fd, &readfds); + memset(&tv, 0, sizeof(tv)); + tv.tv_sec = 10; + r = select(listen_fd + 1, &readfds, NULL, NULL, &tv); + if (r < 0) { + if (errno != EINTR) { + perror("select"); + break; + } + continue; + } + if (r > 0 && FD_ISSET(listen_fd, &readfds)) { + int client_fd; + + memset(&sa, 0, sizeof(sa)); + len = sizeof(sa); + client_fd = accept(listen_fd, + (struct sockaddr *)&sa, &len); + if (client_fd < 0) { + if (errno != ECONNABORTED) { + perror("accept"); + break; + } + continue; + } + printf("client connection from %s:%i\n", + inet_ntoa(sa.sin_addr), ntohs(sa.sin_port)); + handle_client(client_fd); + close(client_fd); + } + } + + close(listen_fd); + return (0); + +error: + if (listen_fd) + close(listen_fd); + + return (1); +} + +static void +handle_client(int client_fd) +{ + int server_fd; + int max_fd; + time_t t; + unsigned long bytes_in, bytes_out; + + t = time(NULL); + bytes_in = bytes_out = 0; + irc_pass[0] = irc_nick[0] = irc_ident[0] = irc_channel[0] = 0; + icb_logged_in = 0; + terminate_client = 1; + + printf("connecting to server %s:%u\n", + inet_ntoa(sa_connect.sin_addr), ntohs(sa_connect.sin_port)); + irc_send_notice(client_fd, "*** Connecting to server %s:%u", + inet_ntoa(sa_connect.sin_addr), ntohs(sa_connect.sin_port)); + if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + perror("socket"); + goto done; + } + if (connect(server_fd, (struct sockaddr *)&sa_connect, + sizeof(sa_connect))) { + perror("connect"); + irc_send_notice(client_fd, "*** Error: connect: %s", + strerror(errno)); + close(server_fd); + goto done; + } + + if (fcntl(server_fd, F_SETFL, fcntl(server_fd, F_GETFL) | O_NONBLOCK) || + fcntl(client_fd, F_SETFL, fcntl(client_fd, F_GETFL) | O_NONBLOCK)) { + perror("fcntl"); + goto done; + } + + if (client_fd > server_fd) + max_fd = client_fd; + else + max_fd = server_fd; + + irc_send_notice(client_fd, "*** Connected"); + terminate_client = 0; + icb_init(); + while (!terminate_client) { + fd_set readfds; + struct timeval tv; + int r; + + FD_ZERO(&readfds); + FD_SET(server_fd, &readfds); + FD_SET(client_fd, &readfds); + memset(&tv, 0, sizeof(tv)); + tv.tv_sec = 10; + r = select(max_fd + 1, &readfds, NULL, NULL, &tv); + if (r < 0) { + if (errno != EINTR) { + perror("select"); + break; + } + continue; + } + if (r > 0) { + char buf[65535]; + int len; + + if (FD_ISSET(server_fd, &readfds)) { + len = read(server_fd, buf, sizeof(buf)); + if (len < 0) { + if (errno == EINTR) + continue; + perror("read"); + len = 0; + } + if (len == 0) { + printf("connection closed by server\n"); + irc_send_notice(client_fd, + "*** Connection closed by server"); + break; + } + icb_recv(buf, len, client_fd, server_fd); + bytes_in += len; + } + if (FD_ISSET(client_fd, &readfds)) { + len = read(client_fd, buf, sizeof(buf)); + if (len < 0) { + if (errno == EINTR) + continue; + perror("read"); + len = 0; + } + if (len == 0) { + printf("connection closed by client\n"); + break; + } + irc_recv(buf, len, client_fd, server_fd); + bytes_out += len; + } + } + } + +done: + if (server_fd >= 0) + close(server_fd); + printf("(%lu seconds, %lu:%lu bytes)\n", + (unsigned long)(time(NULL) - t), bytes_out, bytes_in); + if (terminate_client) + irc_send_notice(client_fd, "*** Closing connection " + "(%u seconds, %lu:%lu bytes)", + time(NULL) - t, bytes_out, bytes_in); +} + +int +sync_write(int fd, const char *buf, int len) +{ + int off = 0; + + while (len > off) { + fd_set writefds; + struct timeval tv; + int r; + + FD_ZERO(&writefds); + FD_SET(fd, &writefds); + memset(&tv, 0, sizeof(tv)); + tv.tv_sec = 10; + r = select(fd + 1, NULL, &writefds, NULL, &tv); + if (r < 0) { + if (errno != EINTR) { + perror("select"); + return (1); + } + continue; + } + if (r > 0 && FD_ISSET(fd, &writefds)) { + r = write(fd, buf + off, len - off); + if (r < 0) { + perror("write"); + return (1); + } + off += r; + } + } + return (0); +} blob - /dev/null blob + 239b7eb7dbd7951cbe0d6d9d72b3b3c7bcc6c515 (mode 644) --- /dev/null +++ irc.c @@ -0,0 +1,275 @@ +/* $Id: irc.c,v 1.2 2015/08/20 17:29:16 dhartmei Exp $ */ + +/* + * Copyright (c) 2003-2004 Daniel Hartmeier + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + */ + +static const char rcsid[] = "$Id: irc.c,v 1.2 2015/08/20 17:29:16 dhartmei Exp $"; + +#include +#include +#include +#include "irc.h" +#include "icb.h" + +extern void scan(const char **, char *, size_t, const char *, + const char *); +extern int sync_write(int, const char *, int); + +static void irc_cmd(const char *, int, int); + +static void irc_send_pong(int, const char *); + +extern int terminate_client; + +char irc_pass[256]; +char irc_ident[256]; +char irc_nick[256]; +char irc_channel[256]; +int in_irc_channel; + +/* + * irc_recv() receives read(2) chunks and assembles complete lines, which are + * passed to irc_cmd(). Overlong lines are truncated after 65kB. + * + * XXX: argument checking is not as strong as for ICB (trusting the client) + * + */ + +void +irc_recv(const char *buf, unsigned len, int client_fd, int server_fd) +{ + static char cmd[65535]; + static unsigned off = 0; + + while (len > 0) { + while (len > 0 && off < (sizeof(cmd) - 1) && *buf != '\n') { + cmd[off++] = *buf++; + len--; + } + if (off == (sizeof(cmd) - 1)) + while (len > 0 && *buf != '\n') { + buf++; + len--; + } + /* off <= sizeof(cmd) - 1 */ + if (len > 0 && *buf == '\n') { + buf++; + len--; + if (off > 0 && cmd[off - 1] == '\r') + cmd[off - 1] = 0; + else + cmd[off] = 0; + irc_cmd(cmd, client_fd, server_fd); + off = 0; + } + } +} + +static void +irc_cmd(const char *cmd, int client_fd, int server_fd) +{ + if (!strncasecmp(cmd, "PASS ", 5)) { + cmd += 5; + scan(&cmd, irc_pass, sizeof(irc_pass), " ", " "); + } else if (!strncasecmp(cmd, "USER ", 5)) { + cmd += 5; + scan(&cmd, irc_ident, sizeof(irc_ident), " ", " "); + if (!icb_logged_in && irc_nick[0] && irc_ident[0]) + icb_send_login(server_fd, irc_nick, + irc_ident, irc_pass); + } else if (!strncasecmp(cmd, "NICK ", 5)) { + cmd += 5; + scan(&cmd, irc_nick, sizeof(irc_nick), " ", " "); + if (icb_logged_in) + icb_send_name(server_fd, irc_nick); + else if (irc_nick[0] && irc_ident[0]) + icb_send_login(server_fd, irc_nick, + irc_ident, irc_pass); + } else if (!strncasecmp(cmd, "JOIN ", 5)) { + char group[128]; + + cmd += 5; + if (*cmd == '#') + cmd++; + scan(&cmd, group, sizeof(group), " ", " "); + icb_send_group(server_fd, group); + } else if (!strncasecmp(cmd, "PART ", 5)) { + in_irc_channel = 0; + } else if (!strncasecmp(cmd, "PRIVMSG ", 8) || + !strncasecmp(cmd, "NOTICE ", 7)) { + char dst[128]; + char msg[8192]; + unsigned i, j; + + cmd += strncasecmp(cmd, "NOTICE ", 7) ? 8 : 7; + scan(&cmd, dst, sizeof(dst), " ", " "); + scan(&cmd, msg, sizeof(msg), " ", ""); + /* strip \001 found in CTCP messages */ + i = 0; + while (msg[i]) { + if (msg[i] == '\001') { + for (j = i; msg[j + 1]; ++j) + msg[j] = msg[j + 1]; + msg[j] = 0; + } else + i++; + } + if (!strcmp(dst, irc_channel)) + icb_send_openmsg(server_fd, + msg + (msg[0] == ':' ? 1 : 0)); + else + icb_send_privmsg(server_fd, dst, + msg + (msg[0] == ':' ? 1 : 0)); + } else if (!strncasecmp(cmd, "MODE ", 5)) { + cmd += 5; + if (!strcmp(cmd, irc_channel)) + icb_send_names(server_fd, irc_channel); + else if (!strncmp(cmd, irc_channel, strlen(irc_channel))) { + cmd += strlen(irc_channel); + if (strncmp(cmd, " +o ", 4)) { + printf("irc_cmd: invalid MODE args '%s'\n", + cmd); + return; + } + cmd += 4; + icb_send_pass(server_fd, cmd); + } + } else if (!strncasecmp(cmd, "TOPIC ", 6)) { + cmd += 6; + if (strncmp(cmd, irc_channel, strlen(irc_channel))) { + printf("irc_cmd: invalid TOPIC args '%s'\n", cmd); + return; + } + cmd += strlen(irc_channel); + if (strncmp(cmd, " :", 2)) { + printf("irc_cmd: invalid TOPIC args '%s'\n", cmd); + return; + } + cmd += 2; + icb_send_topic(server_fd, cmd); + } else if (!strcasecmp(cmd, "LIST")) { + icb_send_list(server_fd); + } else if (!strncasecmp(cmd, "NAMES ", 6)) { + cmd += 6; + icb_send_names(server_fd, cmd); + } else if (!strncasecmp(cmd, "WHOIS ", 6)) { + cmd += 6; + icb_send_whois(server_fd, cmd); + } else if (!strncasecmp(cmd, "WHO ", 4)) { + cmd += 4; + icb_send_who(server_fd, cmd); + } else if (!strncasecmp(cmd, "KICK ", 5)) { + char channel[128], nick[128]; + + cmd += 5; + scan(&cmd, channel, sizeof(channel), " ", " "); + scan(&cmd, nick, sizeof(nick), " ", " "); + if (strcmp(channel, irc_channel)) { + printf("irc_cmd: invalid KICK args '%s'\n", cmd); + return; + } + icb_send_boot(server_fd, nick); + } else if (!strncasecmp(cmd, "PING ", 5)) { + icb_send_noop(server_fd); + cmd += 5; + irc_send_pong(client_fd, cmd); + } else if (!strncasecmp(cmd, "RAWICB ", 7)) { + cmd += 7; + icb_send_raw(server_fd, cmd); + } else if (!strncasecmp(cmd, "QUIT ", 5)) { + printf("client QUIT\n"); + terminate_client = 1; + } else + printf("irc_cmd: unknown cmd '%s'\n", cmd); +} + +void +irc_send_notice(int fd, const char *format, ...) +{ + char cmd[8192], msg[8192]; + va_list ap; + + va_start(ap, format); + vsnprintf(msg, sizeof(msg), format, ap); + va_end(ap); + snprintf(cmd, sizeof(cmd), "NOTICE %s\r\n", msg); + sync_write(fd, cmd, strlen(cmd)); +} + +void +irc_send_code(int fd, const char *from, const char *nick, const char *code, + const char *format, ...) +{ + char cmd[8192], msg[8192]; + va_list ap; + + va_start(ap, format); + vsnprintf(msg, sizeof(msg), format, ap); + va_end(ap); + snprintf(cmd, sizeof(cmd), ":%s %s %s :%s\r\n", from, code, nick, msg); + sync_write(fd, cmd, strlen(cmd)); +} + +void +irc_send_msg(int fd, const char *src, const char *dst, const char *msg) +{ + char cmd[8192]; + + snprintf(cmd, sizeof(cmd), ":%s PRIVMSG %s :%s\r\n", src, dst, msg); + sync_write(fd, cmd, strlen(cmd)); +} + +void +irc_send_join(int fd, const char *src, const char *dst) +{ + char cmd[8192]; + + snprintf(cmd, sizeof(cmd), ":%s JOIN :%s\r\n", src, dst); + sync_write(fd, cmd, strlen(cmd)); + in_irc_channel = 1; +} + +void +irc_send_part(int fd, const char *src, const char *dst) +{ + char cmd[8192]; + + snprintf(cmd, sizeof(cmd), ":%s PART :%s\r\n", src, dst); + sync_write(fd, cmd, strlen(cmd)); +} + +void +irc_send_pong(int fd, const char *daemon) +{ + char cmd[8192]; + + snprintf(cmd, sizeof(cmd), "PONG %s\r\n", daemon); + sync_write(fd, cmd, strlen(cmd)); +} blob - /dev/null blob + 8313a463aa395cb49d1353a08b3775463c6c7469 (mode 644) --- /dev/null +++ irc.h @@ -0,0 +1,20 @@ +/* $Id: irc.h,v 1.1.1.1 2007/01/11 15:55:54 dhartmei Exp $ */ + +#ifndef _IRC_H_ +#define _IRC_H_ + +void irc_recv(const char *, unsigned, int, int); +void irc_send_notice(int, const char *, ...); +void irc_send_code(int, const char *, const char *, const char *, + const char *, ...); +void irc_send_msg(int, const char *, const char *, const char *); +void irc_send_join(int, const char *, const char *); +void irc_send_part(int, const char *, const char *); + +extern char irc_pass[256]; +extern char irc_ident[256]; +extern char irc_nick[256]; +extern char irc_channel[256]; +extern int in_irc_channel; + +#endif