wm: ticl

Download patch

ref: 5ff9649e6c51bae0dd09f218e4943fa5f4d458be
author: libredev <libredev@ircforever.org>
date: Mon Nov 14 00:30:34 EST 2022

initial commit

--- /dev/null
+++ b/COPYING
@@ -1,0 +1,13 @@
+The author(s) of this work have dedicated this work to the public domain by
+waiving all claims of copyright, patent and trademark rights to this work.
+
+This work is provided 'as-is', without any express or implied warranty.
+In no event shall the author(s) be liable for any damages arising from
+the use of this work.
+
+Alternatively, this work is available under any of the following licenses:
+
+Unlicense	https://spdx.org/licenses/Unlicense.html
+CC0-1.0		https://spdx.org/licenses/CC0-1.0.html
+MIT-0		https://spdx.org/licenses/MIT-0.html
+0BSD		https://spdx.org/licenses/0BSD.html
--- /dev/null
+++ b/Makefile
@@ -1,0 +1,50 @@
+# This work is dedicated to the public domain.
+# See COPYING file for more information.
+
+PREFIX = /usr/local
+
+CC     = cc
+CFLAGS = -g -std=c89 -Wall -Wextra -pedantic -Wconversion\
+	 -Wstrict-prototypes -Wold-style-definition\
+	 -D_POSIX_C_SOURCE=200809L
+#CFLAGS += -fsanitize=address -fno-omit-frame-pointer
+#LDFLAGS = -fsanitize=address
+
+VERSION != date '+%Y-%m-%d'
+PROGRAM = ticl
+SOURCES = main.c htable.c utils.c
+HEADERS = htable.h utils.h
+OBJECTS = $(SOURCES:.c=.o)
+
+all: $(PROGRAM)
+
+main.o: htable.o utils.o
+htable.o: htable.c htable.h
+utils.o: utils.c utils.h
+
+$(PROGRAM): $(OBJECTS)
+	$(CC) -o $@ $(OBJECTS) $(LDFLAGS)
+
+.c.o:
+	$(CC) -c $(CFLAGS) $<
+
+clean:
+	rm -f $(PROGRAM) $(OBJECTS) $(PROGRAM)-$(VERSION).tar.gz
+
+dist: clean
+	mkdir -p $(PROGRAM)-$(VERSION)
+	cp -R README COPYING Makefile $(SOURCES) $(HEADERS)\
+		$(PROGRAM)-$(VERSION)
+	tar -cf	$(PROGRAM)-$(VERSION).tar $(PROGRAM)-$(VERSION)
+	gzip $(PROGRAM)-$(VERSION).tar
+	rm -rf $(PROGRAM)-$(VERSION)
+
+install: all
+	mkdir -p $(DESTDIR)$(PREFIX)/bin
+	cp -f $(PROGRAM) $(DESTDIR)$(PREFIX)/bin
+	chmod 755 $(DESTDIR)$(PREFIX)/bin/$(PROGRAM)
+
+uninstall:
+	rm -f $(DESTDIR)$(PREFIX)/bin/$(PROGRAM)
+
+.PHONY: all clean dist install uninstall
--- /dev/null
+++ b/README
@@ -1,0 +1,198 @@
+====================================================================
+WARNING: This program is still in progress, use it at your own risk.
+====================================================================
+
+ticl - tiny irc channel linker
+------------------------------
+
+ticl is a very small and simple multi-network irc channel linker.
+
+
+Working
+-------
+
+A bot named 'linker' joins each given channels and clones each user of its
+channel on other channels and relays the user's messages received from its
+channel to the clones on other channels.
+
+If a user JOIN, QUIT, PART, or NICK, the linker attempts to emulate the
+same action with the clones on the other channels.
+
+The nick of clones has the network symbol (included in square brackets) as
+a suffix.
+
+If nick is already taken then additional suffix '_' will be added until
+nick is accepted on the network.
+
+
+Limitation
+----------
+
+- Networks and channels that require any type of registration or verification
+  will not work because clones cannot register themselves.
+
+- Linking any two channels that are already linked will create an infinite
+  loop of clones and may get your IP banned.
+
+- Channels on the same network will not link (can be possible in the future).
+
+- No spam protection.
+
+
+Features
+--------
+
+- written in POSIX ANSI C.
+- one clone for every user on all other channels.
+- no support for TLS/SSL (this is a feature).
+
+
+Dependencies
+------------
+
+- C compiler (C89)
+- libc (with POSIX support)
+- POSIX make (optional)
+
+
+Compiling
+---------
+
+	$ make
+or
+	$ gcc ticl.c htable.c utils.c -o ticl
+
+
+Example
+-------
+
+To start the program with 'in' as the fifo file:
+
+	$ ./ticl ./in
+
+To link channel #test20 from libera and channel #test21 from ircnow:
+
+	$ echo 'netadd libera L irc.libera.chat 6667 #test20' > ./in
+	$ echo 'netadd ircnow N irc.ircnow.org 6667 #test21' > ./in
+
+To unlink channel ircnow:
+
+	$ echo 'netdel ircnow' > ./in
+
+To shutdown the program:
+
+	$ echo exit > ./in
+
+
+Community
+---------
+
+Join #playground on irc.ircnow.org:6697 (TLS)
+
+For any help, tag or private message me (libredev).
+
+NOTE: Anything is allowed as long as you are not violating IRCNow TOS.
+      (https://wiki.ircnow.org/index.php?n=Terms.Terms)
+
+Email: libredev@ircforever.org (expect late response)
+
+
+SSL/TLS support
+---------------
+
+1. relayd (OpenBSD)
+-------------------
+
+/etc/relayd.conf:
+
+	table <libera> { irc.libera.chat }
+	table <ircnow> { irc.ircnow.org }
+
+	protocol "irctls" {
+		tcp { nodelay, sack }
+	}
+
+	relay "libera" {
+		listen on 127.0.0.1 port 31220
+		protocol "irctls"
+		forward with tls to <libera> port 6697
+	}
+
+	relay "ircnow" {
+		listen on 127.0.0.1 port 31221
+		protocol "irctls"
+		forward with tls to <ircnow> port 6697
+	}
+
+2. stunnel (*BSD, GNU/Linux, GNU/Hurd, Plan9, etc)
+--------------------------------------------------
+
+On debian, /etc/stunnel/stunnel.conf:
+
+	pid = /etc/stunnel/pid
+
+	[libera]
+	client = yes
+	accept = 127.0.0.1:31220
+	connect = irc.libera.chat:6697
+	checkHost = irc.libera.chat
+	verifyChain = yes
+	CApath = /etc/ssl/certs
+	OCSPaia = yes
+
+	[ircnow]
+	client = yes
+	accept = 127.0.0.1:31221
+	connect = irc.ircnow.org:6697
+	checkHost = irc.ircnow.org
+	verifyChain = yes
+	CApath = /etc/ssl/certs
+	OCSPaia = yes
+
+3. To connect:
+--------------
+
+	$ echo 'netadd libera L 127.0.0.1 31220 #test20' > ./in
+	$ echo 'netadd ircnow N 127.0.0.1 31221 #test21' > ./in
+
+
+License
+-------
+
+This work is dedicated to the public domain.
+See COPYING file for more information.
+
+
+FAQ
+---
+
+Why not GPL?
+------------
+
+I was a big fan of GPL but slowly I realized that people should choose free
+software on their own intention and not by force. They should understand that
+free culture is the way to the future. But I don't hate GPL and I think it is
+a great license for new technologies and innovations that can be exploited by
+corporate.
+
+
+Why not MIT/BSD?
+----------------
+
+I hate attribution licenses like MIT, BSD or any permissive license because
+I think the attribution requirement is complete nonsense.
+
+
+Why shouldn't you call it open source?
+--------------------------------------
+
+I hate the term 'open source' because it has nothing to do with free software
+and its philosophy. It is a misleading term used by corporate that refer to
+open collaboration.
+
+But that's not why you shouldn't call it open source, but because the
+Open Source Initiative (OSI) doesn't consider public domain software to be
+open source. For more information, visit https://opensource.org/node/878.
+
+It is free software because the FSF considers the public domain software to be
+free software.
--- /dev/null
+++ b/htable.c
@@ -1,0 +1,223 @@
+/*
+ * This work is dedicated to the public domain.
+ * See COPYING file for more information.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "htable.h"
+#include "utils.h"
+
+static unsigned int
+hash(Htable *ht, void *key)
+{
+	unsigned int i;
+	unsigned int sum = 0;
+
+	/* very simple hash function */
+	for (i = 0; i < (unsigned int)ht->keylenfn(key); i++) {
+		sum += ((unsigned char *)key)[i] * (i + 1);
+	}
+	return (sum % ht->cap);
+}
+
+Htable *
+htcreate(KeyLenFn *keylenfn, KeyCmpFn *keycmpfn, FreeKeyFn *freekeyfn, FreeValFn *freevalfn, unsigned int cap)
+{
+	unsigned int i;
+	Htable *ht;
+
+	if (keylenfn == NULL)	fatal("keylenfn cannot be NULL");
+	if (keycmpfn == NULL)	fatal("keycmpfn cannot be NULL");
+
+	ht = ecalloc(1, sizeof(Htable));
+	ht->nodes	= ecalloc(cap, sizeof(Htnode));
+	ht->cap		= cap;
+	ht->len		= 0;
+	ht->keylenfn	= keylenfn;
+	ht->keycmpfn	= keycmpfn;
+	ht->freekeyfn	= freekeyfn;
+	ht->freevalfn	= freevalfn;
+
+	for (i = 0; i < cap; i++)
+		ht->nodes[i] = NULL;
+	return ht;
+}
+
+static void
+__htdestroy(Htable *ht, int dofree)
+{
+	unsigned int i;
+	Htnode *n;
+	Htnode *tmp;
+
+	for (i = 0; i < ht->cap; i++) {
+		for (n = ht->nodes[i]; n != NULL;) {
+			if (dofree) {
+				ht->freekeyfn(n->key);
+				ht->freevalfn(n->val);
+			}
+			tmp = n;
+			n = n->next;
+			free(tmp);
+		}
+	}
+	free(ht->nodes);
+	free(ht);
+}
+
+void
+htdestroy(Htable *ht)
+{
+	__htdestroy(ht, 1);
+}
+
+void *
+htsearch(Htable *ht, void *key)
+{
+	unsigned int i;
+	Htnode *n;
+
+	i = hash(ht, key);
+	for (n = ht->nodes[i]; n != NULL; n = n->next) {
+		if (ht->keycmpfn(key, n->key) == 0)
+			return n->val;
+	}
+	return NULL;
+}
+
+int
+htinsert(Htable *ht, void *key, void *val)
+{
+	unsigned int i;
+	Htnode *n;	/* current node */
+	Htnode *pn;	/* previous node */
+
+	pn = NULL;
+	i = hash(ht, key);
+	for (n = ht->nodes[i]; n != NULL; n = n->next) {
+		/*if key already exist, override value */
+		if (ht->keycmpfn(key, n->key) == 0) {
+			ht->freekeyfn(n->key);
+			ht->freevalfn(n->val);
+			n->key = key;
+			n->val = val;
+			return -1;
+		}
+		pn = n;
+	}
+
+	/* create a new node */
+	n = ecalloc(1, sizeof(Htnode));
+	n->key = key;
+	n->val = val;
+	n->next = NULL;
+	/* link to the previous node */
+	if (pn == NULL)
+		ht->nodes[i] = n;
+	else
+		pn->next = n;
+	/* increment the hash table length */
+	ht->len++;
+	return 1;
+}
+
+static int
+__htremove(Htable *ht, void *key, int freeval)
+{
+	unsigned int i;
+	Htnode *n;	/* current node */
+	Htnode *pn;	/* previous node */
+
+	pn = NULL;
+	i = hash(ht, key);
+	for (n = ht->nodes[i]; n != NULL; n = n->next) {
+		if (ht->keycmpfn(key, n->key) == 0) {
+			/* free node memory */
+			ht->freekeyfn(n->key);
+			if (freeval)
+				ht->freevalfn(n->val);
+			/* link to the previous node */
+			if (pn == NULL)
+				ht->nodes[i] = n->next;
+			else
+				pn->next = n->next;
+			/* free the node */
+			free(n);
+			return 1;
+		}
+		pn = n;
+	}
+	return -1;
+}
+
+int
+htremove(Htable *ht, void *key)
+{
+	return __htremove(ht, key, 1);
+}
+
+int
+htsetkey(Htable *ht, void *oldkey, void *newkey)
+{
+	void *val = NULL;
+	if ((val = htsearch(ht, oldkey)) == NULL)
+		return -1;
+	__htremove(ht, oldkey, 0);
+	htinsert(ht, newkey, val);
+	return 1;
+}
+
+void
+htresize(Htable *ht, unsigned int ncap)
+{
+	unsigned int i, tmp;
+	Htable *nht;	/* new hash table */
+	Htnode *n;	/* current node */
+	Htnode **list;	/* list of nodes */
+
+	/* create a new hash table */
+	nht = htcreate(ht->keylenfn, ht->keycmpfn, ht->freekeyfn, ht->freevalfn, ncap);
+	for (i = 0; i < ht->cap; i++) {
+		for (n = ht->nodes[i]; n != NULL; n = n->next)
+			htinsert(nht, n->key, n->val);
+	}
+
+	/* swap old hash table with new one */
+	list = ht->nodes;
+	ht->nodes = nht->nodes;
+	nht->nodes = list;
+
+	tmp = ht->cap;
+	ht->cap = nht->cap;
+	nht->cap = tmp;
+
+	tmp = ht->len;
+	ht->len = nht->len;
+	nht->len = tmp;
+
+	/* destroy new(old) hash table */
+	__htdestroy(nht, 0);
+}
+
+int
+htiterate(Htable *ht, Htiter *it)
+{
+	if (it->index == 0 && it->node == NULL) {
+		it->index = 0;
+		it->node = ht->nodes[0];
+	} else {
+		it->node = it->node->next;
+	}
+
+	while (it->index < ht->cap) {
+		while (it->node != NULL) {
+			return 1;
+			it->node = it->node->next;
+		}
+		it->index++;
+		it->node = ht->nodes[it->index];
+	}
+	return 0;
+}
--- /dev/null
+++ b/htable.h
@@ -1,0 +1,43 @@
+/*
+ * This work is dedicated to the public domain.
+ * See COPYING file for more information.
+ */
+
+typedef struct Htnode Htnode;
+typedef struct Htable Htable;
+typedef struct Htiter Htiter;
+
+typedef size_t	(KeyLenFn)(const void *key);
+typedef int	(KeyCmpFn)(const void *key1, const void *key2);
+typedef void	(FreeKeyFn)(void *ptr);
+typedef void	(FreeValFn)(void *ptr);
+
+struct Htnode {
+	void	*key;		/* key for the node */
+	void	*val;		/* value for the node */
+	Htnode	*next;		/* next node (open hash table) */
+};
+
+struct Htable {
+	unsigned int	  cap;		/* capacity */
+	unsigned int	  len;		/* length */
+	Htnode		**nodes;	/* list of nodes */
+	KeyLenFn	 *keylenfn;	/* key length function */
+	KeyCmpFn	 *keycmpfn;	/* key compare function */
+	FreeKeyFn	 *freekeyfn;
+	FreeValFn	 *freevalfn;
+};
+
+struct Htiter {
+	unsigned int	 index;
+	Htnode		*node;
+};
+
+Htable	*htcreate(KeyLenFn *keylenfn, KeyCmpFn *keycmpfn, FreeKeyFn *freekeyfn, FreeValFn *freevalfn, unsigned int cap);
+void	 htdestroy(Htable *ht);
+void	*htsearch(Htable *ht, void *key);
+int	 htinsert(Htable *ht, void *key, void *val);
+int 	 htremove(Htable *ht, void *key);
+int	 htsetkey(Htable *ht, void *oldkey, void *newkey);
+void 	 htresize(Htable *ht, unsigned int ncap);
+int	 htiterate(Htable *ht, Htiter *it);
--- /dev/null
+++ b/main.c
@@ -1,0 +1,758 @@
+/*
+ * This work is dedicated to the public domain.
+ * See COPYING file for more information.
+ */
+
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/select.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "utils.h"
+#include "htable.h"
+
+#define BUFFER_LEN	1024
+#define MAX_FD		1024
+
+#define NET_ADDEND	10
+#define USER_ADDEND	100
+
+#define FD_TYPE_LINKER	1
+#define FD_TYPE_CLONE	2
+
+typedef struct {
+	int	 fd;	/* fd of linker	*/
+	char	*name;	/* name		*/
+	char	*symb;	/* symbol	*/
+	char	*host;	/* hostname	*/
+	char	*port;	/* port		*/
+	char	*chan;	/* channel	*/
+} Network;
+
+static fd_set	 fdset;			/* fd_set (select)	*/
+static int	 nfds;			/* number of fds	*/
+
+static int	 fdnet	[MAX_FD];	/* fd -> network index	*/
+static int	 fdtype	[MAX_FD];	/* fd -> fd type	*/
+static int	 fdsuf	[MAX_FD];	/* fd -> nick suffix	*/
+
+static Network	*networks;		/* linked list of networks */
+static int	 netlen;		/* current network length  */
+static int	 netcap = NET_ADDEND;	/* total memory allocated  */
+
+/*
+ * hash table of users
+ * key   -> <nickname> + '[' + <network_symbol> + ']'
+ * value -> array of linked clones fds indexed
+ *	    corresponding to its connected network
+ */
+static Htable	 *users;	
+
+static int	 isrunning = 1;
+
+/* prototype */
+void	 print_table(void);
+void	 print_users(void);
+int	 fdadd(char *host, char *port);
+void	 fddel(int fd, char *msg);
+void	 netadd(char *name, char *symb, char *host, char *port, char *chan);
+void	 __netdel(int index);
+void	 netdel(char *name);
+int	 cloneadd(int netindex, char *nick);
+void	 clonesupdate(void);
+void	 useradd(char *unick, int nfd);
+void	 userdel(char *nick, char *msg);
+void	 append_netsymb(char *nick, int fd);
+int	*getuserfds(char *nick, int nfd);
+void	 privmsg_update(char *dst, char *src, int fd);
+void	 handle_server(int fd);
+void	 handle_fifo(int fd);
+
+void
+print_table(void)
+{
+	int i, *fds;
+	Htiter	it = {0};
+
+	if (netlen == 0) return;
+
+	/* print networks */
+	printf("Networks\t");
+	for (i = 0; i < netlen; i++)
+		printf("%s[%s](%d)\t", networks[i].name, networks[i].symb, networks[i].fd);
+	printf("\n");
+
+	while (htiterate(users, &it)) {
+		fds = (int *)it.node->val;
+		/* print user nick */
+		printf("%s\t", (char *)it.node->key);
+		/* print clones fd separated by tabs */
+		for (i = 0; i < netlen; i++) {
+			printf("%d", fds[i]);
+			if ((fds[i] > 0) && (fdsuf[fds[i]] > 0))
+				printf("(%d)", fdsuf[fds[i]]);
+			printf("\t");
+		}
+		printf("\n");
+	}
+}
+
+
+void
+print_users(void)
+{
+	Htiter	it = {0};
+	int	index = -1;
+	while (htiterate(users, &it)) {
+		if (index != (int)it.index) {
+			printf("\n%d", it.index);
+			index = (int)it.index;
+		}
+		printf(" -> %s", (char *)it.node->key);
+	}
+	printf("\n");
+}
+
+int
+fdadd(char *host, char *port)
+{
+	int fd;
+	if ((fd = dial(host, port)) == -1)
+		return -1;
+
+	FD_SET(fd, &fdset);
+	if (fd+1 > nfds)
+		nfds = fd+1;
+
+	return fd;
+}
+
+void
+fddel(int fd, char *msg)
+{
+	ircsend(fd, "QUIT :%s", msg);
+	close(fd);
+	FD_CLR(fd, &fdset);
+	fdnet[fd] = -1;
+}
+
+void
+netadd(char *name, char *symb, char *host, char *port, char *chan)
+{
+	int i, fd;
+	Network *n;
+	Htiter	it = {0};
+
+	/* resize */
+	if (netlen < netcap) {
+		netcap += NET_ADDEND;
+		networks = realloc(networks, (size_t)netcap * sizeof(Network));
+
+		while (htiterate(users, &it))
+			it.node->val = realloc(it.node->val, (size_t)netcap * sizeof(int));
+	}
+
+	/* if name or symbol or host:port is already taken */
+	for (i = 0; i < netlen; i++) {
+		if (strcmp(networks[i].name, name) == 0) {
+			printf("error: network name '%s' already taken\n", name);
+			return;
+		}
+		if (strcmp(networks[i].symb, symb) == 0) {
+			printf("error: network symbol '%s' already taken\n", symb);
+			return;
+		}
+		/* if ((strcmp(networks[i].host, host) == 0)
+		&& (strcmp(networks[i].port, port) == 0)) {
+			printf("error: network host '%s' and port '%s' already taken by network '%s'\n", host, port, networks[i].name);
+			return;
+		}*/
+	}
+
+	printf("info: adding network '%s'\n", name);
+	if ((fd = fdadd(host, port)) == -1)
+		return;
+	/* send NICK and USER commands */
+	ircsend(fd, "NICK linker");
+	ircsend(fd, "USER linker localhost %s :%s", host, name);
+	/* add a network */
+	fdnet[fd] = netlen;
+	fdtype[fd] = FD_TYPE_LINKER;
+
+	n = &networks[netlen];
+	n->fd = fd;
+	n->name = strdup(name);
+	n->symb = strdup(symb);
+	n->host = strdup(host);
+	n->port = strdup(port);
+	n->chan = strdup(chan);
+	netlen++;
+}
+
+void
+__netdel(int index)
+{
+	Network *n = &networks[index];
+	free(n->name);
+	free(n->symb);
+	free(n->host);
+	free(n->port);
+	free(n->chan);
+}
+
+void
+netdel(char *name)
+{
+	int i, netindex = -1, *fds;
+	char quit_msg[BUFFER_LEN];
+
+	Htiter	it	= {0};	/* current iterator */
+	Htiter	lastit	= {0};	/* last iterator */
+
+	/* check if the netname exists */
+	for (i = 0; i < netlen; i++) {
+		if (strcmp(name, networks[i].name) == 0) {
+			netindex = i;
+			break;
+		}
+	}
+	if (netindex == -1) {
+		printf("error: network name '%s' doesn't exist\n", name);
+		return;
+	}
+
+	printf("info: deleting network '%s'\n", name);
+
+	/* set the quit msg */
+	snprintf(quit_msg, BUFFER_LEN, "unlinking network %s", name);
+
+	/* set all fds with last network index to the current index. */
+	for (i = 0; i < nfds; i++) {
+		if (fdnet[i] == netlen-1)
+			fdnet[i] = netindex;
+	}
+
+	while (htiterate(users, &it)) {
+		fds = (int *)it.node->val;
+		/* delete the user */
+		if (fds[netindex] == -1) {
+			userdel(it.node->key, quit_msg);
+			/* this node is deleted and has no next */
+			it = lastit;
+		/* delete the clone */
+		} else {
+			if (fds[netindex] > 0)
+				fddel(fds[netindex], quit_msg);
+
+			/* swap last index with the netindex */
+			fds[netindex] = fds[netlen-1];
+			fds[netlen-1] = 0;
+		}
+		lastit = it;
+	}
+
+	fddel(networks[netindex].fd, quit_msg);
+	 __netdel(netindex);
+
+	/* swap the network with the last */
+	networks[netindex] = networks[netlen-1];
+	netlen--;
+}
+
+int
+cloneadd(int netindex, char *nick)
+{
+	int fd;
+	Network *n = &networks[netindex];
+
+	printf("info: adding clone '%s' on network '%s'\n", nick, n->name);
+	if ((fd = fdadd(n->host, n->port)) == -1)
+		return -1;
+	/* send NICK and USER commands */
+	ircsend(fd, "NICK %s", nick);
+	ircsend(fd, "USER username localhost %s :real name", n->host);
+	/* add a user */
+	fdnet[fd] = netindex;
+	fdtype[fd] = FD_TYPE_CLONE;
+	fdsuf[fd] = 0;
+	return fd;
+}
+
+/* update clones in the last network added */
+void
+clonesupdate(void)
+{
+	int	*fds;
+	Htiter	 it = {0};
+
+	while (htiterate(users, &it)) {
+		fds = (int *)it.node->val;
+		/* netlen-1 can be occupied if networks are added quickly
+		 * and has not enough time to connect to it's server */
+		if (fds[netlen-1] == 0)
+			fds[netlen-1] = cloneadd(netlen-1, it.node->key);
+	}
+}
+
+void
+useradd(char *unick, int nfd)
+{
+	int i, *fds;
+	Network *n;
+	size_t l;
+	char *nick;
+
+	n = &networks[fdnet[nfd]];
+
+	/* too long name */
+	l = strlen(unick) + strlen(n->symb) + 2 + 1;
+	if (l > 16)
+		return;
+
+	nick = ecalloc(l, sizeof(char));
+	fds = ecalloc((size_t)netcap, sizeof(int));
+	sprintf(nick, "%s[%s]", unick, n->symb);
+
+	/* resize */
+	if ((users->cap - users->len) < USER_ADDEND)
+		htresize(users, users->cap + USER_ADDEND);
+
+	for (i = 0; i < netlen; i++) {
+		/* set -1 on user network */
+		if (i == fdnet[nfd]) {
+			fds[i] = -1;
+		} else {
+			fds[i] = cloneadd(i, nick);
+		}
+	}
+
+	if (htinsert(users, nick, fds) == -1)
+		fatal("error: user '%s' already exist", nick);
+}
+
+void
+userdel(char *nick, char *msg)
+{
+	int *fds, i;
+	if ((fds = htsearch(users, nick)) == NULL)
+		return;
+
+	for (i = 0; i < netlen; i++) {
+		if (fds[i] > 0)
+			fddel(fds[i], msg);
+	}
+	htremove(users, nick);
+}
+
+void
+append_netsymb(char *nick, int fd)
+{
+	strcat(nick, "[");
+	strcat(nick, networks[fdnet[fd]].symb);
+	strcat(nick, "]");
+}
+
+int *
+getuserfds(char *nick, int nfd)
+{
+	unsigned int l;
+	int *fds = NULL;
+
+	for (l = 0; nick[strlen(nick)-l-1] == '_'; l++);
+	if (l > 0)
+		nick[strlen(nick)-l] = '\0';
+
+	fds = htsearch(users, nick);
+	/* if match but suffix doesn't match */
+	if ((fds != NULL) && (fdsuf[fds[fdnet[nfd]]] != (int)l))
+		fds = NULL;
+
+	/* add the removed delimiter */
+	if (l > 0)
+		nick[strlen(nick)] = '_';
+
+	return fds;
+}
+
+/* trim all the nicknames to original nick
+ * this src will be destructed */
+void
+privmsg_update(char *dst, char *src, int fd)
+{
+	char d;		/* delimiter */
+	char *n;
+
+	while (src != NULL) {
+		n = strpbrk(src, " :;,<>@&~%+\\");
+		if (n == NULL) {
+			d = '\0';
+		} else {
+			d = *n;
+			*n = '\0';
+			n++;
+		}
+
+		/* check if the word is nick */
+		if (getuserfds(src, fd) != NULL)
+			*strrchr(src, '[') = '\0';
+
+		strcat(dst, src);
+		strncat(dst, &d, 1);
+
+		src = n;
+	}
+}
+
+void
+handle_server(int fd)
+{
+	char backup [BUFFER_LEN];
+	char buffer [BUFFER_LEN];
+	char *buf;
+	char *cmd;
+	char *nick;
+	int i;
+
+	char linker[BUFFER_LEN] = "linker";
+
+	if (dreadline(fd, buffer, sizeof(buffer)) < 1)
+		fatal("error: %d: remote host closed connection: %s", fd, strerror(errno));
+
+	strcpy(backup, buffer);
+	buf = buffer;
+
+	/* remove CRLFs */
+	for (i = 0; i < (int)strlen(buf); i++) {
+		if (buf[i] == '\r' || buf[i] == '\n') {
+			buf[i] = '\0';
+			break;
+		}
+	}
+
+	/* first column */
+	cmd = split(&buf, ' ');
+	if (strcmp(cmd, "NOTICE") == 0) {
+		return;
+	} else if (strcmp(cmd, "ERROR") == 0) {
+		goto printbuffer;
+	} else if (strcmp(cmd, "PING") == 0) {
+		ircsend(fd, "PONG %s", buf);
+		return;
+	}
+	/* strip nick from first column */
+	nick = split(&cmd, '!');
+	if (nick[0] == ':')
+		nick++;
+
+	/* second column */
+	cmd = split(&buf, ' ');
+	/* these messages are handled by linker */
+	if (fdtype[fd] == FD_TYPE_CLONE) {
+		if ((strcmp(cmd, "353") == 0)
+		|| (strcmp(cmd, "JOIN") == 0)
+		|| (strcmp(cmd, "QUIT") == 0)
+		|| (strcmp(cmd, "PART") == 0)
+		|| (strcmp(cmd, "KICK") == 0)
+		|| (strcmp(cmd, "NICK") == 0))
+			return;
+	}
+
+	/* set linker nick */
+	for (i = 0; i < fdsuf[fd]; i++)
+		strcat(linker, "_");
+	/* ignore all the info messages */
+	if ((strcmp(cmd, "002") == 0)
+	|| (strcmp(cmd, "003") == 0)
+	|| (strcmp(cmd, "004") == 0)
+	|| (strcmp(cmd, "005") == 0)
+	|| (strcmp(cmd, "003") == 0)
+	|| (strcmp(cmd, "251") == 0)
+	|| (strcmp(cmd, "252") == 0)
+	|| (strcmp(cmd, "254") == 0)
+	|| (strcmp(cmd, "255") == 0)
+	|| (strcmp(cmd, "265") == 0)
+	|| (strcmp(cmd, "266") == 0)
+	|| (strcmp(cmd, "250") == 0)
+	|| (strcmp(cmd, "375") == 0)
+	|| (strcmp(cmd, "372") == 0)
+	|| (strcmp(cmd, "376") == 0)
+	|| (strcmp(cmd, "396") == 0)
+	|| (strcmp(cmd, "366") == 0)
+	|| (strcmp(cmd, "MODE") == 0)
+	|| (strcmp(cmd, "NOTICE") == 0)) {
+		return;
+	} else if (strcmp(cmd, "433") == 0) {	/* Nickname already in use */
+		split(&buf, ' ');
+		nick = split(&buf, ' ');
+		strcat(nick, "_");
+		fdsuf[fd]++;
+		ircsend(fd, "NICK %s", nick);
+		return;
+	} else if (strcmp(cmd, "001") == 0) {
+		ircsend(fd, "JOIN %s", networks[fdnet[fd]].chan);
+		return;
+
+	/* ========================= only linker ========================= */
+	} else if (strcmp(cmd, "353") == 0) {
+		char *nick;
+		split(&buf, ':');
+		clonesupdate();
+		while (*(nick = split(&buf, ' ')) != '\0') {
+			if (*nick == '@'
+			|| *nick == '&'
+			|| *nick == '~'
+			|| *nick == '%'
+			|| *nick == '+'
+			|| *nick == '\\')
+				nick++;
+			if (strcmp(nick, linker) != 0)
+				useradd(nick, fd);
+		}
+		return;
+	} else if (strcmp(cmd, "JOIN") == 0) {
+		if ((strcmp(nick, linker) != 0)
+		&& (getuserfds(nick, fd) == NULL))	/* if not clone */
+			useradd(nick, fd);
+		return;
+	} else if ((strcmp(cmd, "QUIT") == 0)
+		|| (strcmp(cmd, "PART") == 0)) {
+		append_netsymb(nick, fd);
+		userdel(nick, buf);
+		return;
+	} else if (strcmp(cmd, "NICK") == 0) {
+		int *fds, i;
+		char *newnick;
+
+		append_netsymb(nick, fd);
+		if ((fds = htsearch(users, nick)) == NULL)
+			return;
+
+		/* set buf to new nick */
+		split(&buf, ':');
+		/* allocate a newnick and append the netsym and then replace the old */
+		newnick = ecalloc(strlen(buf) + strlen(networks[fdnet[fd]].symb) + 2 + 1, sizeof(char));
+		sprintf(newnick, "%s[%s]", buf, networks[fdnet[fd]].symb);
+		htsetkey(users, nick, newnick);
+
+		for (i = 0; i < netlen; i++) {
+			if (fds[i] > 0)
+				ircsend(fds[i], "NICK %s", newnick);
+		}
+		return;
+	} else if (strcmp(cmd, "KICK") == 0) {
+		/* :<nick_which_is_kicking>!~user@host KICK <channel> <nick_which_has_been_kicked> :<kick_msg> */
+		int *fds;
+		char *chan = split(&buf, ' ');
+		char *user = split(&buf, ' ');
+		char quit_msg[BUFFER_LEN];
+
+		/* set the quit msg */
+		snprintf(quit_msg, BUFFER_LEN, "kicked by %s", nick);
+
+		if (strcmp(user, linker) == 0) {
+			netdel(networks[fdnet[fd]].name);
+			return;
+		}
+
+		/* delete the user if the message from the same network */
+		if ((fds = getuserfds(user, fd)) == NULL) {
+			append_netsymb(user, fd);
+			userdel(user, quit_msg);
+			return;
+		}
+
+		/* remove netsymb and suffix */
+		*strrchr(user, '[') = '\0';
+
+		/* get the original user fd index */
+		for (i = 0; i < netlen; i++) {
+			if (fds[i] == -1)
+				break;
+		}
+
+		/* set buf to msg */
+		split(&buf, ':');	
+		/* send notice in the channel through linker */
+		ircsend(networks[i].fd, "PRIVMSG %s :%s was kicked out by %s from network %s %s [%s]",
+				networks[i].chan, user, nick, networks[fdnet[fd]].name, chan, buf);
+		
+		/* close the kicked fd */
+		fddel(fds[fdnet[fd]], quit_msg);
+		fds[fdnet[fd]] = -2;
+		return;
+	/* ========================= only linker ========================= */
+
+	} else if (strcmp(cmd, "PRIVMSG") == 0) {
+		char msg[BUFFER_LEN] = "";
+		int *fds;
+
+		if (fdtype[fd] == FD_TYPE_LINKER) {
+			append_netsymb(nick, fd);
+			if ((fds = htsearch(users, nick)) == NULL)
+				return;
+
+			split(&buf, ':');	/* set buf to msg */
+			privmsg_update(msg, buf, fd);
+			for (i = 0; i < netlen; i++) {
+				if (fds[i] > 0)
+					ircsend(fds[i], "PRIVMSG %s :%s", networks[i].chan, msg);
+			}
+		} else {
+			char *netsymb;
+			char *user = split(&buf, ' ');
+
+			/* ignore messages from channel (it is handled by linker) */
+			if ((user[0] == '#') || (user[0] == '&'))
+				return;
+
+			append_netsymb(nick, fd);
+			if ((fds = htsearch(users, nick)) == NULL)
+				return;
+
+			/* split user nick and network symbol */
+			*strrchr(user, ']') = '\0';
+			netsymb = strrchr(user, '[');
+			*netsymb++ = '\0';
+
+			/* get the network index */
+			for (i = 0; i < netlen; i++) {
+				if (strcmp(netsymb, networks[i].symb) == 0)
+					break;
+			}
+
+			split(&buf, ':');	/* set buf to msg */
+			privmsg_update(msg, buf, fd);
+			ircsend(fds[i], "PRIVMSG %s :%s", user, msg);
+		}
+		return;
+	}
+
+printbuffer:
+	printf("%d: %s\n", fd, backup);
+}
+
+void
+handle_fifo(int fd)
+{
+	char buffer[BUFFER_LEN];
+	char *buf;
+	char *cmd;
+
+	if (dreadline(fd, buffer, sizeof(buffer)) == -1)
+		fatal("error: dreadline() failed");
+
+	/* printf("fifo: %s\n", buffer); */
+
+	buf = buffer;
+	cmd = split(&buf, ' ');
+	if (strcmp(cmd, "netadd") == 0) {
+		char *name = split(&buf, ' ');
+		char *symb = split(&buf, ' ');
+		char *host = split(&buf, ' ');
+		char *port = split(&buf, ' ');
+		char *chan = buf;
+		if ((*name == '\0') ||  (*symb == '\0') ||  (*host == '\0') ||  (*port == '\0') || (*chan == '\0'))
+			printf("usage: netadd <name> <symbol> <hostname> <port> <channel>\n");
+		netadd(name, symb, host, port, chan);
+	} else if (strcmp(cmd, "netdel") == 0) {
+		char *name = buf;
+		if (*name == '\0')
+			printf("usage: netdel <name>\n");
+		netdel(name);
+	} else if (strcmp(cmd, "print") == 0) {
+		print_table();
+	} else if (strcmp(cmd, "users") == 0) {
+		print_users();
+	} else if (strcmp(cmd, "exit") == 0) {
+		isrunning = 0;
+	} else {
+		printf("error: %s is not a command\n", cmd);
+	}
+}
+
+int
+main(int argc, char *argv[])
+{
+	int		fifofd, r, i;
+	fd_set		rdset;
+	struct stat	fifo_st;
+
+	/* check arguments */
+	if (argc != 2)
+		fatal("usage: %s <fifo>", argv[0]);
+
+	/* warning */
+	printf("WARNING: This software is provided 'as-is', without any express or "
+		"implied warranty.\nIn no event shall the author(s) be liable "
+		"for any damages arising from the use of this software.\n");
+
+	/* init networks and users */
+	networks = ecalloc((size_t)netcap, sizeof(Network));
+	users = htcreate((KeyLenFn *)strlen, (KeyCmpFn *)strcmp, free, free, USER_ADDEND);
+	/* init fdnet array to -1 */
+	for (i = 0; i < MAX_FD; i++)
+		fdnet[i] = -1;
+
+	/* crate or/and open fifo */
+	if (stat(argv[1], &fifo_st) == 0) {
+		if (!(fifo_st.st_mode & S_IFIFO))
+			fatal("error: file %s is not a fifo", argv[1]);
+	} else if (mkfifo(argv[1], S_IRWXU) != 0) {
+		fatal("error: cannot create fifo file %s", argv[1]);
+	}
+	fifofd = open(argv[1], O_RDWR);
+	if (fifofd < 0)
+		fatal("error: cannot open() '%s'", argv[1]);
+
+	/* set stdout to unbufferd */
+	setvbuf(stdout, NULL, _IONBF, 0);
+	/* initialize fdset */
+	FD_ZERO(&fdset);
+	FD_SET(fifofd, &fdset);
+	nfds = fifofd + 1;
+
+	/* select loop */
+	while (isrunning) {
+		rdset = fdset;
+		r = select(nfds, &rdset, 0, 0, NULL);
+		if (r < 0) {
+			if (errno == EINTR)
+				continue;
+			fatal("error: select: %s", strerror(errno));
+		} else if (r == 0) {
+			fatal("error: select() timeout");
+		}
+
+		for (i = 0; i < nfds; i++) {
+			if (FD_ISSET(i, &rdset)) {
+				if (i == fifofd)
+					handle_fifo(i);
+				else
+					handle_server(i);
+
+				/* handle one fd at one time because FD_CLR */
+				break;
+			}
+		}
+	}
+
+	for (i = 0; i < nfds; i++) {
+		if (fdnet[i] > 0) {
+			printf("shutting down %d\n", i);
+			fddel(i, "linker shutting down");
+		}
+	}
+	/* delete all the users */
+	htdestroy(users);
+
+	/* delete all the networks */
+	for (i = 0; i < netlen; i++)
+		__netdel(i);
+
+	free(networks);
+	return 0;
+}
--- /dev/null
+++ b/utils.c
@@ -1,0 +1,152 @@
+/*
+ * This work is dedicated to the public domain.
+ * See COPYING file for more information.
+ */
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <unistd.h>
+
+#include "utils.h"
+
+void
+fatal(const char *fmt, ...)
+{
+	va_list ap;
+	va_start(ap, fmt);
+	vfprintf(stderr, fmt, ap);
+	va_end(ap);
+
+	if (fmt[0] && fmt[strlen(fmt)-1] == ':') {
+		fputc(' ', stderr);
+		perror(NULL);
+	} else {
+		fputc('\n', stderr);
+	}
+	exit(1);
+}
+
+void *
+ecalloc(size_t nmemb, size_t size)
+{
+	void *p;
+	if ((p = calloc(nmemb, size)) == NULL)
+		fatal("calloc:");
+	return p;
+}
+
+char*
+split(char **str, char ch)
+{
+	char *token = *str;
+
+	if (**str == '\0') return *str;
+
+	while (**str != ch && **str != '\0')
+		(*str)++;
+	if (**str == '\0')
+		return token;
+	**str = '\0';
+	(*str)++;
+	while(**str == ch && **str != '\0')
+		(*str)++;
+	return token;
+}
+
+void
+ircsend(int fd, const char *format, ...)
+{
+	static char buf[4096];
+	va_list args;
+	va_start(args, format);
+	vsnprintf(buf, sizeof(buf), format, args);
+	va_end(args);
+	strcat(buf, "\r\n");
+	send(fd, buf, strlen(buf), 0);
+}
+
+int
+dial(char *host, char *port)
+{
+	static struct addrinfo hints, *res = NULL, *res0;
+	int fd = -1, r;
+
+	memset(&hints, 0, sizeof(hints));
+	hints.ai_family = AF_UNSPEC;
+	hints.ai_socktype = SOCK_STREAM;
+
+	if ((r = getaddrinfo(host, port, &hints, &res0)) != 0) {
+		printf("error: getaddrinfo: %s\n", gai_strerror(r));
+		return -1;
+	}
+
+	for (res = res0; res != NULL; res = res->ai_next) {
+		if ((fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) < 0)
+			continue;
+		if (connect(fd, res->ai_addr, res->ai_addrlen) != 0) {
+			close(fd);
+			fd = -1;
+			continue;
+		}
+		break;
+	}
+	if (fd == -1) {
+		printf("error: cannot connect to host '%s'\n", host);
+		return -1;
+	}
+	freeaddrinfo(res0);
+	return fd;
+}
+
+int
+isstrnumber(char *str)
+{
+	int i;
+	for (i = 0; str[i]!= '\0'; i++) {
+		if (isdigit(str[i]) == 0)
+			return 0;
+	}
+	return 1;
+}
+
+size_t
+strlcpy(char *dst, const char *src, size_t size)
+{
+	size_t i;
+
+	if (size == 0)
+		return 0;
+
+	for (i = 0; i < size; i++) {
+		dst[i] = src[i];
+		if (src[i] == '\0')
+			return i;
+	}
+
+	dst[size-1] = '\0';
+	return size-1;
+}
+
+
+ssize_t
+dreadline(int fd, char *buffer, size_t size)
+{
+	size_t i = 0;
+	char c;
+	ssize_t l;
+
+	do {
+		if ((l = read(fd, &c, sizeof(char))) != sizeof(char))
+			return l;
+		buffer[i++] = c;
+	} while ((i < size) && (c != '\n') && (c != '\0'));
+
+	buffer[i-1] = '\0';
+	return (ssize_t)i;
+}
--- /dev/null
+++ b/utils.h
@@ -1,0 +1,13 @@
+/*
+ * This work is dedicated to the public domain.
+ * See COPYING file for more information.
+ */
+
+void	 fatal(const char *fmt, ...);
+void	*ecalloc(size_t nmemb, size_t size);
+char	*split(char **str, char ch);
+void	 ircsend(int fd, const char *format, ...);
+int	 dial(char *host, char *port);
+int	 isstrnumber(char *str);
+size_t	 strlcpy(char *dst, const char *src, size_t size);
+ssize_t	 dreadline(int fd, char *buffer, size_t size);