ii

git clone https://orangeshoelaces.net/git/ii.git

704ab925e92097778821d36954699f665028254d

Author: Hiltjo Posthuma on 06/02/2017

Committer: Hiltjo Posthuma on 06/02/2017

merge my changes to master

Special thanks to:
- Wolfgang Corcoran-Mathe for sending patches and testing.
- Nico Golde for maintaining ii for so long.

Stats

.gitignore |    2 +
CHANGES    |   31 +
FAQ        |   19 +-
LICENSE    |    5 +-
Makefile   |   65 +-
README     |   51 +-
arg.h      |   49 +
config.mk  |   14 +-
ii.1       |   61 +-
ii.c       | 1045 +++++---
query.sh   |   29 -
strlcpy.c  |   32 +
12 files changed, 938 insertions(+), 465 deletions(-)

Patch

diff --git a/.gitignore b/.gitignore
index 5761abc..43d0692 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,3 @@
+ii
+*.a
 *.o
diff --git a/CHANGES b/CHANGES
index 3baaca2..4ebd6bb 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,34 @@
+1.8 (2017-??-??, unreleased):
+    - prevent nick collisions by only setting the nick after the server
+      accepted it and print a message about change to server log.
+    - remove query.sh.
+    - add OpenBSD pledge(2) support.
+    - fix QUIT message string.
+    - raw IRC output to stdout.
+    - add quit command (/q [string]).
+    - write timestamp in outfile as UNIX timestamp (UTC+0).
+    - server host (-s) doesn't default to irc.freenode and is now required.
+    - add option (-u) to connect directly to a UNIX domain socket, this
+      is useful for tunneling connections.
+    - remove "in" file when leaving a channel (enabled commented code).
+    - remove "in" files on exit.
+    - use IRC_MAX (512), instead of PIPE_BUF (4096) on most systems.
+      PIPE_BUF is guaranteed to be atleast 512 bytes for atomic operations.
+    - Makefile: always be verbose.
+    - use C99 and -D_DEFAULT_SOURCE
+    - remove obsolete gethostbyname, use getaddrinfo.
+    - IPV6 support.
+    - timeout now uses separate exit statuscode 2.
+    - cleanup:
+        - use arg.h for command-line option parsing.
+        - use sbase util functions (estrtol, eprintf).
+        - use and import OpenBSD strlcpy().
+        - man page typos.
+    - style:
+        - linewrap to 79 characters.
+        - coding style fixes.
+        - non-roman numerals for LICENSE period.
+
 1.7 (2013-01-05)
     - -k now specifies an environment variable that contains the
       server key. This behaviour has been changed in order to not
diff --git a/FAQ b/FAQ
index 4aefa6a..e5dd7a3 100644
--- a/FAQ
+++ b/FAQ
@@ -6,6 +6,7 @@ Where is IRC command xy (ignore etc.)?
 ii is for advanced users, please use standard tools like awk, sed and grep for
 this. This can be done easily and will not bloat the code.
 
+
 Where is a graphical interface?
 -------------------------------
 Basically ii follows the UNIX philosophy so it is only file based. But it
@@ -14,12 +15,14 @@ the FIFOs and output files. Feel free to implement or wait until we have done
 this. Actually I use ii in combination with vim, multitail and screen and it works
 like a charm.
 
+
 Which commands are supported?
 -----------------------------
 j (join or msg), t (topic), a (away), n (nick), l (leave). The missing are
 obsolete or can be easily used by typing the IRC commands itself (i.e. /WHO
 instead of /who).
 
+
 How can I recognize queries?
 ----------------------------
 ii itself doesn't support this but the queries.sh script is an example
@@ -28,9 +31,23 @@ To get an instant notice of a new file other mechanisms like inotify/dnotify
 could be used as well but I was too lazy to try it out since the script
 is enough for me.
 
+
 What other fancy stuff can I do with ii?
 ----------------------------------------
 It is very easy to write irc bots in ii:
-tail -f \#/out | while read foo; do name=echo $foo | awk '{print $2}' | sed 's,<\\(.*\\)>,\\1,'; if 0 -eq expr $RANDOM % 10 then echo "$name: WHAT??" ; fi; done
+
+	#!/bin/sh
+	chan="#yourchannel"
+	tail -f "${chan}/out" | while read -r line; do
+		cmd=$(printf '%s\n' "$line" | cut -d ' ' -f 4-)
+		name=$(printf '%s\n' "$line" | cut -d ' ' -f 3 | tr -d '<>')
+		if [ "$cmd" = "!rand" ]; then
+			r="$RANDOM"
+			if expr "$r" "%" "10"; then
+				echo "$name: $r" >> "${chan}/in"
+			fi
+		fi
+	done
+
 This will just spam a channel but think about using nagios2irc or you can
 use ii to generate channel stats. Your imagination should be boundless.
diff --git a/LICENSE b/LICENSE
index dd0b65c..9055f40 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,7 +1,8 @@
 MIT/X Consortium License
 
-(C)opyright MMV-MMVI Anselm R. Garbe <garbeam@wmii.de>
-(C)opyright MMV-MMVIII Nico Golde <nico at ngolde dot de>
+(C)opyright 2005-2006 Anselm R. Garbe <garbeam@wmii.de>
+(C)opyright 2005-2011 Nico Golde <nico at ngolde dot de>
+(C)opyright 2014-2017 Hiltjo Posthuma <hiltjo at codemadness dot org>
 
 Permission is hereby granted, free of charge, to any person obtaining a
 copy of this software and associated documentation files (the "Software"),
diff --git a/Makefile b/Makefile
index 0bd4ca5..1abe2ab 100644
--- a/Makefile
+++ b/Makefile
@@ -1,55 +1,48 @@
-# ii - irc it - simple but flexible IRC client
-#   (C)opyright MMV-MMVI Anselm R. Garbe
-#   (C)opyright MMV-MMVII Anselm R. Garbe, Nico Golde
-
+# See LICENSE file for copyright and license details.
 include config.mk
 
-SRC      = ii.c
-OBJ      = ${SRC:.c=.o}
+SRC = ii.c strlcpy.c
+OBJ = ${SRC:.c=.o}
 
-all: options ii
-	@echo built ii
+all: ii
 
 options:
 	@echo ii build options:
-	@echo "LIBS     = ${LIBS}"
-	@echo "INCLUDES = ${INCLUDES}"
 	@echo "CFLAGS   = ${CFLAGS}"
 	@echo "LDFLAGS  = ${LDFLAGS}"
 	@echo "CC       = ${CC}"
 
-.c.o:
-	@echo CC $<
-	@${CC} -c ${CFLAGS} $<
+.o:
+	$(LD) -o $@ $< $(LDFLAGS)
 
-dist: clean
-	@mkdir -p ii-${VERSION}
-	@cp -R query.sh Makefile CHANGES README FAQ LICENSE config.mk ii.c ii.1 ii-${VERSION}
-	@tar -cf ii-${VERSION}.tar ii-${VERSION}
-	@gzip ii-${VERSION}.tar
-	@rm -rf ii-${VERSION}
-	@echo created distribution ii-${VERSION}.tar.gz
+.c.o:
+	$(CC) -c -o $@ $< $(CFLAGS)
 
 ii: ${OBJ}
-	@echo LD $@
-	@${CC} -o $@ ${OBJ} ${LDFLAGS}
+	${CC} -o $@ ${OBJ} ${LDFLAGS}
 
 install: all
-	@mkdir -p ${DESTDIR}${DOCDIR}
-	@mkdir -p ${DESTDIR}${BINDIR}
-	@mkdir -p ${DESTDIR}${MAN1DIR}
-
-	@install -d ${DESTDIR}${BINDIR} ${DESTDIR}${MAN1DIR}
-	@install -m 644 CHANGES README query.sh FAQ LICENSE ${DESTDIR}${DOCDIR}
-	@install -m 775 ii ${DESTDIR}${BINDIR}
-	@install -m 444 ii.1 ${DESTDIR}${MAN1DIR}
-	@echo "installed ii"
+	mkdir -p ${DESTDIR}${DOCDIR}
+	mkdir -p ${DESTDIR}${BINDIR}
+	mkdir -p ${DESTDIR}${MAN1DIR}
+	install -d ${DESTDIR}${BINDIR} ${DESTDIR}${MAN1DIR}
+	install -m 644 CHANGES README FAQ LICENSE ${DESTDIR}${DOCDIR}
+	install -m 775 ii ${DESTDIR}${BINDIR}
+	sed "s/VERSION/${VERSION}/g" < ii.1 > ${DESTDIR}${MAN1DIR}/ii.1
+	chmod 644 ${DESTDIR}${MAN1DIR}/ii.1
 
 uninstall: all
