hysteria

ii wrapper script
git clone git://git.2f30.org/hysteria
Log | Files | Refs | README | LICENSE

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:
MMakefile | 17++++++++++++-----
Ahysteria-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; +}