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);