ftp.nice.ch/users/felix/fayca.c

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();
    }
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.