-	@rm -f ${DESTDIR}${MAN1DIR}/ii.1
-	@rm -rf ${DESTDIR}${DOCDIR}
-	@rm -f ${DESTDIR}${BINDIR}/ii
-	@echo "uninstalled ii"
+	rm -f ${DESTDIR}${MAN1DIR}/ii.1 \
+		${DESTDIR}${BINDIR}/ii
+	rm -rf ${DESTDIR}${DOCDIR}
+
+dist: clean
+	mkdir -p ii-${VERSION}
+	cp -R Makefile CHANGES README FAQ LICENSE strlcpy.c arg.h \
+		config.mk ii.c ii.1 ii-${VERSION}
+	tar -cf ii-${VERSION}.tar ii-${VERSION}
+	gzip ii-${VERSION}.tar
+	rm -rf ii-${VERSION}
 
 clean:
-	rm -f ii *~ *.o *core *.tar.gz
+	rm -f ii *.o
diff --git a/README b/README
index 1f2c84b..a26a4e8 100644
--- a/README
+++ b/README
@@ -1,7 +1,7 @@
 Abstract
 --------
 ii is a minimalistic FIFO and filesystem based IRC client.  It creates an irc
-directory tree with server, channel and nick name directories.  In every
+directory tree with server, channel and nick name directories. In every
 directory a FIFO file (in) and normal file (out) is placed.
 
 The in file is used to communicate with the servers and the out files include
@@ -13,6 +13,7 @@ standard command line tools.  For example if you want to join a channel just do
 echo "/j #channel" > in and ii creates a new channel directory with in and out
 file.
 
+
 Installation
 ------------
 Edit config.mk to match your local setup. ii is installed into
@@ -23,6 +24,7 @@ necessary as root):
 
     $ make clean install
 
+
 Running ii
 ------------
 Simply invoke the 'ii' command with required arguments
@@ -41,19 +43,50 @@ Thanks to Matthias Kopfermann for this hint.
 You can find an example of how this nested environment could look like on:
 http://nion.modprobe.de/blog/archives/440-Using-the-ii-irc-client.html
 
+
+SSL/TLS support
+---------------
+
+Below is an example using OpenBSD relayd which sets up a TCP TLS relay
+connection on localhost. A similar setup can be accomplished using
+stunnel or netcat with TLS support. This also works for other programs
+that don't support TLS natively.
+
+/etc/relayd.conf:
+
+	table <freenode> { irc.freenode.net }
+	table <oftc> { irc.oftc.net }
+
+	protocol "irctls" {
+		tcp { nodelay, sack }
+	}
+
+	relay "freenode" {
+		listen on 127.0.0.1 port 6668
+		protocol "irctls"
+		forward with tls to <freenode> port 6697
+	}
+
+	relay "oftc" {
+		listen on 127.0.0.1 port 6669
+		protocol "irctls"
+		forward with tls to <oftc> port 6697
+	}
+
+
+Then connect:
+
+	./irc -n nick -u name -s 127.0.0.1 -p 6668
+	./irc -n nick -u name -s 127.0.0.1 -p 6669
+
+
 Configuration
 -------------
 No configuration is needed.
 
+
 Changelog
 ---------
 Since I missed the chance to add a proper changelog right from the beginning,
-please have a look at the commit messages on http://code.suckless.org/hg/ii/
+please have a look at the commit messages on http://git.suckless.org/ii/
 they are fairly descriptive on releases prior to 1.2.
-
-Contact
--------
-If you want to contact the developers just write a mail to
-ii (at) modprobe (dot) de
-
--- Nico Golde, Anselm R. Garbe
diff --git a/arg.h b/arg.h
new file mode 100644
index 0000000..e94e02b
--- /dev/null
+++ b/arg.h
@@ -0,0 +1,49 @@
+/*
+ * Copy me if you can.
+ * by 20h
+ */
+
+#ifndef ARG_H__
+#define ARG_H__
+
+extern char *argv0;
+
+/* use main(int argc, char *argv[]) */
+#define ARGBEGIN	for (argv0 = *argv, argv++, argc--;\
+					argv[0] && argv[0][0] == '-'\
+					&& argv[0][1];\
+					argc--, argv++) {\
+				char argc_;\
+				char **argv_;\
+				int brk_;\
+				if (argv[0][1] == '-' && argv[0][2] == '\0') {\
+					argv++;\
+					argc--;\
+					break;\
+				}\
+				for (brk_ = 0, argv[0]++, argv_ = argv;\
+						argv[0][0] && !brk_;\
+						argv[0]++) {\
+					if (argv_ != argv)\
+						break;\
+					argc_ = argv[0][0];\
+					switch (argc_)
+
+#define ARGEND			}\
+			}
+
+#define ARGC()		argc_
+
+#define EARGF(x)	((argv[0][1] == '\0' && argv[1] == NULL)?\
+				((x), abort(), (char *)0) :\
+				(brk_ = 1, (argv[0][1] != '\0')?\
+					(&argv[0][1]) :\
+					(argc--, argv++, argv[0])))
+
+#define ARGF()		((argv[0][1] == '\0' && argv[1] == NULL)?\
+				(char *)0 :\
+				(brk_ = 1, (argv[0][1] != '\0')?\
+					(&argv[0][1]) :\
+					(argc--, argv++, argv[0])))
+
+#endif
diff --git a/config.mk b/config.mk
index b5bc34f..821e8f3 100644
--- a/config.mk
+++ b/config.mk
@@ -1,4 +1,5 @@
 # Customize to fit your system
+VERSION = 1.7
 
 # paths
 PREFIX      = /usr/local
@@ -12,17 +13,18 @@ DESTDIR     =
 
 INCDIR      = ${PREFIX}/include
 LIBDIR      = ${PREFIX}/lib
-VERSION     = 1.7
 
 # includes and libs
 INCLUDES    = -I. -I${INCDIR} -I/usr/include
 LIBS        = -L${LIBDIR} -L/usr/lib -lc
-# uncomment and comment other variables for compiling on Solaris
-#LIBS = -L${LIBDIR} -L/usr/lib -lc -lsocket -lnsl
-#CFLAGS      = -g ${INCLUDES} -DVERSION=\"${VERSION}\"
 
 # compiler
 CC          = cc
-CFLAGS      = -g -O0 -W -Wall ${INCLUDES} -DVERSION=\"${VERSION}\"
-LDFLAGS     = ${LIBS}
 
+# debug
+#CFLAGS      = -g -O0 -pedantic -Wall ${INCLUDES} -DVERSION=\"${VERSION}\" -std=c99 -D_DEFAULT_SOURCE
+#LDFLAGS     = ${LIBS}
+
+# release
+CFLAGS     = -Os ${INCLUDES} -DVERSION=\"${VERSION}\" -std=c99 -D_DEFAULT_SOURCE
+LDFLAGS    = -s
diff --git a/ii.1 b/ii.1
index 11e4e2a..8e06af7 100644
--- a/ii.1
+++ b/ii.1
@@ -1,10 +1,6 @@
-.de FN
-\fI\|\\$1\|\fP\\$2
-..
-.TH ii 1
+.TH II 1 ii\-VERSION
 .SH NAME
 ii \- irc it or irc improved
-
 .SH DESCRIPTION
 .B ii
 is a minimalistic FIFO and filesystem based IRC client.
@@ -21,23 +17,27 @@ For example if you will join a channel just do echo "/j #channel" > in
 and ii creates a new channel directory with in and out file.
 .SH SYNOPSIS
 .B ii
-.RB [ \-s
-.IR servername ]
+.RB < \-s
+.IR servername >
 .RB [ \-p
 .IR port ]
 .RB [ \-k
-.IR environment variable ]
+.IR "environment variable" ]
 .RB [ \-i
 .IR prefix ]
 .RB [ \-n
 .IR nickname ]
 .RB [ \-f
 .IR realname ]
-
+.RB < \-u
+.IR sockname >
 .SH OPTIONS
 .TP
 .BI \-s " servername"
-lets you override the default servername (irc.freenode.net)
+server to connect to, for example: irc.freenode.net
+.TP
+.BI \-u " sockname"
+connect to a UNIX domain socket instead of directly to a server.
 .TP
 .BI \-p " port"
 lets you override the default port (6667)
@@ -54,44 +54,47 @@ lets you override the default nick ($USER)
 .TP
 .BI \-f " realname"
 lets you specify your real name associated with your nick
-
 .SH DIRECTORIES
 .TP
