This is fayca.c in view mode; [Download] [Up]
/* FAYCA - For All You Chat Addicts, simple chat system. */ /* Written by Werner Almesberger almesber@nessie.cs.id.ethz.ch */ /* */ /* When Who Vers What */ /* 18-DEC-1991 WA 0.09 Started logging changes. Fixed use of */ /* SO_REUSEADDR. */ /* 27-DEC-1991 WA 0.10 ANSIfied the source. Fixed a memory leak. Added */ /* sticky first nicks. Added /J, /T, $FAYCA/announce, */ /* $FAYCA/motd and $FAYCA/journal. */ /* 28-DEC-1992 WA 0.11 Ported to Linux (with slightly defective TELNET) */ #include <stdlib.h> #include <stdio.h> #include <ctype.h> #include <string.h> #include <fcntl.h> #include <time.h> #include <limits.h> #include <sys/types.h> #include <sys/socket.h> #ifdef NEXT /* fix this all! */ #define PATH_MAX 1024 #include <sys/types.h> #include <sys/uio.h> #include <signal.h> #else #include <malloc.h> #endif #include <netinet/in.h> #include <arpa/telnet.h> #ifdef linux #include <linux/time.h> #include <linux/types.h> #endif #define SYS_NAME "Fayca" /* system name */ #define ENTRY_CHANNEL "4" /* login channel */ #define LOGIN "(login)" /* login nick name */ #define MAX_ID 16 /* maximum length of a nick name or a channel name */ #define TERM_WIDTH 79 /* maximum usable terminal width */ #define MAX_LINE TERM_WIDTH /* maximum input line length */ #define PORT 1991 /* port number */ #define MAX_USERS 8 /* maximum number of simultaneous users */ #define JOURNAL_LOW 150 /* journal cache dimensions */ #define JOURNAL_HIGH 250 #define JOURNAL_MAX 200 /* maximum request size (entries) */ #define JOURNAL_DFL 10 /* default request size */ #define READ_CHUNK 1024 /* message read block size */ #define REJECT "Sorry, the maximum user limit has been reached." #define MAX_JOU_TYPE 10 /* journal type name length */ #define JOU_SIGNON "Signon" #define JOU_SIGNOFF "Signoff" #define JOU_DISCONN "Disconnect" enum state {s_normal,s_iac,s_do,s_dont,s_will,s_wont,s_esc,s_csi}; typedef struct _user { char *first_nick,*nick,*channel; char buffer[MAX_LINE+1],prev[MAX_LINE+1]; char *eol,*cursor; enum state state; int socket,dead; struct _user *next; } USER; typedef struct _journal { char type[MAX_JOU_TYPE+1]; char date[15+1]; char nick[MAX_ID+1]; } JOURNAL; USER *users; JOURNAL cache[JOURNAL_HIGH],result[JOURNAL_MAX]; int port,user_count = 0,jou_file = 0,jou_size = 0; char *announce = NULL,*motd = NULL,*reject = NULL; static void sys_tell(USER *user,char *msg); static void signoff(USER *user,int abort); static void crash(char *msg) { fprintf(stderr,"CRASH: %s\n",msg); exit(1); } static int is(char *a,char *b) { while (*a || *b) { if ((islower(*a) ? *a : tolower(*a)) != (islower(*b) ? *b : tolower(*b))) return 0; a++; b++; } return 1; } static int contains(char *a,char *b) { char *walk_a,*walk_b; if (!*b) return 1; while (*a) { for (walk_a = a,walk_b = b; *walk_a && *walk_b; walk_a++,walk_b++) if ((islower(*walk_a) ? *walk_a : tolower(*walk_a)) != (islower(*walk_b) ? *walk_b : tolower(*walk_b))) break; if (!*walk_b) return 1; a++; } return 0; } static void journal_add(char *type,char *nick) { int size,count; time_t now; if (jou_size == JOURNAL_HIGH) { if (jou_file) { if (write(jou_file,cache,size = sizeof(JOURNAL)*(JOURNAL_HIGH- JOURNAL_LOW)) == size) jou_size -= JOURNAL_HIGH-JOURNAL_LOW; else { close(jou_file); jou_file = 0; } } if (!jou_file) jou_size--; for (count = 0; count < jou_size; count++) memcpy(&cache[count],&cache[count+(JOURNAL_HIGH-jou_size)], sizeof(JOURNAL)); } strcpy(cache[jou_size].type,type); time(&now); strncpy(cache[jou_size].date,ctime(&now)+4,15); cache[jou_size].date[15] = 0; strcpy(cache[jou_size].nick,nick); jou_size++; } static int journal_scan(int limit,char *str) { int current,walk; current = JOURNAL_MAX-1; for (walk = jou_size-1; walk >= 0; walk--) { if (contains(cache[walk].nick,str) || contains(cache[walk].date,str) || contains(cache[walk].type,str)) { memcpy(&result[current],&cache[walk],sizeof(JOURNAL)); current--; if (!--limit) return 0; } } /* ... */ return limit; } static int journal_get(int limit,char *str) { int remaining,count; if ((remaining = journal_scan(limit,str)) == limit) return 0; for (count = 0; count < limit-remaining; count++) memcpy(&result[count],&result[count+remaining+(JOURNAL_MAX-limit)], sizeof(JOURNAL)); return limit-remaining; } static int name_okay(USER *user,char *name) { if (name == NULL || !*name) { sys_tell(user,"Name is missing"); return 0; } if (strlen(name) > MAX_ID) { sys_tell(user,"Name is too long"); return 0; } while (*name) { if (isalnum(*name) || *name == '_' || *name == '-' || *name == '&' || *name == '.') name++; else { sys_tell(user,"Invalid characters in name"); return 0; } } return 1; } static void remove_user(USER *user) { USER **walk; walk = &users; while (*walk != user) walk = &(*walk)->next; *walk = user->next; if (user->nick) { free(user->nick); if (user->nick != user->first_nick) free(user->first_nick); } free(user); user_count--; } static void join_channel(USER *user,char *channel) { USER *walk; for (walk = users; walk; walk = walk->next) if (walk->channel && is(channel,walk->channel)) break; if (walk) user->channel = walk->channel; else { if ((user->channel = malloc(strlen(channel)+1)) == NULL) crash("Out of memory"); strcpy(user->channel,channel); } } static void leave_channel(USER *user) { USER *walk; for (walk = users; walk; walk = walk->next) if (walk != user && walk->channel && is(walk->channel,user->channel)) break; if (walk == NULL) free(user->channel); user->channel = NULL; } static void output(USER *user,char *msg) { int len; if (write(user->socket,msg,len = strlen(msg)) < len) signoff(user,1); } static void send_msg(USER *user,char *prefix,char *msg,int *left) { char temp[MAX_LINE+MAX_LINE]; int len; len = strlen(strcat(strcat(strcpy(temp,prefix)," "),msg)); while (len < *left) temp[len++] = ' '; temp[len] = 0; *left = 0; strcat(temp,"\r\n"); output(user,temp); } static void send_user(USER *user,char *prefix,char *msg) { char *here,*next,copy; char temp[MAX_LINE+1]; int p_len,left; int from_end,from_beg; if (user->dead) return; if (left = user->eol-user->buffer) output(user,"\r"); p_len = strlen(prefix); while (strlen(msg)+p_len+2 >= TERM_WIDTH) { for (here = msg+TERM_WIDTH-2-p_len; here > msg && *here != ' '; here--); if (here != msg) next = here+1; else { here = msg+TERM_WIDTH-2-p_len; next = here+1; } copy = *(here+1); *(here+1) = 0; send_msg(user,prefix,msg,&left); *(here+1) = copy; msg = next; } send_msg(user,prefix,msg,&left); if (user->buffer != user->eol) { *user->eol = 0; output(user,user->buffer); if (from_end = user->eol-user->cursor) { from_beg = user->cursor-user->buffer+1; if (from_end > from_beg) { copy = *user->cursor; *user->cursor = 0; output(user,"\r"); output(user,user->buffer); *user->cursor = copy; } else { memset(temp,8,from_end); temp[from_end] = 0; output(user,temp); } } } } static void send_channel(char *channel,char *prefix,char *msg,USER *except) { USER *walk; for (walk = users; walk; walk = walk->next) if (walk != except && walk->channel && (channel == NULL || is(channel,walk->channel))) send_user(walk,prefix,msg); } static void sys_tell(USER *user,char *msg) { char prefix[MAX_ID+5]; sprintf(prefix,"|*%s*|",SYS_NAME); send_user(user,prefix,msg); } static void sys_say(char *channel,char *msg,USER *exclude) { char prefix[MAX_ID+5]; sprintf(prefix,"|-%s-|",SYS_NAME); send_channel(channel,prefix,msg,exclude); } static void tell(USER *from,USER *to,char *msg) { char prefix[MAX_ID+5]; sprintf(prefix,"<*%s*>",from->nick); send_user(to,prefix,msg); } static void say(USER *from,char *msg) { char prefix[MAX_ID+3]; sprintf(prefix,"<%s>",from->nick); send_channel(from->channel,prefix,msg,from); } static void signoff(USER *user,int abort) { char msg[MAX_LINE]; if (user->channel) { sprintf(msg,"%s has left the %s system%s",user->nick,SYS_NAME,abort ? " (disconnected)" : ""); sys_say(NULL,msg,abort ? user : NULL); if (abort) journal_add(JOU_DISCONN,user->first_nick); else journal_add(JOU_SIGNOFF,user->first_nick); } if (abort) shutdown(user->socket,2); close(user->socket); if (user->channel) leave_channel(user); if (abort) user->dead = 1; else remove_user(user); } static void help(USER *user) { sys_tell(user,"COMMANDS"); sys_tell(user," /Channel channel change channel"); sys_tell(user," /Exit leave the system"); sys_tell(user," /Help this list"); sys_tell(user," /Journal [limit,][string] list last logins"); sys_tell(user," /Nick nick change your nick name"); sys_tell(user," /Show show currently logged in users"); sys_tell(user," /Time show current time"); sys_tell(user," /Whisper user msg send secret message to user"); sys_tell(user,"KEYS"); sys_tell(user," ^A home, ^B back, ^E end of line, ^F forwards, ^H erase,"); sys_tell(user," ^P previous line, ^R redraw, ^U and ^X erase line."); } static void show(USER *user) { USER *walk; char fmt_same[40],fmt_diff[40],msg[MAX_LINE]; sprintf(fmt_same,"%%-%ds %%%ds %%-%ds%%s",MAX_ID,MAX_ID+2,MAX_ID); sprintf(fmt_diff,"%%-%ds (%%s)%%-%ds %%-%ds%%s",MAX_ID,MAX_ID,MAX_ID); for (walk = users; walk; walk = walk->next) { if (walk->nick == NULL || is(walk->nick,walk->first_nick)) sprintf(msg,fmt_same,walk->nick ? walk->nick : LOGIN,"", walk->channel ? walk->channel : "",walk->dead ? " (dead)" : ""); else { sprintf(fmt_diff,"%%-%ds (%%s)%%-%ds%%-%ds%%s",MAX_ID,MAX_ID+1- strlen(walk->first_nick),MAX_ID); sprintf(msg,fmt_diff,walk->nick,walk->first_nick,"",walk->channel, walk->dead ? " (dead)" : ""); } sys_tell(user,msg); } } static void channel(USER *user,char *channel) { char msg[MAX_LINE]; if (!name_okay(user,channel)) return; if (is(channel,user->channel)) return; sprintf(msg,"%s has left this channel",user->nick); sys_say(user->channel,msg,user); leave_channel(user); join_channel(user,channel); sprintf(msg,"%s has joined this channel",user->nick); sys_say(channel,msg,user); sprintf(msg,"You're now on channel %s",channel); sys_tell(user,msg); } static void nick(USER *user,char *nick) { char msg[MAX_LINE]; USER *walk; if (!name_okay(user,nick)) return; for (walk = users; walk; walk = walk->next) if (walk->nick && is(walk->nick,nick)) { sys_tell(user,"This nick is already in use"); return; } sprintf(msg,"%s is now %s",user->nick,nick); if (user->nick != user->first_nick) free(user->nick); if ((user->nick = malloc(strlen(nick)+1)) == NULL) crash("Out of memory"); strcpy(user->nick,nick); sys_say(user->channel,msg,NULL); } static void whisper(USER *user,char *arg) { USER *walk; char *msg,*fix; if (arg == NULL) msg = fix = NULL; else { if (msg = fix = strchr(arg,' ')) { *(fix = msg++) = 0; while (*msg == ' ') msg++; if (!*msg) msg = NULL; } } if (msg == NULL) { if (fix) *fix = ' '; sys_tell(user,"Two arguments expected"); return; } for (walk = users; walk; walk = walk->next) if (walk->nick && is(walk->nick,arg)) break; *fix = ' '; if (walk) tell(user,walk,msg); else sys_tell(user,"No such user"); } static void journal(USER *user,char *arg) { int items,count,limit = JOURNAL_DFL; char fmt[40],buffer[MAX_LINE+1]; char *str,*here; if (arg == NULL) str = ""; else { if ((here = strchr(arg,',')) == NULL) str = arg; else { limit = atoi(arg); if (limit <= 0 || limit > JOURNAL_MAX) { sys_tell(user,"Invalid limit"); return; } for (str = here+1; *str && *str == ' '; str++); } } if (!(items = journal_get(limit,str))) { sys_tell(user,"No entries found"); return; } sprintf(fmt,"%%s, %%-%ds %%s",MAX_JOU_TYPE); for (count = 0; count < items; count++) { sprintf(buffer,fmt,result[count].date,result[count].type, result[count].nick); sys_tell(user,buffer); } } static void c_time(USER *user) { time_t now; char buffer[MAX_LINE+1]; char *here; time(&now); strcpy(buffer,ctime(&now)); if (here = strchr(buffer,'\n')) *here = 0; sys_tell(user,buffer); } static void error(USER *user) { sys_tell(user,"Unknown command. Enter /Help for a list of commands."); } static void action(USER *user, char *arg) { char buffer[MAX_LINE+1]; if(arg == (char *)0) { sprintf(buffer, "%s erweist sich als FAYCA-Frischling.", user->nick); } else { sprintf(buffer, "%s %s", user->nick, arg); } send_channel(user->channel, "***", buffer, (USER *)0); } static void interpret(USER *user,char *line) { char cmd,*arg; if (!*line) return; if (*line != '/') say(user,line); else { cmd = *(line+1); if (islower(cmd)) cmd = toupper(cmd); if (arg = strchr(line,' ')) { while (*arg == ' ') arg++; if (!*arg) arg = NULL; } switch (cmd) { case '?': case 'H': help(user); break; case 'S': show(user); break; case 'C': channel(user,arg); break; case 'N': nick(user,arg); break; case 'W': whisper(user,arg); break; case 'J': journal(user,arg); break; case 'T': c_time(user); break; case 'E': signoff(user,0); break; case 'A': action(user, arg); break; default: error(user); } } } static void process(USER *user,char *line) { USER *walk; char msg[MAX_LINE]; if (user->nick) interpret(user,line); else { if (*line == '/') { if (line[1] == 's' || line[1] == 'S') { show(user); return; } if (line[1] == 'e' || line[1] == 'E') { signoff(user,0); return; } } if (!name_okay(user,line)) return; for (walk = users; walk; walk = walk->next) if (walk->nick && is(walk->nick,line)) { sys_tell(user,"This nick is already in use"); return; } if ((user->nick = user->first_nick = malloc(strlen(line)+1)) == NULL) crash("Out of memory"); strcpy(user->nick,line); if (motd) output(user,motd); join_channel(user,ENTRY_CHANNEL); sprintf(msg,"%s has entered the %s system on channel %s",line, SYS_NAME,ENTRY_CHANNEL); sys_say(NULL,msg,NULL); journal_add(JOU_SIGNON,line); } } static void cleanup(void) { USER *walk,*next; for (walk = users; walk; walk = next) { next = walk->next; if (walk->dead) remove_user(walk); } } static void accept_new(void) { USER *user; int size,socket; char echo[7]; size = 0; if ((socket = accept(port,NULL,&size)) < 0) perror("accept"); #ifndef linux if (fcntl(socket,F_SETFL,FNDELAY) < 0) perror("fcntl"); #else if (fcntl(socket,F_SETFL,O_NONBLOCK) < 0) perror("fcntl"); #endif if (user_count == MAX_USERS || reject) { write(socket,reject ? reject : REJECT,strlen(reject ? reject : REJECT)); write(socket,"\r\n",2); close(socket); return; } user_count++; if ((user = malloc(sizeof(USER))) == NULL) crash("Out of memory"); user->nick = user->channel = NULL; user->socket = socket; user->dead = 0; user->state = s_normal; user->eol = user->cursor = user->buffer; *user->prev = 0; user->next = users; users = user; echo[0] = IAC; echo[1] = WILL; echo[2] = TELOPT_ECHO; echo[3] = IAC; echo[4] = WILL; echo[5] = TELOPT_SGA; echo[6] = 0; output(user,echo); output(user,announce); } static void crsr_back(USER *user) { if (user->cursor == user->buffer) return; user->cursor--; output(user,"\b"); } static void crsr_fwd(USER *user) { char c[2]; if (user->cursor >= user->eol) return; c[0] = *user->cursor++; c[1] = 0; output(user,c); } static void crsr_up(USER *user) { char temp[MAX_LINE+MAX_LINE+1]; int slack; if (!*user->prev) return; output(user,"\r"); output(user,user->prev); if ((slack = user->eol-user->buffer-strlen(user->prev)) > 0) { memset(temp,' ',slack); memset(temp+slack,8,slack); *(temp+slack+slack) = 0; output(user,temp); } user->eol = user->cursor = strchr(strcpy(user->buffer,user->prev),0); } static void special(USER *user,unsigned char ch) { switch (user->state) { case s_iac: switch (ch) { case WONT: user->state = s_wont; break; case WILL: user->state = s_will; break; case DO: user->state = s_do; break; case DONT: user->state = s_dont; break; default: user->state = s_normal; } break; case s_wont: case s_will: case s_do: case s_dont:user->state = s_normal; break; case s_esc: if (ch == '[') user->state = s_csi; else user->state = s_normal; break; case s_csi: switch (ch) { case 'D': crsr_back(user); break; case 'C': crsr_fwd(user); break; case 'A': crsr_up(user); } user->state = s_normal; break; default: crash("Unknown terminal state"); } } static void get_input(USER *user) { char input[MAX_LINE+1],echo[2],temp[MAX_LINE*3]; char *walk,*move; int size,dist; echo[1] = 0; if ((size = read(user->socket,input,MAX_LINE)) <= 0) { signoff(user,1); return; } for (walk = input; size--; walk++) { if (user->state != s_normal) special(user,*walk); else { switch((unsigned char) *walk) { #ifdef linux case '\n': #endif case '\r': *user->eol = 0; user->eol = user->cursor = user->buffer; output(user,"\r\n"); if (*user->buffer) process(user,strcpy(user->prev,user->buffer)); break; case 8: case 127: if (user->buffer != user->cursor) { user->cursor--; *user->eol-- = 0; if (user->cursor == user->eol) output(user,"\b \b"); else { output(user,"\b"); output(user,user->cursor+1); for (move = user->cursor+1; *move; move++) *(move-1) = *move; temp[0] = ' '; memset(temp+1,8,dist = user->eol-user-> cursor+1); temp[dist+1] = 0; output(user,temp); } } break; case 1: user->cursor = user->buffer; output(user,"\r"); break; case 5: if (user->cursor != user->eol) { *user->eol = 0; output(user,user->cursor); user->cursor = user->eol; } break; case 27: user->state = s_esc; break; case IAC: user->state = s_iac; break; case 2: crsr_back(user); break; case 6: crsr_fwd(user); break; case 16: crsr_up(user); break; case 18: output(user,"\r"); *user->eol = 0; output(user,user->buffer); if (dist = user->eol-user->cursor) { memset(temp,8,dist); temp[dist] = 0; output(user,temp); } break; case 21: case 24: if (dist = user->eol-user->buffer) { output(user,"\r"); memset(temp,' ',dist); temp[dist] = 0; output(user,temp); output(user,"\r"); user->eol = user->cursor = user->buffer; } break; default: if (*walk >= ' ' && *walk <= '~') { if (user->eol-user->buffer == MAX_LINE) output(user,"\7"); else { echo[0] = *walk; output(user,echo); if (user->cursor == user->eol) { *user->cursor = *walk; user->eol = ++user->cursor; } else { *user->eol++ = 0; output(user,user->cursor); for (move = user->eol; move > user-> cursor; move--) *move = *(move-1); *user->cursor++ = *walk; memset(temp,8,dist = user->eol-user-> cursor); temp[dist] = 0; output(user,temp); } } } } } } } static void poll(void) { USER *walk; fd_set flags; int max; FD_ZERO(&flags); FD_SET(port,&flags); max = port; for (walk = users; walk; walk = walk->next) if (!walk->dead) { FD_SET(walk->socket,&flags); if (walk->socket > max) max = walk->socket; } select(max+1,&flags,NULL,NULL,NULL); if (FD_ISSET(port,&flags)) accept_new(); for (walk = users; walk; walk = walk->next) if (FD_ISSET(walk->socket,&flags)) get_input(walk); } static void die(char *msg) { perror(msg); exit(1); } static char *get_path(char *name,char *buffer) { char *path; sprintf(buffer,"%s/%s",(path = getenv("FAYCA")) ? path : ".",name); } static char *get_msg(char *name) { char full_name[PATH_MAX+1]; char input[READ_CHUNK],cvt[READ_CHUNK*2]; char *path,*msg,*new,*from,*to; int file,total,size,count; (void) get_path(name,full_name); if ((file = open(full_name,O_RDONLY)) < 0) return NULL; msg = NULL; total = 0; while ((size = read(file,input,READ_CHUNK)) > 0) { from = input; to = cvt; for (count = size; count > 0; count--) { if (*from == '\n') { *to++ = '\r'; size++; } *to++ = *from++; } if (msg == NULL || (new = realloc(msg,total+size+1)) == NULL) { if ((new = malloc(total+size+1)) == NULL) crash("Out of memory"); if (msg) { memcpy(new,msg,total); free(msg); } } memcpy(new+total,cvt,size); (msg = new)[(total += size)+1] = 0; } (void) close(file); return msg; } static void setup(void) { char full_name[PATH_MAX+1]; if ((announce = get_msg("announce")) == NULL) announce = "Welcome to FAYCA (For All You Chat Addicts)\r\n\ ~~~~~~~~~~~~~~~~~~\\ Version 0.10 ALPHA\r\n\n\ Please enter your nick name (or /Show or /Exit): "; motd = get_msg("motd"); get_path("journal",full_name); /* if ((jou_file = open(full_name,O_RDWR)) < 0) */ jou_file = 0; } int main(int argc,char **argv) { struct sockaddr_in name; int option,tmp,nr = PORT; FILE *file; if (argc == 3 && !strcmp(argv[1],"-t")) return kill(atoi(argv[2]),0) < 0 ? 1 : 0; setup(); if ((port = socket(AF_INET,SOCK_STREAM,0)) < 0) die("socket"); option = 1; if (setsockopt(port,SOL_SOCKET,SO_REUSEADDR,(char *) &option, sizeof(option)) < 0) die("setsockopt"); bzero(&name,sizeof(name)); name.sin_family = AF_INET; while (argc-- > 1) { if (atoi(argv[1])) nr = atoi(argv[1]); else reject = argv[1]; argv++; } name.sin_port = htons(nr); name.sin_addr.s_addr = INADDR_ANY; if (bind(port,(struct sockaddr *) &name,sizeof(name)) < 0) die("bind"); if (listen(port,5) < 0) die("listen"); if ((file = fopen("fayca.pid","w")) == NULL) die("fopen"); if (fprintf(file,"%d\n",getpid()) < 0) die("fprintf"); if (fclose(file) < 0) die("fclose"); fprintf(stderr,"Ready at port %d\n",ntohs(name.sin_port)); while (1) { poll(); cleanup(); } }