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 }