hysteria

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

hysteria-namelist.c (7947B)


      1 #include <ctype.h>
      2 #include <errno.h>
      3 #include <limits.h>
      4 #include <stdio.h>
      5 #include <stdlib.h>
      6 #include <string.h>
      7 #include <sys/stat.h>
      8 #include <sys/types.h>
      9 
     10 #include "util.h"
     11 
     12 #define IRC_CHANNEL_MAX 200
     13 #define IRC_NAME_MAX    50
     14 
     15 typedef struct User User;
     16 struct User {
     17 	char nickname[IRC_NAME_MAX];
     18 	User *next;
     19 };
     20 
     21 typedef struct ChannelUser ChannelUser;
     22 struct ChannelUser {
     23 	User *user;
     24 	ChannelUser *next;
     25 };
     26 
     27 typedef struct Channel Channel;
     28 struct Channel {
     29 	char name[IRC_CHANNEL_MAX]; /* channel name (normalized) */
     30 	ChannelUser *names;       /* users in channel (linked-list) */
     31 	char namespath[PATH_MAX]; /* names path */
     32 	Channel *next;
     33 };
     34 
     35 static User *users; /* linked-list of all users */
     36 static Channel *channels; /* linked-list of channels */
     37 
     38 static void
     39 eprint(const char *s) {
     40 	fputs(s, stderr);
     41 	exit(1);
     42 }
     43 
     44 static void *
     45 ecalloc(size_t nmemb, size_t size)
     46 {
     47 	void *p;
     48 
     49 	if(!(p = calloc(nmemb, size)))
     50 		eprint("cannot allocate memory\n");
     51 	return p;
     52 }
     53 
     54 static void
     55 create_filepath(char *filepath, size_t len, const char *path,
     56     const char *channel, const char *suffix)
     57 {
     58 	const char *e = "path to directory too long\n";
     59 	int r;
     60 
     61 	if(channel[0]) {
     62 		r = snprintf(filepath, len, "%s/%s", path, channel);
     63 		if(r < 0 || (size_t)r >= len)
     64 			eprint(e);
     65 		r = snprintf(filepath, len, "%s/%s/%s", path, channel, suffix);
     66 		if(r < 0 || (size_t)r >= len)
     67 			eprint(e);
     68 	} else {
     69 		r = snprintf(filepath, len, "%s/%s", path, suffix);
     70 		if(r < 0 || (size_t)r >= len)
     71 			eprint(e);
     72 	}
     73 }
     74 
     75 static void
     76 channel_normalize_path(char *s)
     77 {
     78 	for(; *s; s++) {
     79 		if(isalpha(*s))
     80 			*s = tolower(*s);
     81 		else if(!isdigit(*s) && !strchr(".#&", *s))
     82 			*s = '_';
     83 	}
     84 }
     85 
     86 static void
     87 channel_normalize_name(char *s)
     88 {
     89 	char *p;
     90 
     91 	if(*s == '&' || *s == '#')
     92 		s++;
     93 	for(p = s; *s; s++) {
     94 		if(!strchr(" ,&#\x07", *s)) {
     95 			*p = *s;
     96 			p++;
     97 		}
     98 	}
     99 	*p = '\0';
    100 }
    101 
    102 static Channel *
    103 channel_new(const char *name)
    104 {
    105 	Channel *c;
    106 	char channelpath[PATH_MAX];
    107 
    108 	strlcpy(channelpath, name, sizeof(channelpath));
    109 	channel_normalize_path(channelpath);
    110 
    111 	c = ecalloc(1, sizeof(Channel));
    112 	c->next = NULL;
    113 	strlcpy(c->name, name, sizeof(c->name));
    114 	channel_normalize_name(c->name);
    115 
    116 	create_filepath(c->namespath, sizeof(c->namespath), ".",
    117 					channelpath, "names");
    118 	return c;
    119 }
    120 
    121 static Channel *
    122 channel_add(const char *name)
    123 {
    124 	Channel *c;
    125 
    126 	c = channel_new(name);
    127 	if(!channels) {
    128 		channels = c;
    129 	} else {
    130 		c->next = channels;
    131 		channels = c;
    132 	}
    133 	return c;
    134 }
    135 
    136 static int
    137 user_cmp(const char *s1, const char *s2)
    138 {
    139 	for(; *s1 && strchr("+|@%&~", *s1); s1++);
    140 	for(; *s2 && strchr("+|@%&~", *s2); s2++);
    141 	return strcmp(s1, s2);
    142 }
    143 
    144 static User *
    145 user_find(const char *name)
    146 {
    147 	User *u;
    148 
    149 	for(u = users; u; u = u->next) {
    150 		if(!user_cmp(u->nickname, name))
    151 			return u;
    152 	}
    153 	return NULL;
    154 }
    155 
    156 static User *
    157 user_add(const char *name)
    158 {
    159 	User *n, *u;
    160 
    161 	n = ecalloc(1, sizeof(User));
    162 	strlcpy(n->nickname, name, sizeof(n->nickname));
    163 	if(!users) {
    164 		users = n;
    165 		return n;
    166 	}
    167 	for(u = users; u; u = u->next) {
    168 		if(!u->next) {
    169 			u->next = n;
    170 			return n;
    171 		}
    172 	}
    173 	return NULL;
    174 }
    175 
    176 static ChannelUser *
    177 channel_user_find(Channel *c, User *f)
    178 {
    179 	ChannelUser *cu;
    180 
    181 	for(cu = c->names; cu; cu = cu->next) {
    182 		if(cu->user == f)
    183 			return cu;
    184 	}
    185 	return NULL;
    186 }
    187 
    188 static int
    189 channel_user_writefile(Channel *c)
    190 {
    191 	ChannelUser *u;
    192 	FILE *fp;
    193 
    194 	if(!(fp = fopen(c->namespath, "w"))) {
    195 		fprintf(stderr, "fopen: %s: %s\n", c->namespath, strerror(errno));
    196 		return -1;
    197 	}
    198 	for(u = c->names; u; u = u->next)
    199 		fprintf(fp, "%s\n", u->user->nickname);
    200 	fclose(fp);
    201 	return 0;
    202 }
    203 
    204 static int
    205 user_changenick(const char *nickfrom, const char *nickto)
    206 {
    207 	User *u;
    208 	Channel *c;
    209 	int r = 0;
    210 
    211 	if(!(u = user_find(nickfrom)))
    212 		return -1;
    213 	strlcpy(u->nickname, nickto, sizeof(u->nickname));
    214 	/* check which channel lists need updating */
    215 	for(c = channels; c; c = c->next) {
    216 		if(channel_user_find(c, u)) {
    217 			if(channel_user_writefile(c) == -1)
    218 				r = -1;
    219 		}
    220 	}
    221 	return r;
    222 }
    223 
    224 static int
    225 channel_user_add(Channel *c, const char *name)
    226 {
    227 	ChannelUser *n, *u;
    228 	User *f;
    229 
    230 	/* skip whitespace */
    231 	for(; *name && isspace(*name); name++);
    232 	if(!*name)
    233 		return -1;
    234 	if(!(f = user_find(name)))
    235 		f = user_add(name);
    236 	if(channel_user_find(c, f))
    237 		return -1; /* exist */
    238 
    239 	n = ecalloc(1, sizeof(ChannelUser));
    240 	n->user = f;
    241 	if(!c->names) {
    242 		c->names = n;
    243 		return 0;
    244 	} else {
    245 		for(u = c->names; u; u = u->next) {
    246 			if(!u->next) {
    247 				u->next = n;
    248 				return 0;
    249 			}
    250 		}
    251 	}
    252 	return -1;
    253 }
    254 
    255 static Channel *
    256 channel_find(const char *name)
    257 {
    258 	Channel *c;
    259 	char chan[IRC_CHANNEL_MAX];
    260 
    261 	strlcpy(chan, name, sizeof(chan));
    262 	channel_normalize_name(chan);
    263 	for(c = channels; c; c = c->next) {
    264 		if(!strcmp(chan, c->name))
    265 			return c;
    266 	}
    267 	return NULL;
    268 }
    269 
    270 static int
    271 user_rm(User *f)
    272 {
    273 	User *u, *t, *prev = NULL;
    274 
    275 	for(u = users; u; prev = u, u = u->next) {
    276 		if(u == f) {
    277 			t = u->next;
    278 			free(u);
    279 			if(!prev)
    280 				users = t;
    281 			else
    282 				prev->next = t;
    283 			return 0;
    284 		}
    285 	}
    286 	return -1;
    287 }
    288 
    289 static int
    290 channel_user_rm(Channel *c, User *f)
    291 {
    292 	ChannelUser *u, *p = NULL, *t;
    293 
    294 	for(u = c->names; u; p = u, u = u->next) {
    295 		if(f == u->user) {
    296 			t = u->next;
    297 			if(p)
    298 				p->next = t;
    299 			else
    300 				c->names = t;
    301 			free(u);
    302 			return 0;
    303 		}
    304 	}
    305 	return -1;
    306 }
    307 
    308 static int
    309 channel_user_leave(Channel *c, const char *name)
    310 {
    311 	User *f;
    312 	ChannelUser *cu;
    313 	int r;
    314 
    315 	if(!(f = user_find(name)))
    316 		return -1;
    317 	r = channel_user_rm(c, f);
    318 	/* check if user isn't visible in any channels
    319 	 * if so remove from global list. */
    320 	for(c = channels; c; c = c->next) {
    321 		for(cu = c->names; cu; cu = cu->next) {
    322 			if(cu->user == f)
    323 				return r;
    324 		}
    325 	}
    326 	return (r != -1 && user_rm(f) != -1) ? 0 : -1;
    327 }
    328 
    329 static int
    330 user_quit(const char *name)
    331 {
    332 	Channel *c;
    333 	User *f = NULL;
    334 
    335 	if(!(f = user_find(name)))
    336 		return -1; /* not found */
    337 	/* remove user from all channels */
    338 	for(c = channels; c; c = c->next)
    339 		channel_user_rm(c, f);
    340 	/* remove from global list */
    341 	return user_rm(f);
    342 }
    343 
    344 
    345 static int
    346 proc_server_namereply(char *chan, char *users)
    347 {
    348 	Channel *c;
    349 	char *p, *t;
    350 
    351 	p = chan;
    352 	if(!strncmp(p, "= ", 2))
    353 		p += 2;
    354 	if(!(c = channel_find(p)))
    355 		return -1;
    356 
    357 	for(t = p = users; p && t; p = t + 1) {
    358 		if((t = strchr(p, ' ')))
    359 			*t = '\0';
    360 		channel_user_add(c, p);
    361 		if(t)
    362 			*t = ' ';
    363 	}
    364 	return channel_user_writefile(c);
    365 }
    366 
    367 int
    368 main(void) {
    369 	Channel *c;
    370 	size_t siz, i;
    371 	ssize_t n;
    372 	char *nick, *chan, *cmd, *text, *t, *p, *line = NULL;
    373 
    374 	while((n = getline(&line, &siz, stdin)) > 0) {
    375 		if(!(p = strchr(line, ':')))
    376 			continue;
    377 		/* strip crlf */
    378 		for(; n > 0 && (line[n - 1] == '\n' || line[n - 1] == '\r'); n--)
    379 			line[n - 1] = '\0';
    380 
    381 		nick = cmd = text = chan = "";
    382 		for(i = 0; ; i++) {
    383 			t = strchr(p, ' ');
    384 			if(i == 0)
    385 				nick = p;
    386 			else if(i == 1)
    387 				cmd = p;
    388 			else if(i == 2)
    389 				text = p;
    390 			if(!t)
    391 				break;
    392 			if(i < 2)
    393 				*t = '\0';
    394 			p = t + 1;
    395 		}
    396 		if(*nick == ':')
    397 			nick++;
    398 		if((p = strchr(nick, '!')))
    399 			*p = '\0';
    400 		if(*text == ':')
    401 			text++;
    402 
    403 		if(*text == '#' || *text == '&') {
    404 			chan = text;
    405 			text = "";
    406 			if((p = strchr(chan, ' '))) {
    407 				*p = '\0';
    408 				text = p + 1;
    409 				if(*text == ':')
    410 					text++;
    411 			}
    412 		}
    413 		/* name reply */
    414 		if(strcmp(cmd, "353") == 0) {
    415 			t = strchr(text, '=');
    416 			p = strchr(text, ':');
    417 			if(t && p)
    418 				proc_server_namereply(t, p);
    419 		} else if(strcmp(cmd, "NICK") == 0) {
    420 			user_changenick(nick, text);
    421 		} else if(strcmp(cmd, "JOIN") == 0) {
    422 			if(!(c = channel_find(chan)))
    423 				c = channel_add(chan);
    424 			channel_user_add(c, nick);
    425 			channel_user_writefile(c);
    426 		} else if(strcmp(cmd, "KICK") == 0) {
    427 			if((p = strchr(text, ' ')))
    428 				*p = '\0';
    429 			if(!(c = channel_find(chan)))
    430 				c = channel_add(chan);
    431 			channel_user_leave(c, text);
    432 			channel_user_writefile(c);
    433 		} else if(strcmp(cmd, "PART") == 0) {
    434 			if(!(c = channel_find(chan)))
    435 				c = channel_add(chan);
    436 			channel_user_leave(c, nick);
    437 			channel_user_writefile(c);
    438 		} else if(strcmp(cmd, "QUIT") == 0) {
    439 			user_quit(nick);
    440 		}
    441 	}
    442 	free(line);
    443 
    444 	return 0;
    445 }