commit ac8e6845d4d8b237241df25cb36fc9dc43231430
parent 956ddc8b6352317b2527d48b6b3ca98c311cb56b
Author: Hiltjo Posthuma <hiltjo@codemadness.org>
Date: Sun, 9 Nov 2014 14:56:44 +0000
add initial hysteria-namelist
this makes a "names" file containing a simple list for each channel. it
will keep a global list of all users (for nick renames) and a list per
channel.
At the moment it keeps track off:
- handle name reply (353).
- user joins channel.
- user leaves channel.
- user is kicked from channel.
- user changes nickname.
- user quits network.
Diffstat:
M | Makefile | | | 17 | ++++++++++++----- |
A | hysteria-namelist.c | | | 442 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
2 files changed, 454 insertions(+), 5 deletions(-)
diff --git a/Makefile b/Makefile
@@ -1,10 +1,10 @@
include config.mk
-SRC = hysteria-highlight.c
+SRC = hysteria-highlight.c hysteria-namelist.c
HDR =
OBJ = ${SRC:.c=.o}
-all: options hysteria-highlight
+all: options hysteria-highlight hysteria-namelist
options:
@echo hysteria build options:
@@ -14,17 +14,21 @@ options:
.c.o:
@echo CC $<
- @${CC} -c ${CFLAGS} $<
+ @${CC} -c -o $@ $< ${CFLAGS}
${OBJ}: config.mk
hysteria-highlight: ${OBJ}
@echo CC -o $@
- @${CC} -o $@ ${OBJ} ${LDFLAGS}
+ @${CC} -o $@ $@.o ${LDFLAGS}
+
+hysteria-namelist: ${OBJ}
+ @echo CC -o $@
+ @${CC} -o $@ $@.o ${LDFLAGS}
clean:
@echo cleaning
- @rm -f hysteria-highlight ${OBJ}
+ @rm -f hysteria-highlight hysteria-namelist ${OBJ}
install: all
mkdir -p ${DESTDIR}${PREFIX}/bin
@@ -34,6 +38,7 @@ install: all
hysteria-connect \
hysteria-highlight \
hysteria-monitor \
+ hysteria-namelist \
hysteria-waitfile \
${DESTDIR}${PREFIX}/bin
chmod 755 \
@@ -42,6 +47,7 @@ install: all
${DESTDIR}${PREFIX}/bin/hysteria-connect \
${DESTDIR}${PREFIX}/bin/hysteria-highlight \
${DESTDIR}${PREFIX}/bin/hysteria-monitor \
+ ${DESTDIR}${PREFIX}/bin/hysteria-namelist \
${DESTDIR}${PREFIX}/bin/hysteria-waitfile
uninstall:
@@ -51,6 +57,7 @@ uninstall:
${DESTDIR}${PREFIX}/bin/hysteria-connect \
${DESTDIR}${PREFIX}/bin/hysteria-highlight \
${DESTDIR}${PREFIX}/bin/hysteria-monitor \
+ ${DESTDIR}${PREFIX}/bin/hysteria-namelist \
${DESTDIR}${PREFIX}/bin/hysteria-waitfile
.PHONY: all hysteria options clean dist install uninstall
diff --git a/hysteria-namelist.c b/hysteria-namelist.c
@@ -0,0 +1,442 @@
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#define IRC_CHANNEL_MAX 200
+#define IRC_NAME_MAX 50
+
+typedef struct User User;
+struct User {
+ char nickname[IRC_NAME_MAX];
+ User *next;
+};
+
+typedef struct ChannelUser ChannelUser;
+struct ChannelUser {
+ User *user;
+ ChannelUser *next;
+};
+
+typedef struct Channel Channel;
+struct Channel {
+ char name[IRC_CHANNEL_MAX]; /* channel name (normalized) */
+ ChannelUser *names; /* users in channel (linked-list) */
+ char namespath[PATH_MAX]; /* names path */
+ Channel *next;
+};
+
+static User *users; /* linked-list of all users */
+static Channel *channels; /* linked-list of channels */
+
+static void
+eprint(const char *s) {
+ fputs(s, stderr);
+ exit(EXIT_FAILURE);
+}
+
+static void *
+ecalloc(size_t nmemb, size_t size)
+{
+ void *p;
+ if(!(p = calloc(nmemb, size)))
+ eprint("cannot allocate memory\n");
+ return p;
+}
+
+static void
+create_filepath(char *filepath, size_t len, const char *path,
+ const char *channel, const char *suffix)
+{
+ const char *e = "path to directory too long\n";
+ int r;
+
+ if(channel[0]) {
+ r = snprintf(filepath, len, "%s/%s", path, channel);
+ if(r < 0 || (size_t)r >= len)
+ eprint(e);
+ r = snprintf(filepath, len, "%s/%s/%s", path, channel, suffix);
+ if(r < 0 || (size_t)r >= len)
+ eprint(e);
+ } else {
+ r = snprintf(filepath, len, "%s/%s", path, suffix);
+ if(r < 0 || (size_t)r >= len)
+ eprint(e);
+ }
+}
+
+static void
+channel_normalize_path(char *s)
+{
+ for(; *s; s++) {
+ if(isalpha(*s))
+ *s = tolower(*s);
+ else if(!isdigit(*s) && !strchr(".#&", *s))
+ *s = '_';
+ }
+}
+
+static void
+channel_normalize_name(char *s)
+{
+ char *p;
+
+ if(*s == '&' || *s == '#')
+ s++;
+ for(p = s; *s; s++) {
+ if(!strchr(" ,&#\x07", *s)) {
+ *p = *s;
+ p++;
+ }
+ }
+ *p = '\0';
+}
+
+static Channel *
+channel_new(const char *name)
+{
+ Channel *c;
+ char channelpath[PATH_MAX];
+
+ strlcpy(channelpath, name, sizeof(channelpath));
+ channel_normalize_path(channelpath);
+
+ c = ecalloc(1, sizeof(Channel));
+ c->next = NULL;
+ strlcpy(c->name, name, sizeof(c->name));
+ channel_normalize_name(c->name);
+
+ create_filepath(c->namespath, sizeof(c->namespath), ".",
+ channelpath, "names");
+ return c;
+}
+
+static Channel *
+channel_add(const char *name)
+{
+ Channel *c;
+
+ c = channel_new(name);
+ if(!channels) {
+ channels = c;
+ } else {
+ c->next = channels;
+ channels = c;
+ }
+ return c;
+}
+
+static int
+user_cmp(const char *s1, const char *s2)
+{
+ for(; *s1 && strchr("+|@%&~", *s1); s1++);
+ for(; *s2 && strchr("+|@%&~", *s2); s2++);
+ return strcmp(s1, s2);
+}
+
+static User *
+user_find(const char *name)
+{
+ User *u;
+
+ for(u = users; u; u = u->next) {
+ if(!user_cmp(u->nickname, name))
+ return u;
+ }
+ return NULL;
+}
+
+static User *
+user_add(const char *name)
+{
+ User *n, *u;
+
+ n = ecalloc(1, sizeof(User));
+ strlcpy(n->nickname, name, sizeof(n->nickname));
+ if(!users) {
+ users = n;
+ return n;
+ }
+ for(u = users; u; u = u->next) {
+ if(!u->next) {
+ u->next = n;
+ return n;
+ }
+ }
+ return NULL;
+}
+
+static ChannelUser *
+channel_user_find(Channel *c, User *f)
+{
+ ChannelUser *cu;
+
+ for(cu = c->names; cu; cu = cu->next) {
+ if(cu->user == f)
+ return cu;
+ }
+ return NULL;
+}
+
+static int
+channel_user_writefile(Channel *c)
+{
+ ChannelUser *u;
+ FILE *fp;
+
+ if(!(fp = fopen(c->namespath, "w"))) {
+ fprintf(stderr, "fopen: %s: %s\n", c->namespath, strerror(errno));
+ return -1;
+ }
+ for(u = c->names; u; u = u->next)
+ fprintf(fp, "%s\n", u->user->nickname);
+ fclose(fp);
+ return 0;
+}
+
+static int
+user_changenick(const char *nickfrom, const char *nickto)
+{
+ User *u;
+ Channel *c;
+ int r = 0;
+
+ if(!(u = user_find(nickfrom)))
+ return -1;
+ strlcpy(u->nickname, nickto, sizeof(u->nickname));
+ /* check which channel lists need updating */
+ for(c = channels; c; c = c->next) {
+ if(channel_user_find(c, u)) {
+ if(channel_user_writefile(c) == -1)
+ r = -1;
+ }
+ }
+ return r;
+}
+
+static int
+channel_user_add(Channel *c, const char *name)
+{
+ ChannelUser *n, *u;
+ User *f;
+
+ /* skip whitespace */
+ for(; *name && isspace(*name); name++);
+ if(!*name)
+ return -1;
+ if(!(f = user_find(name)))
+ f = user_add(name);
+ if(channel_user_find(c, f))
+ return -1; /* exist */
+
+ n = ecalloc(1, sizeof(ChannelUser));
+ n->user = f;
+ if(!c->names) {
+ c->names = n;
+ return 0;
+ } else {
+ for(u = c->names; u; u = u->next) {
+ if(!u->next) {
+ u->next = n;
+ return 0;
+ }
+ }
+ }
+ return -1;
+}
+
+static Channel *
+channel_find(const char *name)
+{
+ Channel *c;
+ char chan[IRC_CHANNEL_MAX];
+
+ strlcpy(chan, name, sizeof(chan));
+ channel_normalize_name(chan);
+ for(c = channels; c; c = c->next) {
+ if(!strcmp(chan, c->name))
+ return c;
+ }
+ return NULL;
+}
+
+static int
+user_rm(User *f)
+{
+ User *u, *t, *prev = NULL;
+
+ for(u = users; u; prev = u, u = u->next) {
+ if(u == f) {
+ t = u->next;
+ free(u);
+ if(!prev)
+ users = t;
+ else
+ prev->next = t;
+ return 0;
+ }
+ }
+ return -1;
+}
+
+static int
+channel_user_rm(Channel *c, User *f)
+{
+ ChannelUser *u, *p = NULL, *t;
+
+ for(u = c->names; u; p = u, u = u->next) {
+ if(f == u->user) {
+ t = u->next;
+ if(p)
+ p->next = t;
+ else
+ c->names = t;
+ free(u);
+ return 0;
+ }
+ }
+ return -1;
+}
+
+static int
+channel_user_leave(Channel *c, const char *name)
+{
+ User *f;
+ ChannelUser *cu;
+ int r;
+
+ if(!(f = user_find(name)))
+ return -1;
+ r = channel_user_rm(c, f);
+ /* check if user isn't visible in any channels
+ * if so remove from global list. */
+ for(c = channels; c; c = c->next) {
+ for(cu = c->names; cu; cu = cu->next) {
+ if(cu->user == f)
+ return r;
+ }
+ }
+ return (r != -1 && user_rm(f) != -1) ? 0 : -1;
+}
+
+static int
+user_quit(const char *name)
+{
+ Channel *c;
+ User *f = NULL;
+
+ if(!(f = user_find(name)))
+ return -1; /* not found */
+ /* remove user from all channels */
+ for(c = channels; c; c = c->next)
+ channel_user_rm(c, f);
+ /* remove from global list */
+ return user_rm(f);
+}
+
+
+static int
+proc_server_namereply(char *chan, char *users)
+{
+ Channel *c;
+ char *p, *t;
+
+ p = chan;
+ if(!strncmp(p, "= ", 2))
+ p += 2;
+ if(!(c = channel_find(p)))
+ return -1;
+
+ for(t = p = users; p && t; p = t + 1) {
+ if((t = strchr(p, ' ')))
+ *t = '\0';
+ channel_user_add(c, p);
+ if(t)
+ *t = ' ';
+ }
+ return channel_user_writefile(c);
+}
+
+int
+main(void) {
+ Channel *c;
+ size_t siz, i;
+ ssize_t n;
+ char *nick, *chan, *cmd, *text, *t, *p, *line = NULL;
+
+ while((n = getline(&line, &siz, stdin)) > 0) {
+ if(!(p = strchr(line, ':')))
+ continue;
+ /* strip crlf */
+ for(; n > 0 && (line[n - 1] == '\n' || line[n - 1] == '\r'); n--)
+ line[n - 1] = '\0';
+
+ nick = cmd = text = chan = "";
+ for(i = 0; ; i++) {
+ t = strchr(p, ' ');
+ if(i == 0)
+ nick = p;
+ else if(i == 1)
+ cmd = p;
+ else if(i == 2)
+ text = p;
+ if(!t)
+ break;
+ if(i < 2)
+ *t = '\0';
+ p = t + 1;
+ }
+ if(*nick == ':')
+ nick++;
+ if((p = strchr(nick, '!')))
+ *p = '\0';
+ if(*text == ':')
+ text++;
+
+ if(*text == '#' || *text == '&') {
+ chan = text;
+ text = "";
+ if((p = strchr(chan, ' '))) {
+ *p = '\0';
+ text = p + 1;
+ if(*text == ':')
+ text++;
+ }
+ }
+ /* name reply */
+ if(strcmp(cmd, "353") == 0) {
+ t = strchr(text, '=');
+ p = strchr(text, ':');
+ if(t && p)
+ proc_server_namereply(t, p);
+ } else if(strcmp(cmd, "NICK") == 0) {
+ user_changenick(nick, text);
+ } else if(strcmp(cmd, "JOIN") == 0) {
+ if(!(c = channel_find(chan)))
+ c = channel_add(chan);
+ channel_user_add(c, nick);
+ channel_user_writefile(c);
+ } else if(strcmp(cmd, "KICK") == 0) {
+ if((p = strchr(text, ' ')))
+ *p = '\0';
+ if(!(c = channel_find(chan)))
+ c = channel_add(chan);
+ channel_user_leave(c, text);
+ channel_user_writefile(c);
+ } else if(strcmp(cmd, "PART") == 0) {
+ if(!(c = channel_find(chan)))
+ c = channel_add(chan);
+ channel_user_leave(c, nick);
+ channel_user_writefile(c);
+ } else if(strcmp(cmd, "QUIT") == 0) {
+ user_quit(nick);
+ }
+ }
+ free(line);
+
+ return EXIT_SUCCESS;
+}