commit - /dev/null
commit + d23d2886439eadc49bfefc895d3c1d0640a9b217
blob - /dev/null
blob + 60b96d5b0dabc99f459a753f4d8454b4261b9931 (mode 644)
--- /dev/null
+++ Makefile
+# $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 <bsd.prog.mk>
blob - /dev/null
blob + b66554a807563240095345aeb2ca1bad88e38099 (mode 644)
--- /dev/null
+++ icb.c
+/* $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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#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
+/* $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
+.\" $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
+/* $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 <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#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
+/* $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 <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#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
+/* $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