-.FN ~/irc
+.B ~/irc
 In this directory the irc tree will be created. In this directory you
 will find a directory for your server (default: irc.freenode.net) in
 which the FIFO and the output file will be stored.
 If you join a channel a new directory with the name of the channel
 will be created in the ~/irc/$servername/ directory.
-
 .SH COMMANDS
 .TP
-.FN /a " [<message>]"
+.BI /a " [<message>]"
 mark yourself as away
 .TP
-.FN /j " #channel/nickname [<message>]"
+.BI /j " #channel/nickname [<message>]"
 join a channel or open private conversation with user
 .TP
-.FN /l " #channel/nickname"
+.BI /l " [reason]"
 leave a channel or query
 .TP
-.FN /n " nick"
+.BI /n " nick"
 change the nick name
 .TP
-.FN /t " topic"
-set the topic of a channel
-.TP
-Everything which is not a command will simply be posted into the channel or to the server.
-So if you need /who just write /WHO as described in the RFC to the server in FIFO.
+.BI /q " [reason]"
+quit ii
 .TP
-.FN "out file usage"
-Write wrappers, pagers or use your tools of choice to display the out file contents (loco, multitail, etc.). 
+.BI /t " topic"
+set the topic of a channel
+.SH RAW COMMANDS
+.LP
+Everything which is not a command will be posted into the channel or to the server.
+So if you need /who just write /WHO as described in RFC#1459 to the server in FIFO.
+.SH SSL PROTOCOL SUPPORT
+.LP
+For TLS/SSL protocol support you can connect to a local tunnel, for example with stunnel or socat.
 .SH CONTACT
-.TP
-Write to ii (at) modprobe (dot) de for suggestions, fixes, 7|-|>< ;) etc.
+.LP
+Subscribe to the mailinglist and write to dev (at) suckless (dot) org for suggestions, fixes, etc.
 .SH AUTHORS
