ticl

tiny irc channel linker
git clone git://git.ircforever.org/ticl
Log | Files | Refs | Submodules | README | LICENSE

commit 5ff9649e6c51bae0dd09f218e4943fa5f4d458be
Author: libredev <libredev@ircforever.org>
Date:   Mon, 14 Nov 2022 05:30:34 +0530

initial commit

Diffstat:
ACOPYING | 13+++++++++++++
AMakefile | 50++++++++++++++++++++++++++++++++++++++++++++++++++
AREADME | 198+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ahtable.c | 223+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ahtable.h | 43+++++++++++++++++++++++++++++++++++++++++++
Amain.c | 758+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Autils.c | 152+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Autils.h | 13+++++++++++++
8 files changed, 1450 insertions(+), 0 deletions(-)

diff --git a/COPYING b/COPYING @@ -0,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 diff --git a/Makefile b/Makefile @@ -0,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 diff --git a/README b/README @@ -0,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. diff --git a/htable.c b/htable.c @@ -0,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; +} diff --git a/htable.h b/htable.h @@ -0,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); diff --git a/main.c b/main.c @@ -0,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; +} diff --git a/utils.c b/utils.c @@ -0,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; +} diff --git a/utils.h b/utils.h @@ -0,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);