Commit Diff


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 <bsd.prog.mk>
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 <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
@@ -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 <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
@@ -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 <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
@@ -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