-Copyright \(co 2005-2006 by Anselm R. Garbe <garbeam (at) gmail (dot) com> and 
-Copyright \(co 2005-2008 by Nico Golde <nico (at) ngolde (dot) de>
+ii engineers, see LICENSE file
 .SH SEE ALSO
 .BR echo (1),
-.BR tail (1),
+.BR tail (1)
+.SH BUGS
+Please report them!
diff --git a/ii.c b/ii.c
index 5d57458..72e59b5 100644
--- a/ii.c
+++ b/ii.c
@@ -1,514 +1,853 @@
-/* (C)opyright MMV-MMVI Anselm R. Garbe <garbeam at gmail dot com>
- * (C)opyright MMV-MMXI Nico Golde <nico at ngolde dot de>
- * See LICENSE file for license details. */
+/* See LICENSE file for license details. */
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/un.h>
+
+#include <ctype.h>
 #include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
 #include <netdb.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/socket.h>
-#include <sys/select.h>
 #include <netinet/in.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
-#include <limits.h>
-#include <fcntl.h>
 #include <string.h>
-#include <pwd.h>
-#include <signal.h>
-#include <ctype.h>
 #include <time.h>
 #include <unistd.h>
 
-#define EXIT_TIMEOUT 2
+char *argv0;
+
+#include "arg.h"
+
+#undef strlcpy
+size_t strlcpy(char *, const char *, size_t);
+
+#define IRC_CHANNEL_MAX   200
+#define IRC_MSG_MAX       512 /* quaranteed to be <= than PIPE_BUF */
+#define PING_TIMEOUT      300
 
-#ifndef PIPE_BUF /* For OS that doesn't includes PIPE_BUF in limits.h, FreeBSD? */
-#define PIPE_BUF _POSIX_PIPE_BUF
-#endif
-#define PING_TIMEOUT 300
-#define SERVER_PORT 6667
 enum { TOK_NICKSRV = 0, TOK_USER, TOK_CMD, TOK_CHAN, TOK_ARG, TOK_TEXT, TOK_LAST };
 
 typedef struct Channel Channel;
 struct Channel {
-	int fd;
-	char *name;
+	int fdin;
+	char name[IRC_CHANNEL_MAX]; /* channel name (normalized) */
+	char inpath[PATH_MAX];      /* input path */
+	char outpath[PATH_MAX];     /* output path */
 	Channel *next;
 };
 
-static int irc;
-static time_t last_response;
+static Channel * channel_add(const char *);
+static Channel * channel_find(const char *);
+static Channel * channel_join(const char *);
+static void      channel_leave(Channel *);
+static Channel * channel_new(const char *);
+static void      channel_normalize_name(char *);
+static void      channel_normalize_path(char *);
+static int       channel_open(Channel *);
+static void      channel_print(Channel *, const char *);
+static int       channel_reopen(Channel *);
+static void      channel_rm(Channel *);
+static void      create_dirtree(const char *);
+static void      create_filepath(char *, size_t, const char *, const char *, const char *);
+static void      ewritestr(int, const char *);
+static void      handle_channels_input(int, Channel *);
+static void      handle_server_output(int);
+static int       isnumeric(const char *);
+static void      loginkey(int, const char *);
+static void      loginuser(int, const char *, const char *);
+static void      proc_channels_input(int, Channel *, char *);
+static void      proc_channels_privmsg(int, Channel *, char *);
+static void      proc_server_cmd(int, char *);
+static int       read_line(int, char *, size_t);
+static void      run(int, const char *);
+static void      setup(void);
+static void      sighandler(int);
+static int       tcpopen(const char *, const char *);
+static size_t    tokenize(char **, size_t, char *, int);
+static int       udsopen(const char *);
+static void      usage(void);
+
+static int      isrunning = 1;
+static time_t   last_response = 0;
 static Channel *channels = NULL;
-static char *host = "irc.freenode.net";
-static char nick[32];			/* might change while running */
-static char _nick[32];			/* might change while running */
-static char path[_POSIX_PATH_MAX];
-static char message[PIPE_BUF]; /* message buf used for communication */
-
-static void usage() {
-	fputs("ii - irc it - " VERSION "\n"
-	      "(C)opyright MMV-MMVI Anselm R. Garbe\n"
-	      "(C)opyright MMV-MMXI Nico Golde\n"
-	      "usage: ii [-i <irc dir>] [-s <host>] [-p <port>]\n"
-	      "          [-n <nick>] [-k <password>] [-f <fullname>]\n", stderr);
-	exit(EXIT_FAILURE);
+static Channel *channelmaster = NULL;
+static char     nick[32];          /* active nickname at runtime */
+static char     _nick[32];         /* nickname at startup */
+static char     ircpath[PATH_MAX]; /* irc dir (-i) */
+static char     msg[IRC_MSG_MAX];  /* message buf used for communication */
+
+static void
+usage(void)
+{
+	fprintf(stderr, "usage: %s <-s host> [-i <irc dir>] [-p <port>] "
+	        "[-u <sockname>] [-n <nick>] [-k <password>] "
+	        "[-f <fullname>]\n", argv0);
+	exit(1);
 }
 
-static char *striplower(char *s) {
-	char *p = NULL;
-	for(p = s; p && *p; p++) {
-		if(*p == '/') *p = ',';
-		*p = tolower(*p);
+static void
+ewritestr(int fd, const char *s)
+{
+	size_t len, off = 0;
+	int w = -1;
+
+	len = strlen(s);
+	for (off = 0; off < len; off += w) {
+		if ((w = write(fd, s + off, len - off)) == -1)
+			break;
+		off += w;
+	}
+	if (w == -1) {
+		fprintf(stderr, "%s: write: %s\n", argv0, strerror(errno));
+		exit(1);
 	}
-	return s;
 }
 
-/* creates directories top-down, if necessary */
-static void create_dirtree(const char *dir) {
-	char tmp[256];
-	char *p = NULL;
+/* creates directories bottom-up, if necessary */
+static void
+create_dirtree(const char *dir)
+{
+	char tmp[PATH_MAX], *p;
+	struct stat st;
 	size_t len;
-	snprintf(tmp, sizeof(tmp),"%s",dir);
+
+	strlcpy(tmp, dir, sizeof(tmp));
 	len = strlen(tmp);
-	if(tmp[len - 1] == '/')
-		tmp[len - 1] = 0;
-	for(p = tmp + 1; *p; p++)
-		if(*p == '/') {
-			*p = 0;
-			mkdir(tmp, S_IRWXU);
-			*p = '/';
-		}
+	if (len > 0 && tmp[len - 1] == '/')
+		tmp[len - 1] = '\0';
+
+	if ((stat(tmp, &st) != -1) && S_ISDIR(st.st_mode))
+		return; /* dir exists */
+
+	for (p = tmp + 1; *p; p++) {
+		if (*p != '/')
+			continue;
+		*p = '\0';
+		mkdir(tmp, S_IRWXU);
+		*p = '/';
+	}
 	mkdir(tmp, S_IRWXU);
 }
 
-static int get_filepath(char *filepath, size_t len, char *channel, char *file) {
-	if(channel) {
-		if(!snprintf(filepath, len, "%s/%s", path, channel))
-			return 0;
+static void
+channel_normalize_path(char *s)
+{
+	for (; *s; s++) {
+		if (isalpha(*s))
+			*s = tolower(*s);
+		else if (!isdigit(*s) && !strchr(".#&", *s))
+			*s = '_';
+	}
+}
+
+static void
+channel_normalize_name(char *s)
+{
+	char *p;
+
+	while (*s == '&' || *s == '#')
+		s++;
+	for (p = s; *s; s++) {
+		if (!strchr(" ,&#\x07", *s)) {
+			*p = *s;
+			p++;
+		}
+	}
+	*p = '\0';
+}
+
+static void
+create_filepath(char *filepath, size_t len, const char *path,
+	const char *channel, const char *suffix)
+{
+	int r;
+
+	if (channel[0]) {
+		r = snprintf(filepath, len, "%s/%s", path, channel);
+		if (r < 0 || (size_t)r >= len)
+			goto error;
 		create_dirtree(filepath);
-		return snprintf(filepath, len, "%s/%s/%s", path, channel, file);
+		r = snprintf(filepath, len, "%s/%s/%s", path, channel, suffix);
+		if (r < 0 || (size_t)r >= len)
+			goto error;
+	} else {
+		r = snprintf(filepath, len, "%s/%s", path, suffix);
+		if (r < 0 || (size_t)r >= len)
+			goto error;
 	}
-	return snprintf(filepath, len, "%s/%s", path, file);
+	return;
+
+error:
+	fprintf(stderr, "%s: path to irc directory too long\n", argv0);
+	exit(1);
 }
 
-static void create_filepath(char *filepath, size_t len, char *channel, char *suffix) {
-	if(!get_filepath(filepath, len, striplower(channel), suffix)) {
-		fputs("ii: path to irc directory too long\n", stderr);
-		exit(EXIT_FAILURE);
+static int
+channel_open(Channel *c)
+{
+	int fd;
+	struct stat st;
+
+	/* make "in" fifo if it doesn't exist already. */
+	if (lstat(c->inpath, &st) != -1) {
+		if (!(st.st_mode & S_IFIFO))
+			return -1;
+	} else if (mkfifo(c->inpath, S_IRWXU)) {
+		return -1;
 	}
+	c->fdin = -1;
+	fd = open(c->inpath, O_RDONLY | O_NONBLOCK, 0);
+	if (fd == -1)
+		return -1;
+	c->fdin = fd;
+
+	return 0;
 }
 
-static int open_channel(char *name) {
-	static char infile[256];
-	create_filepath(infile, sizeof(infile), name, "in");
-	if(access(infile, F_OK) == -1)
-		mkfifo(infile, S_IRWXU);
-	return open(infile, O_RDONLY | O_NONBLOCK, 0);
+static int
+channel_reopen(Channel *c)
+{
+	if (c->fdin > 2) {
+		close(c->fdin);
+		c->fdin = -1;
+	}
+	return channel_open(c);
 }
 
-static void add_channel(char *cname) {
+static Channel *
+channel_new(const char *name)
+{
 	Channel *c;
-	int fd;
-	char *name = striplower(cname);
+	char channelpath[PATH_MAX];
+
+	strlcpy(channelpath, name, sizeof(channelpath));
+	channel_normalize_path(channelpath);
+
+	if (!(c = calloc(1, sizeof(Channel)))) {
+		fprintf(stderr, "%s: calloc: %s\n", argv0, strerror(errno));
+		exit(1);
+	}
+	c->next = NULL;
+	strlcpy(c->name, name, sizeof(c->name));
+	channel_normalize_name(c->name);
+
+	create_filepath(c->inpath, sizeof(c->inpath), ircpath,
+	                channelpath, "in");
+	create_filepath(c->outpath, sizeof(c->outpath), ircpath,
+	                channelpath, "out");
+	return c;
+}
 
-	for(c = channels; c; c = c->next)
-		if(!strcmp(name, c->name))
-			return; /* already handled */
+static Channel *
+channel_find(const char *name)
+{
+	Channel *c;
+	char chan[IRC_CHANNEL_MAX];
 
-	fd = open_channel(name);
-	if(fd == -1) {
-		printf("ii: exiting, cannot create in channel: %s\n", name);
-		exit(EXIT_FAILURE);
+	strlcpy(chan, name, sizeof(chan));
+	channel_normalize_name(chan);
+	for (c = channels; c; c = c->next) {
+		if (!strcmp(chan, c->name))
+			return c; /* already handled */
 	}
-	c = calloc(1, sizeof(Channel));
-	if(!c) {
-		perror("ii: cannot allocate memory");
-		exit(EXIT_FAILURE);
+	return NULL;
+}
+
+static Channel *
+channel_add(const char *name)
+{
+	Channel *c;
+
+	c = channel_new(name);
+	if (channel_open(c) == -1) {
+		fprintf(stderr, "%s: cannot create channel: %s: %s\n",
+		         argv0, name, strerror(errno));
+		free(c);
+		return NULL;
 	}
-	if(!channels) channels = c;
-	else {
+	if (!channels) {
+		channels = c;
+	} else {
 		c->next = channels;
 		channels = c;
 	}
-	c->fd = fd;
-	c->name = strdup(name);
+	return c;
 }
 
-static void rm_channel(Channel *c) {
+static Channel *
+channel_join(const char *name)
+{
+	Channel *c;
+
+	if (!(c = channel_find(name)))
+		c = channel_add(name);
+	return c;
+}
+
+static void
+channel_rm(Channel *c)
+{
 	Channel *p;
-	if(channels == c) channels = channels->next;
-	else {
-		for(p = channels; p && p->next != c; p = p->next);
-		if(p->next == c)
+
+	if (channels == c) {
+		channels = channels->next;
+	} else {
+		for (p = channels; p && p->next != c; p = p->next)
+			;
+		if (p && p->next == c)
 			p->next = c->next;
 	}
-	free(c->name);
 	free(c);
 }
 
-static void login(char *key, char *fullname) {
-	if(key) snprintf(message, PIPE_BUF,
-				"PASS %s\r\nNICK %s\r\nUSER %s localhost %s :%s\r\n", key,
-				nick, nick, host, fullname ? fullname : nick);
-	else snprintf(message, PIPE_BUF, "NICK %s\r\nUSER %s localhost %s :%s\r\n",
-				nick, nick, host, fullname ? fullname : nick);
-	write(irc, message, strlen(message));	/* login */
+static void
+channel_leave(Channel *c)
+{
+	if (c->fdin > 2) {
+		close(c->fdin);
+		c->fdin = -1;
+	}
+	/* remove "in" file on leaving the channel */
+	unlink(c->inpath);
+	channel_rm(c);
+}
+
+static void
+loginkey(int ircfd, const char *key)
+{
+	snprintf(msg, sizeof(msg), "PASS %s\r\n", key);
+	ewritestr(ircfd, msg);
+}
+
+static void
+loginuser(int ircfd, const char *host, const char *fullname)
+{
+	snprintf(msg, sizeof(msg), "NICK %s\r\nUSER %s localhost %s :%s\r\n",
+	         nick, nick, host, fullname);
+	puts(msg);
+	ewritestr(ircfd, msg);
 }
 
-static int tcpopen(unsigned short port) {
+static int
+udsopen(const char *uds)
+{
+	struct sockaddr_un sun;
+	size_t len;
 	int fd;
-	struct sockaddr_in sin;
-	struct hostent *hp = gethostbyname(host);
 
-	memset(&sin, 0, sizeof(struct sockaddr_in));
-	if(!hp) {
-		perror("ii: cannot retrieve host information");
-		exit(EXIT_FAILURE);
+	if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
+		fprintf(stderr, "%s: socket: %s\n", argv0, strerror(errno));
+		exit(1);
+	}
+
+	sun.sun_family = AF_UNIX;
+	if (strlcpy(sun.sun_path, uds, sizeof(sun.sun_path)) >= sizeof(sun.sun_path)) {
+		fprintf(stderr, "%s: UNIX domain socket path truncation\n", argv0);
+		exit(1);
+	}
+	len = strlen(sun.sun_path) + 1 + sizeof(sun.sun_family);
+	if (connect(fd, (struct sockaddr *)&sun, len) == -1) {
+		fprintf(stderr, "%s: connect: %s\n", argv0, strerror(errno));
+		exit(1);
+	}
+	return fd;
+}
+
+static int
+tcpopen(const char *host, const char *service)
+{
+	struct addrinfo hints, *res = NULL, *rp;
+	int fd = -1, e;
+
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_family = AF_UNSPEC; /* allow IPv4 or IPv6 */
+	hints.ai_flags = AI_NUMERICSERV; /* avoid name lookup for port */
+	hints.ai_socktype = SOCK_STREAM;
+
+	if ((e = getaddrinfo(host, service, &hints, &res))) {
+		fprintf(stderr, "%s: getaddrinfo: %s\n", argv0, gai_strerror(e));
+		exit(1);
 	}
-	sin.sin_family = AF_INET;
-	memcpy(&sin.sin_addr, hp->h_addr, hp->h_length);
-	sin.sin_port = htons(port);
-	if((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
-		perror("ii: cannot create socket");
-		exit(EXIT_FAILURE);
+
+	for (rp = res; rp; rp = rp->ai_next) {
+		fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+		if (fd == -1)
+			continue;
+		if (connect(fd, res->ai_addr, res->ai_addrlen) == -1) {
+			close(fd);
+			fd = -1;
+			continue;
+		}
+		break; /* success */
 	}
-	if(connect(fd, (const struct sockaddr *) &sin, sizeof(sin)) < 0) {
-		perror("ii: cannot connect to host");
-		exit(EXIT_FAILURE);
+	if (fd == -1) {
+		fprintf(stderr, "%s: could not connect to %s:%s: %s\n",
+			argv0, host, service, strerror(errno));
+		exit(1);
 	}
+
+	freeaddrinfo(res);
 	return fd;
 }
 
-static size_t tokenize(char **result, size_t reslen, char *str, char delim) {
+static int
+isnumeric(const char *s)
+{
+	errno = 0;
+	strtol(s, NULL, 10);
+	return errno == 0;
+}
+
+static size_t
+tokenize(char **result, size_t reslen, char *str, int delim)
+{
 	char *p = NULL, *n = NULL;
-	size_t i;
+	size_t i = 0;
 
-	if(!str)
-		return 0;
-	for(n = str; *n == ' '; n++);
+	for (n = str; *n == ' '; n++)
+		;
 	p = n;
-	for(i = 0; *n != 0;) {
-		if(i == reslen)
+	while (*n != '\0') {
+		if (i >= reslen)
 			return 0;
-		if(i > TOK_CHAN - TOK_CMD && strtol(result[0], NULL, 10) > 0) delim=':'; /* workaround non-RFC compliant messages */
-		if(*n == delim) {
-			*n = 0;
+		if (i > TOK_CHAN - TOK_CMD && result[0] && isnumeric(result[0]))
+			delim = ':'; /* workaround non-RFC compliant messages */
+		if (*n == delim) {
+			*n = '\0';
 			result[i++] = p;
 			p = ++n;
-		} else
+		} else {
 			n++;
+		}
 	}
-	if(i<reslen && p < n && strlen(p))
+	/* add last entry */
+	if (i < reslen && p < n && p && *p)
 		result[i++] = p;
-	return i;				/* number of tokens */
+	return i; /* number of tokens */
 }
 
-static void print_out(char *channel, char *buf) {
-	static char outfile[256], server[256], buft[18];
-	FILE *out = NULL;
-	time_t t = time(0);
-
-	if(channel) snprintf(server, sizeof(server), "-!- %s", channel);
-	if(strstr(buf, server)) channel="";
-	create_filepath(outfile, sizeof(outfile), channel, "out");
-	if(!(out = fopen(outfile, "a"))) return;
-	if(channel && channel[0]) add_channel(channel);
+static void
+channel_print(Channel *c, const char *buf)
+{
+	FILE *fp = NULL;
+	time_t t = time(NULL);
 
-	strftime(buft, sizeof(buft), "%F %R", localtime(&t));
-	fprintf(out, "%s %s\n", buft, buf);
-	fclose(out);
+	if (!(fp = fopen(c->outpath, "a")))
+		return;
+	fprintf(fp, "%lu %s\n", (unsigned long)t, buf);
+	fclose(fp);
 }
 
-static void proc_channels_privmsg(char *channel, char *buf) {
-	snprintf(message, PIPE_BUF, "<%s> %s", nick, buf);
-	print_out(channel, message);
-	snprintf(message, PIPE_BUF, "PRIVMSG %s :%s\r\n", channel, buf);
-	write(irc, message, strlen(message));
+static void
+proc_channels_privmsg(int ircfd, Channel *c, char *buf)
+{
+	snprintf(msg, sizeof(msg), "<%s> %s", nick, buf);
+	channel_print(c, msg);
+	snprintf(msg, sizeof(msg), "PRIVMSG %s :%s\r\n", c->name, buf);
+	ewritestr(ircfd, msg);
 }
 
-static void proc_channels_input(Channel *c, char *buf) {
-	/* static char infile[256]; */
+static void
+proc_channels_input(int ircfd, Channel *c, char *buf)
+{
 	char *p = NULL;
+	size_t buflen;
 
-	if(buf[0] != '/' && buf[0] != 0) {
-		proc_channels_privmsg(c->name, buf);
+	if (buf[0] != '/' && buf[0] != '\0') {
+		proc_channels_privmsg(ircfd, c, buf);
 		return;
 	}
-	message[0] = '\0';
-	if(buf[2] == ' ' || buf[2] == '\0') switch (buf[1]) {
-		case 'j':
-			p = strchr(&buf[3], ' ');
-			if(p) *p = 0;
-			if((buf[3]=='#')||(buf[3]=='&')||(buf[3]=='+')||(buf[3]=='!')){
-				if(p) snprintf(message, PIPE_BUF, "JOIN %s %s\r\n", &buf[3], p + 1); /* password protected channel */
-				else snprintf(message, PIPE_BUF, "JOIN %s\r\n", &buf[3]);
-				add_channel(&buf[3]);
-			}
-			else if(p){
-				add_channel(&buf[3]);
-				proc_channels_privmsg(&buf[3], p + 1);
+	msg[0] = '\0';
+	if (buf[2] == ' ' || buf[2] == '\0') {
+		buflen = strlen(buf);
+		switch (buf[1]) {
+		case 'j': /* join */
+			if ((p = strchr(&buf[3], ' '))) /* password parameter */
+				*p = '\0';
+			if ((buf[3] == '#') || (buf[3] == '&') || (buf[3] == '+') ||
+				(buf[3] == '!'))
+			{
+				/* password protected channel */
+				if (p)
+					snprintf(msg, sizeof(msg), "JOIN %s %s\r\n", &buf[3], p + 1);
+				else
+					snprintf(msg, sizeof(msg), "JOIN %s\r\n", &buf[3]);
+				channel_join(&buf[3]);
+			} else if (p) {
+				if ((c = channel_join(&buf[3])))
+					proc_channels_privmsg(ircfd, c, p + 1);
 				return;
 			}
 			break;
-		case 't':
-			if(strlen(buf)>=3) snprintf(message, PIPE_BUF, "TOPIC %s :%s\r\n", c->name, &buf[3]);
+		case 't': /* topic */
+			if (buflen >= 3)
+				snprintf(msg, sizeof(msg), "TOPIC %s :%s\r\n", c->name, &buf[3]);
 			break;
-		case 'a':
-			if(strlen(buf)>=3){
-				snprintf(message, PIPE_BUF, "-!- %s is away \"%s\"", nick, &buf[3]);
-				print_out(c->name, message);
+		case 'a': /* away */
+			if (buflen >= 3) {
+				snprintf(msg, sizeof(msg), "-!- %s is away \"%s\"", nick, &buf[3]);
+				channel_print(c, msg);
 			}
-			if(buf[2] == 0 || strlen(buf)<3) /* or used to make else part safe */
-				snprintf(message, PIPE_BUF, "AWAY\r\n");
+			if (buflen >= 3)
+				snprintf(msg, sizeof(msg), "AWAY :%s\r\n", &buf[3]);
 			else
-				snprintf(message, PIPE_BUF, "AWAY :%s\r\n", &buf[3]);
+				snprintf(msg, sizeof(msg), "AWAY\r\n");
 			break;
-		case 'n':
-			if(strlen(buf)>=3){
-				snprintf(_nick, sizeof(nick),"%s", &buf[3]);
-				snprintf(message, PIPE_BUF, "NICK %s\r\n", &buf[3]);
+		case 'n': /* change nick */
+			if (buflen >= 3) {
+				strlcpy(_nick, &buf[3], sizeof(_nick));
+				snprintf(msg, sizeof(msg), "NICK %s\r\n", &buf[3]);
 			}
 			break;
-		case 'l':
-			if(c->name[0] == 0)
+		case 'l': /* leave */
+			if (c == channelmaster)
 				return;
-			if(buf[2] == ' ' && strlen(buf)>=3)
-				snprintf(message, PIPE_BUF, "PART %s :%s\r\n", c->name, &buf[3]);
+			if (buflen >= 3)
+				snprintf(msg, sizeof(msg), "PART %s :%s\r\n", c->name, &buf[3]);
 			else
-				snprintf(message, PIPE_BUF,
-						"PART %s :ii - 500 SLOC are too much\r\n", c->name);
-			write(irc, message, strlen(message));
-			close(c->fd);
-			/*create_filepath(infile, sizeof(infile), c->name, "in");
-			unlink(infile); */
-			rm_channel(c);
+				snprintf(msg, sizeof(msg),
+				         "PART %s :leaving\r\n", c->name);
+			ewritestr(ircfd, msg);
+			channel_leave(c);
 			return;
 			break;
-		default:
-			snprintf(message, PIPE_BUF, "%s\r\n", &buf[1]);
+		case 'q': /* quit */
+			if (buflen >= 3)
+				snprintf(msg, sizeof(msg), "QUIT :%s\r\n", &buf[3]);
+			else
+				snprintf(msg, sizeof(msg),
+				         "QUIT %s\r\n", "bye");
+			ewritestr(ircfd, msg);
+			isrunning = 0;
+			return;
+			break;
+		default: /* raw IRC command */
+			snprintf(msg, sizeof(msg), "%s\r\n", &buf[1]);
 			break;
 		}
-	else
-		snprintf(message, PIPE_BUF, "%s\r\n", &buf[1]);
-
-	if (message[0] != '\0')
-		write(irc, message, strlen(message));
+	} else {
+		/* raw IRC command */
+		snprintf(msg, sizeof(msg), "%s\r\n", &buf[1]);
+	}
+	if (msg[0] != '\0')
+		ewritestr(ircfd, msg);
 }
 
-static void proc_server_cmd(char *buf) {
+static void
+proc_server_cmd(int fd, char *buf)
+{
+	Channel *c;
+	const char *channel;
 	char *argv[TOK_LAST], *cmd = NULL, *p = NULL;
-	int i;
+	unsigned int i;
 
-	if(!buf || *buf=='\0')
+	if (!buf || buf[0] == '\0')
 		return;
 
-	for(i = 0; i < TOK_LAST; i++)
+	/* clear tokens */
+	for (i = 0; i < TOK_LAST; i++)
 		argv[i] = NULL;
-	/* <message>  ::= [':' <prefix> <SPACE> ] <command> <params> <crlf>
-	   <prefix>   ::= <servername> | <nick> [ '!' <user> ] [ '@' <host> ]
-	   <command>  ::= <letter> { <letter> } | <number> <number> <number>
-	   <SPACE>    ::= ' ' { ' ' }
-	   <params>   ::= <SPACE> [ ':' <trailing> | <middle> <params> ]
-	   <middle>   ::= <Any *non-empty* sequence of octets not including SPACE
-	   or NUL or CR or LF, the first of which may not be ':'>
-	   <trailing> ::= <Any, possibly *empty*, sequence of octets not including NUL or CR or LF>
-	   <crlf>     ::= CR LF */
-
-	if(buf[0] == ':') {		/* check prefix */
-		if (!(p = strchr(buf, ' '))) return;
-		*p = 0;
-		for(++p; *p == ' '; p++);
+
+	/* check prefix */
+	if (buf[0] == ':') {
+		if (!(p = strchr(buf, ' ')))
+			return;
+		*p = '\0';
+		for (++p; *p == ' '; p++)
+			;
 		cmd = p;
 		argv[TOK_NICKSRV] = &buf[1];
-		if((p = strchr(buf, '!'))) {
-			*p = 0;
+		if ((p = strchr(buf, '!'))) {
+			*p = '\0';
 			argv[TOK_USER] = ++p;
 		}
-	} else
+	} else {
 		cmd = buf;
+	}
 
 	/* remove CRLFs */
-	for(p = cmd; p && *p != 0; p++)
-		if(*p == '\r' || *p == '\n')
-			*p = 0;
+	for (p = cmd; p && *p != '\0'; p++) {
+		if (*p == '\r' || *p == '\n')
+			*p = '\0';
+	}
 
-	if((p = strchr(cmd, ':'))) {
-		*p = 0;
+	if ((p = strchr(cmd, ':'))) {
+		*p = '\0';
 		argv[TOK_TEXT] = ++p;
 	}
 
 	tokenize(&argv[TOK_CMD], TOK_LAST - TOK_CMD, cmd, ' ');
 
-	if(!argv[TOK_CMD] || !strncmp("PONG", argv[TOK_CMD], 5)) {
-		return;
-	} else if(!strncmp("PING", argv[TOK_CMD], 5)) {
-		snprintf(message, PIPE_BUF, "PONG %s\r\n", argv[TOK_TEXT]);
-		write(irc, message, strlen(message));
+	if (!argv[TOK_CMD] || !strcmp("PONG", argv[TOK_CMD])) {
 		return;
-	} else if(!argv[TOK_NICKSRV] || !argv[TOK_USER]) {	/* server command */
-		snprintf(message, PIPE_BUF, "%s%s", argv[TOK_ARG] ? argv[TOK_ARG] : "", argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
-		print_out(0, message);
+	} else if (!strcmp("PING", argv[TOK_CMD])) {
+		snprintf(msg, sizeof(msg), "PONG %s\r\n", argv[TOK_TEXT]);
+		ewritestr(fd, msg);
 		return;
-	} else if(!strncmp("ERROR", argv[TOK_CMD], 6))
-		snprintf(message, PIPE_BUF, "-!- error %s", argv[TOK_TEXT] ? argv[TOK_TEXT] : "unknown");
-	else if(!strncmp("JOIN", argv[TOK_CMD], 5) && (argv[TOK_CHAN] || argv[TOK_TEXT])) {
-		if (argv[TOK_TEXT] != NULL)
+	} else if (!argv[TOK_NICKSRV] || !argv[TOK_USER]) {
+		/* server command */
+		snprintf(msg, sizeof(msg), "%s%s",
+				argv[TOK_ARG] ? argv[TOK_ARG] : "",
+				argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
+		channel_print(channelmaster, msg);
+		return; /* don't process further */
+	} else if (!strcmp("ERROR", argv[TOK_CMD]))
+		snprintf(msg, sizeof(msg), "-!- error %s",
+				argv[TOK_TEXT] ? argv[TOK_TEXT] : "unknown");
+	else if (!strcmp("JOIN", argv[TOK_CMD]) && (argv[TOK_CHAN] || argv[TOK_TEXT])) {
+		if (argv[TOK_TEXT])
 			argv[TOK_CHAN] = argv[TOK_TEXT];
-		snprintf(message, PIPE_BUF, "-!- %s(%s) has joined %s", argv[TOK_NICKSRV], argv[TOK_USER], argv[TOK_CHAN]);
-	} else if(!strncmp("PART", argv[TOK_CMD], 5) && argv[TOK_CHAN]) {
-		snprintf(message, PIPE_BUF, "-!- %s(%s) has left %s", argv[TOK_NICKSRV], argv[TOK_USER], argv[TOK_CHAN]);
-	} else if(!strncmp("MODE", argv[TOK_CMD], 5))
-		snprintf(message, PIPE_BUF, "-!- %s changed mode/%s -> %s %s", argv[TOK_NICKSRV], argv[TOK_CMD + 1] ? argv[TOK_CMD + 1] : "" , argv[TOK_CMD + 2]? argv[TOK_CMD + 2] : "", argv[TOK_CMD + 3] ? argv[TOK_CMD + 3] : "");
-	else if(!strncmp("QUIT", argv[TOK_CMD], 5))
-		snprintf(message, PIPE_BUF, "-!- %s(%s) has quit \"%s\"", argv[TOK_NICKSRV], argv[TOK_USER], argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
-	else if(!strncmp("NICK", argv[TOK_CMD], 5) && argv[TOK_TEXT] && !strcmp(_nick, argv[TOK_TEXT])) {
-		snprintf(nick, sizeof(nick), "%s", _nick);
-		snprintf(message, PIPE_BUF, "-!- changed nick to \"%s\"", nick);
-		print_out(NULL, message);
-	} else if(!strncmp("NICK", argv[TOK_CMD], 5) && argv[TOK_TEXT])
-		snprintf(message, PIPE_BUF, "-!- %s changed nick to %s", argv[TOK_NICKSRV], argv[TOK_TEXT]);
-	else if(!strncmp("TOPIC", argv[TOK_CMD], 6))
-		snprintf(message, PIPE_BUF, "-!- %s changed topic to \"%s\"", argv[TOK_NICKSRV], argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
-	else if(!strncmp("KICK", argv[TOK_CMD], 5) && argv[TOK_ARG])
-		snprintf(message, PIPE_BUF, "-!- %s kicked %s (\"%s\")", argv[TOK_NICKSRV], argv[TOK_ARG], argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
-	else if(!strncmp("NOTICE", argv[TOK_CMD], 7))
-		snprintf(message, PIPE_BUF, "-!- \"%s\")", argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
-	else if(!strncmp("PRIVMSG", argv[TOK_CMD], 8))
-		snprintf(message, PIPE_BUF, "<%s> %s", argv[TOK_NICKSRV], argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
+		snprintf(msg, sizeof(msg), "-!- %s(%s) has joined %s",
+				argv[TOK_NICKSRV], argv[TOK_USER], argv[TOK_CHAN]);
+	} else if (!strcmp("PART", argv[TOK_CMD]) && argv[TOK_CHAN]) {
+		snprintf(msg, sizeof(msg), "-!- %s(%s) has left %s",
+				argv[TOK_NICKSRV], argv[TOK_USER], argv[TOK_CHAN]);
+		/* if user itself leaves, don't write to channel (don't reopen channel). */
+		if (!strcmp(argv[TOK_NICKSRV], nick))
+			return;
+	} else if (!strcmp("MODE", argv[TOK_CMD])) {
+		snprintf(msg, sizeof(msg), "-!- %s changed mode/%s -> %s %s",
+				argv[TOK_NICKSRV],
+				argv[TOK_CHAN] ? argv[TOK_CHAN] : "",
+				argv[TOK_ARG]  ? argv[TOK_ARG] : "",
+				argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
+	} else if (!strcmp("QUIT", argv[TOK_CMD])) {
+		snprintf(msg, sizeof(msg), "-!- %s(%s) has quit \"%s\"",
+				argv[TOK_NICKSRV], argv[TOK_USER],
+				argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
+	} else if (!strncmp("NICK", argv[TOK_CMD], 5) && argv[TOK_TEXT] &&
+	          !strcmp(_nick, argv[TOK_TEXT])) {
+		strlcpy(nick, _nick, sizeof(nick));
+		snprintf(msg, sizeof(msg), "-!- changed nick to \"%s\"", nick);
+		channel_print(channelmaster, msg);
+	} else if (!strcmp("NICK", argv[TOK_CMD]) && argv[TOK_TEXT]) {
+		snprintf(msg, sizeof(msg), "-!- %s changed nick to %s",
+				argv[TOK_NICKSRV], argv[TOK_TEXT]);
+	} else if (!strcmp("TOPIC", argv[TOK_CMD])) {
+		snprintf(msg, sizeof(msg), "-!- %s changed topic to \"%s\"",
+				argv[TOK_NICKSRV],
+				argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
+	} else if (!strcmp("KICK", argv[TOK_CMD]) && argv[TOK_ARG]) {
+		snprintf(msg, sizeof(msg), "-!- %s kicked %s (\"%s\")",
+				argv[TOK_NICKSRV], argv[TOK_ARG],
+				argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
+	} else if (!strcmp("NOTICE", argv[TOK_CMD])) {
+		snprintf(msg, sizeof(msg), "-!- \"%s\")",
+				argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
+	} else if (!strcmp("PRIVMSG", argv[TOK_CMD])) {
+		snprintf(msg, sizeof(msg), "<%s> %s", argv[TOK_NICKSRV],
+				argv[TOK_TEXT] ? argv[TOK_TEXT] : "");
+	} else {
+		return; /* can't read this message */
+	}
+	if (argv[TOK_CHAN] && !strcmp(argv[TOK_CHAN], nick))
+		channel = argv[TOK_NICKSRV];
 	else
-		return;	/* can't read this message */
+		channel = argv[TOK_CHAN];
 
-	if(!argv[TOK_CHAN] || !strncmp(argv[TOK_CHAN], nick, strlen(nick)))
-		print_out(argv[TOK_NICKSRV], message);
+	if (!channel || channel[0] == '\0')
+		c = channelmaster;
 	else
-		print_out(argv[TOK_CHAN], message);
+		c = channel_join(channel);
+	if (c)
+		channel_print(c, msg);
 }
 
-static int read_line(int fd, size_t res_len, char *buf) {
+static int
+read_line(int fd, char *buf, size_t bufsiz)
+{
 	size_t i = 0;
-	char c = 0;
+	char c = '\0';
+
 	do {
-		if(read(fd, &c, sizeof(char)) != sizeof(char))
+		if (read(fd, &c, sizeof(char)) != sizeof(char))
 			return -1;
 		buf[i++] = c;
-	}
-	while(c != '\n' && i < res_len);
-	buf[i - 1] = 0;			/* eliminates '\n' */
+	} while (c != '\n' && i < bufsiz);
+	buf[i - 1] = '\0'; /* eliminates '\n' */
 	return 0;
 }
 
-static void handle_channels_input(Channel *c) {
-	static char buf[PIPE_BUF];
-	if(read_line(c->fd, PIPE_BUF, buf) == -1) {
-		close(c->fd);
-		int fd = open_channel(c->name);
-		if(fd != -1)
-			c->fd = fd;
-		else
-			rm_channel(c);
+static void
+handle_channels_input(int ircfd, Channel *c)
+{
+	char buf[IRC_MSG_MAX];
+
+	if (read_line(c->fdin, buf, sizeof(buf)) == -1) {
+		if (channel_reopen(c) == -1)
+			channel_rm(c);
 		return;
 	}
-	proc_channels_input(c, buf);
+	proc_channels_input(ircfd, c, buf);
 }
 
-static void handle_server_output() {
-	static char buf[PIPE_BUF];
-	if(read_line(irc, PIPE_BUF, buf) == -1) {
-		perror("ii: remote host closed connection");
-		exit(EXIT_FAILURE);
+static void
+handle_server_output(int ircfd)
+{
+	char buf[IRC_MSG_MAX];
+
+	if (read_line(ircfd, buf, sizeof(buf)) == -1) {
+		fprintf(stderr, "%s: remote host closed connection: %s\n",
+		        argv0, strerror(errno));
+		exit(1);
 	}
-	proc_server_cmd(buf);
+	fprintf(stdout, "%lu %s\n", (unsigned long)time(NULL), buf);
+	fflush(stdout);
+	proc_server_cmd(ircfd, buf);
 }
 
-static void run() {
-	Channel *c, *n;
-	int r, maxfd;
-	fd_set rd;
+static void
+sighandler(int sig)
+{
+	if (sig == SIGTERM || sig == SIGINT)
+		isrunning = 0;
+}
+
+static void
+setup(void)
+{
+	struct sigaction sa;
+
+	memset(&sa, 0, sizeof(sa));
+	sa.sa_handler = sighandler;
+	sigaction(SIGTERM, &sa, NULL);
+	sigaction(SIGINT, &sa, NULL);
+}
+
+static void
+run(int ircfd, const char *host)
+{
+	Channel *c, *tmp;
+	fd_set rdset;
 	struct timeval tv;
-	char ping_msg[512];
+	char ping_msg[IRC_MSG_MAX];
+	int r, maxfd;
 
 	snprintf(ping_msg, sizeof(ping_msg), "PING %s\r\n", host);
-	for(;;) {
-		FD_ZERO(&rd);
-		maxfd = irc;
-		FD_SET(irc, &rd);
-		for(c = channels; c; c = c->next) {
-			if(maxfd < c->fd)
-				maxfd = c->fd;
-			FD_SET(c->fd, &rd);
+	while (isrunning) {
+		maxfd = ircfd;
+		FD_ZERO(&rdset);
+		FD_SET(ircfd, &rdset);
+		for (c = channels; c; c = c->next) {
+			if (c->fdin > maxfd)
+				maxfd = c->fdin;
+			FD_SET(c->fdin, &rdset);
 		}
-
+		memset(&tv, 0, sizeof(tv));
 		tv.tv_sec = 120;
-		tv.tv_usec = 0;
-		r = select(maxfd + 1, &rd, 0, 0, &tv);
-		if(r < 0) {
-			if(errno == EINTR)
+		r = select(maxfd + 1, &rdset, 0, 0, &tv);
+		if (r < 0) {
+			if (errno == EINTR)
 				continue;
-			perror("ii: error on select()");
-			exit(EXIT_FAILURE);
-		} else if(r == 0) {
-			if(time(NULL) - last_response >= PING_TIMEOUT) {
-				print_out(NULL, "-!- ii shutting down: ping timeout");
-				exit(EXIT_TIMEOUT);
+			fprintf(stderr, "%s: select: %s\n", argv0, strerror(errno));
+			exit(1);
+		} else if (r == 0) {
+			if (time(NULL) - last_response >= PING_TIMEOUT) {
+				channel_print(channelmaster, "-!- ii shutting down: ping timeout");
+				exit(2); /* status code 2 for timeout */
 			}
-			write(irc, ping_msg, strlen(ping_msg));
+			ewritestr(ircfd, ping_msg);
 			continue;
 		}
-		if(FD_ISSET(irc, &rd)) {
-			handle_server_output();
+		if (FD_ISSET(ircfd, &rdset)) {
+			handle_server_output(ircfd);
 			last_response = time(NULL);
 		}
-		for(c = channels; c; c = n) {
-			n = c->next;
-			if(FD_ISSET(c->fd, &rd))
-				handle_channels_input(c);
+		for (c = channels; c; c = tmp) {
+			tmp = c->next;
+			if (FD_ISSET(c->fdin, &rdset))
+				handle_channels_input(ircfd, c);
 		}
 	}
 }
 
-int main(int argc, char *argv[]) {
-	int i;
-	unsigned short port = SERVER_PORT;
-	struct passwd *spw = getpwuid(getuid());
-	char *key = NULL, *fullname = NULL;
-	char prefix[_POSIX_PATH_MAX];
-
-	if(!spw) {
-		fputs("ii: getpwuid() failed\n", stderr);
-		exit(EXIT_FAILURE);
-	}
-	snprintf(nick, sizeof(nick), "%s", spw->pw_name);
-	snprintf(prefix, sizeof(prefix),"%s/irc", spw->pw_dir);
-	if (argc <= 1 || (argc == 2 && argv[1][0] == '-' && argv[1][1] == 'h')) usage();
-
-	for(i = 1; (i + 1 < argc) && (argv[i][0] == '-'); i++) {
-		switch (argv[i][1]) {
-			case 'i': snprintf(prefix,sizeof(prefix),"%s", argv[++i]); break;
-			case 's': host = argv[++i]; break;
-			case 'p': port = strtol(argv[++i], NULL, 10); break;
-			case 'n': snprintf(nick,sizeof(nick),"%s", argv[++i]); break;
-			case 'k': key = getenv(argv[++i]); break;
-			case 'f': fullname = argv[++i]; break;
-			default: usage(); break;
-		}
+int
+main(int argc, char *argv[])
+{
+	Channel *c, *tmp;
+	struct passwd *spw;
+	const char *key = NULL, *fullname = NULL, *host = "";
+	const char *uds = NULL, *service = "6667";
+	char prefix[PATH_MAX];
+	int ircfd, r;
+
+	/* use nickname and home dir of user by default */
+	if (!(spw = getpwuid(getuid()))) {
+		fprintf(stderr, "%s: getpwuid: %s\n", argv0, strerror(errno));
+		exit(1);
 	}
-	irc = tcpopen(port);
-	
-	#ifdef __OpenBSD__	/* OpenBSD pledge(2) support */
-		if (pledge("stdio rpath wpath cpath dpath", NULL) == -1) {
-			fprintf(stderr, "ii pledge: %s\n", strerror(errno));
-			exit(EXIT_FAILURE);
-		}
-	#endif
+	strlcpy(nick, spw->pw_name, sizeof(nick));
+	snprintf(prefix, sizeof(prefix), "%s/irc", spw->pw_dir);
+
+	ARGBEGIN {
+	case 'f':
+		fullname = EARGF(usage());
+		break;
+	case 'i':
+		strlcpy(prefix, EARGF(usage()), sizeof(prefix));
+		break;
+	case 'k':
+		key = getenv(EARGF(usage()));
+		break;
+	case 'n':
+		strlcpy(nick, EARGF(usage()), sizeof(nick));
+		break;
+	case 'p':
+		service = EARGF(usage());
+		break;
+	case 's':
+		host = EARGF(usage());
+		break;
+	case 'u':
+		uds = EARGF(usage());
+		break;
+	default:
+		usage();
+		break;
+	} ARGEND;
+
+	if (!*host)
+		usage();
+
+	if (uds)
+		ircfd = udsopen(uds);
+	else
+		ircfd = tcpopen(host, service);
 
-	if(!snprintf(path, sizeof(path), "%s/%s", prefix, host)) {
-		fputs("ii: path to irc directory too long\n", stderr);
-		exit(EXIT_FAILURE);
+#ifdef __OpenBSD__
+	/* OpenBSD pledge(2) support */
+	if (pledge("stdio rpath wpath cpath dpath", NULL) == -1) {
+		fprintf(stderr, "%s: pledge: %s\n", argv0, strerror(errno));
+		exit(1);
 	}
-	create_dirtree(path);
+#endif
 
-	add_channel(""); /* master channel */
-	login(key, fullname);
-	run();
+	r = snprintf(ircpath, sizeof(ircpath), "%s/%s", prefix, host);
+	if (r < 0 || (size_t)r >= sizeof(ircpath)) {
+		fprintf(stderr, "%s: path to irc directory too long\n", argv0);
+		exit(1);
+	}
+	create_dirtree(ircpath);
+
+	channelmaster = channel_add(""); /* master channel */
+	if (key)
+		loginkey(ircfd, key);
+	loginuser(ircfd, host, fullname && *fullname ? fullname : nick);
+	setup();
+	run(ircfd, host);
+	if (channelmaster)
+		channel_leave(channelmaster);
+
+	for (c = channels; c; c = tmp) {
+		tmp = c->next;
+		channel_leave(c);
+	}
 
-	return EXIT_SUCCESS;
+	return 0;
 }
diff --git a/query.sh b/query.sh
deleted file mode 100755
index 87a9525..0000000
--- a/query.sh
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/bin/sh
-# ----------------------------------------------------
-# Nico Golde <nico@ngolde.de>
-# License: do whatever you want with this code
-# Purpose: locate new queries for the ii irc client
-# ----------------------------------------------------
-
-IRCPATH=$HOME/irc
-TMPFILE=$IRCPATH/queries.tmp
-
-if [ ! -f $TMPFILE ]; then
-    touch $TMPFILE
-fi
-
-echo "searching new query data"
-for i in `find $IRCPATH -newer $TMPFILE -name 'out'`
-do
-    grep -v '\-!\-' $i  > /dev/null 2>&1 # if file doesnt just contain server stuff
-    if [ $? -ne 1 ]; then
-        # strip server, nickserv and channel out files
-        echo $i | egrep -v -i "nickserv|#|$IRCPATH/(irc\.freenode\.net|irc\.oftc\.net)/out" > /dev/null 2>&1
-        if [ $? -ne 1 ]; then
-            printf "new data in: %s\n========================================================\n" "$i"
-            tail -5 $i
-        fi
-    fi
-done
-
-touch $TMPFILE
diff --git a/strlcpy.c b/strlcpy.c
new file mode 100644
index 0000000..db0e6f0
--- /dev/null
+++ b/strlcpy.c
@@ -0,0 +1,32 @@
+/* Taken from OpenBSD */
+#include <sys/types.h>
+#include <string.h>
+
+/*
+ * Copy src to string dst of size siz.  At most siz-1 characters
+ * will be copied.  Always NUL terminates (unless siz == 0).
+ * Returns strlen(src); if retval >= siz, truncation occurred.
+ */
+size_t
+strlcpy(char *dst, const char *src, size_t siz)
+{
+	char *d = dst;
+	const char *s = src;
+	size_t n = siz;
+
+	/* Copy as many bytes as will fit */
+	if (n != 0) {
+		while (--n != 0) {
+			if ((*d++ = *s++) == '\0')
+				break;
+		}
+	}
+	/* Not enough room in dst, add NUL and traverse rest of src */
+	if (n == 0) {
+		if (siz != 0)
+			*d = '\0'; /* NUL-terminate dst */
+		while (*s++)
+			;
+	}
+	return(s - src - 1); /* count does not include NUL */
+}