ftp.nice.ch/Attic/openStep/unix/connectivity/communication/msend.3.3.m.I.bs.tgz#/msend-3.3/mesgd.c

This is mesgd.c in view mode; [Download] [Up]

/*
 *
 * Copyright (c) 1992 Sun Microsystems, Inc. 
 * Copyright (c) 1993 Andrew Herbert <andrew@werple.apana.org.au>
 * Copyright (c) 1993 Zik Saleeba <zik@zikzak.apana.org.au>
 * Copyright (c) 1996 Luke Howard <lukeh@inter.net.au>
 *
 * This is a server implementation of the Message Send protocol
 * defined in RFC1312. This implementation may be freely
 * copied, modified, and redistributed, provided that this
 * comment and the Sun Microsystems copyright are retained.
 * Anyone installing, modifying, or documenting this
 * software is advised to read the section in the RFC which
 * deals with security issues.
 *
 * Author: Geoff.Arnold@east.sun.com
 * Modified by: Andrew Herbert <andrew@werple.apana.org.au>
 * Modified by: David Barr <barr@darwin.psu.edu>
 * Modified by: Zik Saleeba <zik@zikzak.apana.org.au>
 * Modified by: Luke Howard <lukeh@inter.net.au>
 */

/* $Id: mesgd.c,v 1.2 1997/10/26 14:31:16 lukeh Exp $ */

#include "common.h"

#ifdef NEXT_ALERTPANEL
#define PATH_TO_RUNALERT	"/LocalApps/msend.app/malert"
#endif

#ifdef NeXT
#include <libc.h>
#endif

#define MAXMSGSIZE 65536
#define LINELENGTH 80
#define SCRLENGTH 20	/* conservative */
#define SLASHDEV "/dev/"

char *prog;
int debug = 0;
int verbose = 0;
int inetd = 0;
int tcpmode = 0;
char *empty_arg = "";

char hostname[MAXHOSTNAMELEN];	/* our hostname */
char *domainname;	/* ditto for domain */
char * recipient;
char * recip_term;
char sender[256];
char * sender_term;
char * msg_text;
char * cookie;
char * signature;

char console[] = "/dev/console";
/* utmp globals */
#ifdef USE_UTMPX
struct utmpx *utmp;
#else
struct utmp utmpbuf;
struct utmp *utmp;
#endif
FILE *utmp_file;	/* stream for utmp, also non-0 indicates valid entry */


/*
 * types for all procedures
 */
#ifdef __ANSI__
void usage(PR(void);
void handle_udp(int s);
void handle_tcp(int s);
int main(int argc, char *argv[]);
SIGHANDLER_TYPE reaper(int sig);
void udp_ack(int s, struct sockaddr_in *to, char *msg);
void ack(int s, char *msg);
char *deliver(void);
int rip_apart_message(struct sockaddr_in *from, int fromlen, char *buff, int buflen);
int check_cache(char *cookie, struct sockaddr_in *addrp);
void filter(char *text);
char *first_utmp_entry(void);
void next_utmp_entry(void);
#ifdef NEXT_ALERTPANEL
char *nx_send_to_term(char *term);
#endif
char *send_to_term(char *term);
void store_to_file(struct passwd *pwd, int wasread, char *errmsg);
void output_message(FILE *stream, int forscreen, int wasread);
void compat_memset(const char *dest, char cchar, int length);
int compat_strcasecmp(const char *a, const char *b);
int compat_strncasecmp(const char *a, const char *b, int n);
void downcase(char *s);
#else /* ANSI */
void usage();
void handle_udp();
void handle_tcp();
int main();
SIGHANDLER_TYPE reaper();
void udp_ack();
void ack();
char *deliver();
int rip_apart_message();
int check_cache();
void filter();
char *first_utmp_entry();
void next_utmp_entry();
char *send_to_term();
void store_to_file();
void output_message();
void compat_memset();
int compat_strcasecmp();
int compat_strncasecmp();
void downcase();
#endif /* ANSI */

void
usage()
{
	fprintf(stderr, "usage: %s [-d][-i][-pN]\n", prog);
	fprintf(stderr, "  -d    - turn on debugging\n");
	fprintf(stderr, "  -i    - run from inetd, doing udp\n");
	fprintf(stderr, "  -t    - run from inetd, doing tcp\n");
	fprintf(stderr, "  -pN   - use port N (default: 18)\n");
}

int
main(argc, argv)
int argc;
char *argv[];
{

	short port = 0;
	int tcpsock1;
	int tcpsock2;
	int udpsock = 0;
	int i;
	fd_set ready;
#ifdef NO_GETDTABLESIZE
	int nfds = FD_SETSIZE;
#else
	int nfds = getdtablesize();
#endif

	struct sockaddr_in sin;
	struct servent *sp;
	struct timeval datagram_timeout;


	/* process options:
	 * -d (debug)
	 * -i (running from inetd, datagram mode)
	 * -t (running from inetd, tcp mode)
	 * -pN (use port N instead of 18)
	 */

	prog = *argv++;
	argc--;

	while(argc && *argv[0] == '-') {
		(*argv)++;
		switch (toupper(*argv[0])){
			case 'D':
				debug++;
				verbose++;
				inetd = 0;
				break;
			case 'I':
				inetd++;
				tcpmode = 0;
				debug = 0;
				verbose = 0;
				break;
			case 'T':
				inetd++;
				tcpmode++;
				debug = 0;
				verbose = 0;
				break;
			case 'P':
				(*argv)++;
				port = atoi(*argv);
				break;
			default:
				usage();
				exit(1);
				/*NOTREACHED*/
		}
		argv++;
		argc--;
	}
	if(argc != 0) {
		usage();
		exit(1);
		/*NOTREACHED*/
	}

	if(!debug && !inetd) {
		if(fork())
			exit(0);
		for(i = nfds-1; i >= 0; --i)
			close(i);
		(void)open("/dev/null", O_RDWR);
		dup2(0, 1);
		dup2(0, 2);
#ifdef USE_SETSID
		setsid();
#else
/* NB - setsid() also works in SunOS but maybe not other BSD-derived code */
		i = open("/dev/tty", O_RDWR);
		if(i >= 0){
			ioctl(i, TIOCNOTTY, 0);
			close(i);
		}
#endif
/* XXX todo - add code to use SYSLOG if we're not in debug mode */
	}

#ifdef IGNORE_CHILDREN
	signal(SIGCHLD, SIG_IGN);
#else
	signal(SIGCHLD, reaper);
#endif

	/* get the host and domain names */
	if (gethostname(hostname, sizeof(hostname)) < 0) {
    		perror("gethostname");
    		exit(99);
	}
#ifdef GETHOSTNAME_NO_FQDN
	domainname = hostname + strlen(hostname);
	if (getdomainname(domainname, sizeof(hostname) - strlen(hostname)) < 0) {
   		perror("getdomainname");
    		exit(99);
	}
#else
	domainname = (char *)STRCHR(hostname, '.');
	if (domainname) domainname++;	/* skip over leading "." if we found
						a domainname part */
#endif

	if (inetd) {
		/* running from inetd */
		if (tcpmode) {
			/* tcp mode */
			handle_tcp(0);
		}
		else {
			/* dgram mode */
			do	{
				FD_ZERO(&ready);
				FD_SET(0, &ready);
				datagram_timeout.tv_sec = 60;
				datagram_timeout.tv_usec = 0; /* die after a minute of inactivity */
				i = select(nfds, &ready, (fd_set *)0, (fd_set *)0, (struct timeval *)&datagram_timeout);
				if(i <= 0) {
					if(debug)perror("select");
					continue;
				}
				if(FD_ISSET(0, &ready))
					handle_udp(udpsock);
			} while (i > 0);
		}
	}
	else {
		/* not running from inetd */
		/* set up ports for listening */
		sin.sin_family = AF_INET;

		/*
		 * compute the port to use: consult /etc/services, but if not
		 * found use 18 (from the RFC). the -pN option overrides
		 */

		if(port == 0) {
			sp = getservbyname("message", "udp");
			if(sp)
				sin.sin_port = sp->s_port;
			else
				sin.sin_port = htons(18);	/* from the RFC */
		}
		else
			sin.sin_port = htons(port);

		sin.sin_addr.s_addr = INADDR_ANY;

		if(debug) printf("%s: using port %d\n", prog, htons(sin.sin_port));



		tcpsock1 = socket(AF_INET, SOCK_STREAM, 0);
		if(bind(tcpsock1, (struct sockaddr *)&sin, sizeof sin) < 0) {
			if(debug) perror("bind (TCP)");
			exit(99); /* XXX */
		}
		listen(tcpsock1, 5);

		udpsock = socket(AF_INET, SOCK_DGRAM, 0);
		if(bind(udpsock, (struct sockaddr *)&sin, sizeof sin) < 0) {
			if(debug) perror("bind (UDP)");
			exit(99); /* XXX */
		}

		if(debug) printf("entering main loop...\n");
		while(1) {
			FD_ZERO(&ready);
			FD_SET(udpsock, &ready);
			FD_SET(tcpsock1, &ready);
			i = select(nfds, &ready, (fd_set *)0, (fd_set *)0, (struct timeval *)0);
			if(debug) printf("----------------------------------------------------------\nselect returned %d\n", i);
			if(i <= 0) {
				if(debug)perror("select");
				continue;
			}
			if(FD_ISSET(udpsock, &ready))
				handle_udp(udpsock);
			else if(FD_ISSET(tcpsock1, &ready)) {
				tcpsock2 = accept(tcpsock1, (struct sockaddr *)0,
					(int *)0);
	
				if(debug) {
					printf("forking....\n");
					handle_tcp(tcpsock2);
				}
				else {
					if(fork() == 0) {
						close(tcpsock1);
						close(udpsock);
						handle_tcp(tcpsock2);
						exit(0);
					}
				}
			}
		}
	}
}

#define CACHE_ENTRIES 32

struct mc_entry {
	struct sockaddr_in mc_addr;
	char mc_cookie[48];
};

int mc_used = 0;
int mc_next = 0;
struct mc_entry mcache[CACHE_ENTRIES];

int
check_cache(cookie, addrp)
char *cookie;
struct sockaddr_in *addrp;
{
	int i;
	if(mc_used) {
		for (i = 0; i < mc_used; i++) {
			if(!strcmp(cookie, mcache[i].mc_cookie) &&
			   !MEMCMP((char *)addrp, (char *)&mcache[i].mc_addr,
				sizeof(*addrp)))
				return(1);
		}
	}
	MEMCPY((char *)&mcache[mc_next].mc_addr, (char *)addrp, sizeof(*addrp));
	strcpy(mcache[mc_next].mc_cookie, cookie);

	mc_next++;
	if(mc_next > mc_used) mc_used = mc_next;
	mc_next = mc_next % CACHE_ENTRIES;
	return(0);
}

void
handle_udp(s)
int s;
{
	char buff[MAXMSGSIZE+1];
	int buflen;
	struct sockaddr_in from;
	int fromlen;
	char *txt;

	fromlen = sizeof (from);

	if (debug) printf("%s: udp msg received\n", prog);

	buflen = recvfrom(s, buff, MAXMSGSIZE, 0,
		 (struct sockaddr *)&from,  &fromlen);

	if (buflen < 0) {
		perror("recvfrom");
		return;
	}
	if (debug) printf("addr,port= %s,%d\n", inet_ntoa(*(struct in_addr *)&from.sin_addr), ntohs(*(u_short *)&from.sin_port));
#ifdef SECURE
	if (ntohs(*(u_short *)&from.sin_port) > 1023) {
		fprintf(stderr,"non trusted port: message rejected\n");
		return;
	}
#endif
	buff[MAXMSGSIZE] = '\0';
	if (rip_apart_message(&from, fromlen, buff, buflen)) {
		fprintf(stderr, "%s: malformed message\n", prog);
		return;
	}
	if (check_cache(cookie, &from)) {
		if(debug) printf("duplicate message\n");
		return;
	}

	if (debug)
		printf("forking....\n");

	if (!debug) {
		if(fork() != 0)
			return;
	}

	txt = deliver();
	udp_ack(s, &from, txt);

	if (!debug)
		exit(0);
}

void
handle_tcp(s)
int s;
{
	char *buff;
	int buflen;
	int msglen;
	struct sockaddr_in peer;
	int peerlen;
	char *txt;
	int tryread;

	if (debug) printf("%s: tcp msg received\n", prog);

	peerlen = sizeof peer;
	if (getpeername(s, (struct sockaddr *)&peer, &peerlen) < 0) {
		perror("getpeername");
		exit(99);
	}

	/* read in a message of any size */
	buflen = 1024;
	buff = malloc(buflen);
	if (buff == NULL) {
		fprintf(stderr, "%s: out of memory\n", prog);
		ack(s, "-Out of memory");
		close(s);
		return;
	}
	msglen = 0;
	do {
		tryread = buflen - msglen - 1;
		msglen = read(s, buff+msglen, tryread);
		if (msglen < 0) {
			perror("read");
			ack(s, "-Read error");
			close(s);
			return;
		}
		else if (msglen == tryread) {
			buflen = (buflen*3)/2;
			buff = realloc(buff, buflen);
			if (buff == NULL) {
				fprintf(stderr, "%s: out of memory\n", prog);
				ack(s, "-Out of memory");
				close(s);
				return;
			}
		}
	} while (msglen == tryread);
	*(buff+msglen) = '\0';	/* null-terminate just in case */

#ifdef SECURE
	if (ntohs(*(u_short *)&peer.sin_port) > 1023) {
		fprintf(stderr,"non trusted port: message rejected\n");
		nak(s, "Message rejected");
		if (close(s))
			perror("close");
		exit(1);
	}
#endif

	if (rip_apart_message(&peer, peerlen, buff, msglen)) {
		fprintf(stderr, "%s: malformed message\n", prog);
		ack(s, "-Message format error");
		close(s);
		return;
	}

	txt = deliver();
	ack(s, txt);
	free(buff);
	close(s);
}


/* Note the type difference here */

#ifndef IGNORE_CHILDREN
#ifndef BSD_SIGHANDLERS
SIGHANDLER_TYPE
reaper(sig)
	int sig;
{
	int i, j;

	i = wait(&j);
}
#else /* non-POSIX signal handler */
SIGHANDLER_TYPE
reaper(sig)
	int sig;
{
#ifdef NO_UNION_WAIT
	int status;
#else
	union wait status;
#endif

	while (wait3(&status, WNOHANG, 0) > 0)
		continue;

	return 0;
}
#endif /* BSD_SIGHANDLERS */
#endif /* IGNORE_CHILDREN */

/* ack msg must be of the form "[+-]string" */

void
udp_ack(s, to, msg)
	int	s;
	struct	sockaddr_in *to;
	char	*msg;
{
	if (debug)
		printf("sending ack (%s)\n", msg);

	sendto(s, msg, strlen(msg) + 1, 0, (struct sockaddr *)to, sizeof (*to));
}

void
ack(s, msg)
	int	s;
	char	*msg;
{
	if (debug)
		printf("sending ack (%s)\n", msg);

	write(s, msg, strlen(msg) + 1);
}

int
rip_apart_message(from, fromlen, buff, buflen)
	struct	sockaddr_in *from;
	int	fromlen;
	char	*buff;
	int	buflen;
{
	struct	hostent *hp;
	char 	*cp;
	char 	*cp2;
	char	*lim;

	recipient = NULL;
	recip_term = NULL;
	sender_term = NULL;
	msg_text = NULL;
	cookie = NULL;
	signature = NULL;

	if(buff[0] != 'B')
		return(1);

/*
 * Gather up all parts
 */
	lim = &buff[MAXMSGSIZE];
	recipient = buff + 1;
	if(debug) printf("recipient   = '%s'\n", recipient);

	recip_term = recipient + strlen(recipient) + 1;
	if (recip_term >= lim) return 1;
	if(debug) printf("recip_term   = '%s'\n", recip_term);
	/* toss preceding "/dev/" if any */
	if (STRNCASECMP(recip_term, SLASHDEV, strlen(SLASHDEV)) == 0)
		recip_term += strlen(SLASHDEV);

	msg_text = recip_term + strlen(recip_term) + 1;
	if (msg_text >= lim) return 1;
	if(debug) printf("msg_text   = '%s'\n", msg_text);

	cp = msg_text + strlen(msg_text) + 1;
	sender[80] = '\0';
	strncpy(sender, cp, 80);
	if (sender >= lim) return 1;
	if(debug) printf("sender   = '%s'\n", sender);

/*
 * Lookup the sending hostname
 */
	hp = gethostbyaddr((char *)&(from->sin_addr), sizeof (struct in_addr), AF_INET);
	if (hp) {
	    /* tack-on the hostname if not local */
	    if (STRCASECMP(hp->h_name, hostname)) {
	        cp2 = sender+strlen(sender);
		*cp2++ = '@';
		strncpy(cp2, hp->h_name, sizeof(sender) - (cp2-sender));
		cp2 = (char *)STRCHR(cp2, '.');		/* locate domain part */
		if (domainname && cp2 && !STRCASECMP(domainname, cp2+1))
		    *cp2 = 0;	/* it's a local site, so no need for full name */
	    }
	}
	else
	    fprintf(stderr, "gethostbyaddr: failed\n");

	if(debug)
		printf("sender      = '%s'\n", sender);

/*
 * Gather up sender_term etc
 */
	sender_term = cp + strlen(cp) + 1;
	if (sender_term >= lim) return 1;
	if(debug) printf("sender_term   = '%s'\n", sender_term);

	cookie = sender_term + strlen(sender_term) + 1;
	if (cookie >= lim) return 1;
	if(debug) printf("cookie   = '%s'\n", cookie);

	signature = cookie + strlen(cookie) + 1;
	if (signature >= lim) return 1;
	if(debug) printf("signature   = '%s'\n", signature);

	return(0);
}


/*
 * delivers the message; returns NULL if OK, otherwise
 * a string describing the problem
 */	
char *
deliver()
{
	int	only_one;
	char	*retval;
	struct	passwd *pwd;
	int	sentok;
	char	*sendterm;

	filter(msg_text);
	filter(signature);
	downcase(recipient);	/* in case someone has a vax or something */
	if(debug) printf("delivering message....\n");

	/* set only_one to false only if recip_term is "*" */
	only_one = strcmp(recip_term, "*");

	/* go through utmp entries, sending to appropriate ones */
	if ((retval = first_utmp_entry())[0] != '+')
		return retval;

	retval = NULL;
	sentok = 0;
	/* check if they're logged on */
	do {
		if (debug) printf("evaluating utmp entry %s %s...\n",
			utmp->ut_name, utmp->ut_line);

#ifdef USE_SYSV_UTMP
		/* check for invalid entry */
		if (utmp->ut_type != USER_PROCESS)
			continue;
		if (debug) printf("valid entry\n");
#endif

		/* check for wrong recipient */
		sendterm = utmp->ut_line;
		if (*recipient) {
			if (STRNCASECMP(recipient, utmp->ut_name,
					sizeof(utmp->ut_name))) {
				continue;
			}
		}
#ifndef NO_DEFAULT_DEST
		else {
			/* no recipient; if no term force console */
			if (*recip_term == '\0')
				sendterm = DEFAULT_DEST;
		}
#endif

		/* check for wrong term */
		if (*recip_term) {
			/* specific term or "*" */
			if (strcmp(recip_term, "*")) {
				/* specific term */
				if (STRNCASECMP(recip_term, utmp->ut_line,
						sizeof(utmp->ut_line))) {
					/* nope, wrong term */
					continue;
				}
			}
		}

		/* passed all tests, send it */

#ifdef NEXT_ALERTPANEL
	if (strcmp(sendterm, DEFAULT_DEST) == 0) {
		retval = nx_send_to_term(sendterm);
		only_one = 1;
	}
	else {
		retval = send_to_term(sendterm);
	}
#else
		retval = send_to_term(sendterm);
#endif

		if (*retval == '+')
			sentok = 1;

		/* see if once is enough */
		if (only_one && (retval[0] == '+'))
			break;

	} while (next_utmp_entry(), utmp_file);	/* keep going if more entries */
	
	/* delivery to a file */
	if (*recipient) {
		/* are they a user on this system? */
		pwd = getpwnam(recipient);
		if (pwd) store_to_file(pwd, sentok, &retval);
	}

	/* did we manage to deliver at all? */
	if (retval == NULL)
		retval = "-recipient unknown";

	return retval;
}

/*
 * first_utmp_entry
 *
 * opens utmp, calls next_utmp_entry and returns NULL if open is successful;
 * returns error string if open fails.
 */
char *
first_utmp_entry()
{
#ifdef USE_UTMPX
	setutxent();
	utmp_file = (FILE *)1;	/* to flag it's open */
#else /* USE_UTMPX */
	utmp = &utmpbuf;
	MEMZERO((char *) &utmpbuf, sizeof(utmpbuf));
	utmp_file = fopen(_PATH_UTMP, "r");
	if(utmp_file == NULL) {
		perror("fopen utmp");
		return "-unable to open utmp\n";
	}
#endif /* USE_UTMPX */
	next_utmp_entry();

	return "+OK";
}

/*
 * next_utmp_entry
 *
 * sets up next utmp entry with utmp_ok != 0,
 * closes file and utmp_ok == 0 if none.
 */
void
next_utmp_entry()
{
#ifdef USE_UTMPX
	utmp = getutxent();
	if (utmp == NULL)
		utmp_file = NULL;	/* flag end */
#else /* USE_UTMPX */
	while (fread((char *) &utmpbuf, sizeof(utmpbuf), 1, utmp_file) == 1) {
		if (*utmpbuf.ut_line && *utmpbuf.ut_name) {
			return;		/* utmp_file is non-NULL */
		}
	}
	/* if we get here, we're at eof; close & zero utmp_file */
	(void) fclose(utmp_file);
	utmp_file = NULL;	/* indicates no more entries */
#endif /* USE_UTMPX */
}

#ifdef NEXT_ALERTPANEL
#ifdef NEXT_DO_MSSERVER
char *nx_send_to_term(term)
	char *term;
{
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	MSServer *msServer;
	MSNotification *notification;
	
	msServer = [NXAutoreleaseConnection connectionToName:"MSServer"];
	notification = [[MSNotification alloc] initWithSender:[NSString stringWithCString:sender]
		andMessage:[NSString stringWithCString:msg_text]];
	
	[msServer dispatchNotification:notification];
	
	[msServer release];
	[pool release];
	
	return "+OK";
}
#else
char *nx_send_to_term(term)
	char *term;
{
	/*
	 *	for /usr/local/bin/Alert (PATH_TO_RUNALERT)
	 */

	char outline[LINELENGTH];
	char outline2[LINELENGTH];
	time_t aclock;
	struct tm *tclock;
	int before;
	char *atpos;
	int length;
	int ccount;
	int lcount;
	
	char buffer[LINELENGTH*3];
	
	sprintf(outline2, "Message from %s", sender);
	sprintf(outline, "Arrived at ");
	
	/* make a pretty header line */
	time(&aclock);
	tclock = localtime(&aclock);
#ifdef USE_STRFTIME
#ifdef NEW_STRFTIME
	strftime(&outline[strlen(outline)], LINELENGTH, "%R %A %e %B ", tclock);
#else /* NEW_STRFTIME */
	strftime(&outline[strlen(outline)], LINELENGTH, "%H:%M %A %d %B ", tclock);
#endif /* NEW_STRFTIME */
#else /* USE_STRFTIME */
	strcat(outline, asctime(tclock));
	outline[strlen(outline)-1] = ' '; /* strip lf */
#endif /* USE_STRFTIME */
	outline[LINELENGTH-1] = '\0';

/* truncate message */
	if(strlen(msg_text)>=LINELENGTH) {
		msg_text[LINELENGTH] = '\0';
	}
	
	sprintf(buffer, "%s \"%s\" \"%s\n %s\" &", PATH_TO_RUNALERT, outline2, outline, msg_text);
	system(buffer);
	
	
	return "+OK";

}

#endif
#endif

char *
send_to_term(term)
	char *term;
{
	/*
	 * write to specific terminal if any, else console
	 */
	char device[32];
	struct stat statbuf;
	int rc;
	FILE *stream;

	sprintf(device, "/dev/%s", term);
	if (stat(device, &statbuf)) {
		perror("stat");
		return("-unable to stat %s", device);
	}
	
	if (debug) printf("writing to %s\n", device);

#ifndef NO_S_IWGRP
	if (! (statbuf.st_mode & (S_IWGRP | S_IWOTH))) {
#else /* NO_S_IWGRP */
	if (! (statbuf.st_mode & (022))) {
#endif
		if (debug) printf("won't write to %s because mode is %o\n",
			device, statbuf.st_mode);
		return("-their messages are turned off");
	}

	stream = fopen(device, "w");
	if(stream == NULL) {
		perror("fopen");
		return("-unable to write");
	}
	output_message(stream, 1, 1);
	rc = fclose(stream);
	if (rc) {
		perror("fclose");
		return("-unable to close terminal");
	}
	if(debug) printf("delivery successful\n");

	return "+OK";
}


void
errcat(errmsg, message)
	char **errmsg;
	char *message;
{
	static char ErrLine[256];

	if (*errmsg != NULL) {
		if (**errmsg != '+') {
			strcpy(ErrLine, *errmsg);
			strcat(ErrLine, " - ");
			strcat(ErrLine, message);
			*errmsg = ErrLine;
		}
	}
	else {
		strcpy(ErrLine, "-");
		strcat(ErrLine, message);
		*errmsg = ErrLine;
	}
}


void
store_to_file(pwd, wasread, errmsg)
	struct passwd *pwd;
	int wasread;
	char **errmsg;
{
	FILE *msgfile;
	char fullpath[PATH_MAX+1];
	int rc;
	char *err;

	err = *errmsg;
	if (debug) printf("storing to file\n");

	/* change uid to the user who is receiving the message */
	seteuid(pwd->pw_uid);

	/* open the file */
	fullpath[PATH_MAX] = '\0';
#ifdef SAVE_PATH
	strncpy(fullpath, SAVE_PATH, sizeof(fullpath));
	strncat(fullpath, "/", sizeof(fullpath));
	strncat(fullpath, recipient, sizeof(fullpath));
#else
	strncpy(fullpath, pwd->pw_dir, sizeof(fullpath));
	strncat(fullpath, "/.message", sizeof(fullpath));
#endif
	rc = open(fullpath, O_WRONLY | O_CREAT | O_APPEND, 0600);
	if (rc < 0) {
		perror("open appending message");
		errcat(errmsg, "unable to save");
		return;
	}

	/* lock the file for exclusive access */
#ifdef USE_LOCKFILES
	if (lock_file(fullpath)) {
		perror("can't get lock");
		errcat(errmsg, "unable to save");
		return;
	}
#else
# ifdef USE_LOCKF
	lockf(fileno(msgfile), F_LOCK, 0);
# else
	flock(fileno(msgfile), LOCK_EX);
# endif
#endif

	/* write our message to the file */
	msgfile = fdopen(rc, "a");
	if (msgfile == NULL) {
		close(rc);
		perror("fdopen appending message");
		errcat(errmsg, "unable to save");
		return;
	}
	output_message(msgfile, 0, wasread);

	/* unlock/close the file */
	rc = fclose(msgfile);
	if (rc)
		perror("fclose");

#ifdef USE_LOCKFILES
	rc = unlock_file(fullpath);
	if (rc) {
		perror("fclose");
		return;
	}
#endif
	if (*errmsg == NULL)
		errcat(errmsg, "saved for when they next log on");
	else
		errcat(errmsg, "message saved");
}


/*
 * Output the message to a stream. If it's going to go to the
 * screen, fill it with spaces to the 79th column. If it's going
 * to a file, output only newlines, not cr/lf.
 */

void
output_message(stream, forscreen, wasread)
	FILE *stream;
	int forscreen;
	int wasread;
{
	char outline[LINELENGTH];
	char outline2[LINELENGTH];
	time_t aclock;
	struct tm *tclock;
	int before;
	char *atpos;
	int length;
	int ccount;
	int lcount;

	/* output a leading newline/beep */
	fputs(forscreen ? "\r\n\7" : MESSAGE_SEP, stream);

	/* make a pretty header line */
	time(&aclock);
	tclock = localtime(&aclock);
	if (wasread)
		strcpy(outline, " A message arrived at ");
	else
		strcpy(outline, "* Unread message from ");
#ifdef USE_STRFTIME
#ifdef NEW_STRFTIME
	strftime(&outline[strlen(outline)], LINELENGTH, "%R %A %e %B ", tclock);
#else /* NEW_STRFTIME */
	strftime(&outline[strlen(outline)], LINELENGTH, "%H:%M %A %d %B ", tclock);
#endif /* NEW_STRFTIME */
#else /* USE_STRFTIME */
	strcat(outline, asctime(tclock));
	outline[strlen(outline)-1] = ' '; /* strip lf */
#endif /* USE_STRFTIME */
	MEMSET(outline2, '*', LINELENGTH-1);
	outline[LINELENGTH-1] = '\0';
	outline2[LINELENGTH-1] = '\0';
	before = (LINELENGTH-1 - strlen(outline)) / 2;
	strncpy(&outline2[before], outline, strlen(outline));
	fprintf(stream, "%s%s", outline2, forscreen ? "\r\n" : "\n");

	/* make the "from" line */
	MEMSET(outline, ' ', LINELENGTH-1);
	strncpy(outline, "From:", 5);
#ifndef YUKKY_FROM
	atpos = (char *)STRCHR(sender, '@');
	if (atpos == NULL) {
#endif /* !YUKKY_FROM */
		strncpy(&outline[6], sender, strlen(sender));
#ifndef YUKKY_FROM
		if (!forscreen)
#endif /* !YUKKY_FROM */
			outline[strlen(sender)+6] = '\0';
#ifndef YUKKY_FROM
	}
	else {
		length = strlen(sender);
		strncpy(&outline[6], sender, (long)atpos - (long)sender);
		outline[LINELENGTH-3-length] = '[';
		strncpy(&outline[LINELENGTH-2-length], sender, length);
		outline[LINELENGTH-2] = ']';
	}
#endif /* YUKKY_FROM */
	fprintf(stream, "%s%s", outline, forscreen ? "\r\n" : "\n");

	/* now output the message */
	lcount = 0;
	for (atpos = msg_text, length = 0; *atpos != '\0' && (!forscreen || lcount <= SCRLENGTH); atpos++) {
		if (*atpos == '\r') {
			/* do a newline */
			lcount++;
			/* clear to end of line with spaces for screen */
			if (forscreen) {
				for (ccount = length; ccount < LINELENGTH-1; ccount++)
					putc(' ', stream);
			}
			fputs(forscreen ? "\r\n" : "\n", stream);
			length = 0;

			/* ignore a following newline */
			if (*(atpos+1) == '\n')
				atpos++;
		}
		else if (*atpos == '\t' && forscreen) {	
			/* convert tabs to spaces */
			for (ccount = ((length+8)&0xfff8) - length; ccount > 0; ccount--) {
				putc(' ', stream);
				length++;
			}
		}
		else {
			if (*atpos == '-' && length == 0 &&
			    strncmp(atpos, MSG_SEP, strlen(MSG_SEP)) && !forscreen)
				putc(' ', stream); /* escape message separator */
			if (length < 256 || !forscreen)
				putc(*atpos, stream);		/* don't display too much junk */
			length++;
		}
	}
	if (forscreen && lcount > SCRLENGTH)
		fprintf(stream, "... truncated - run \"msend -l1\" to see the whole message ...\r\n");

	/* output a closing line */
	/* check for a useful signature */
	MEMSET(outline, '*', LINELENGTH-1);
	ccount = strlen(signature);
	atpos = (char *)STRCHR(signature, '\r');
	if (atpos != NULL && (long)atpos - (long)signature < ccount)
		ccount = (long)atpos - (long)signature;
	atpos = (char *)STRCHR(signature, '\n');
	if (atpos != NULL && (long)atpos - (long)signature < ccount)
		ccount = (long)atpos - (long)signature;
	if (ccount > 0) {
		if (ccount > LINELENGTH-5)
			ccount = LINELENGTH-5;
		before = (LINELENGTH-1 - ccount) / 2;
		outline[before-1] = ' ';
		strncpy(outline+before, signature, ccount);
		outline[before+ccount] = ' ';
	}
	fprintf(stream, "%s%s", outline, forscreen ? "\r\n" : "\n");
}


/*
 * As noted in the RFC, it is important to filter out control
 * chracters and suchlike, since there may exist terminals
 * which will behave in bizarre and security-violating ways
 * if presented with certain control code sequences. The
 * client may also be filtering messages, but it is incumbent
 * upon a well-written server implementation not to rely on being
 * sent only "clean" messages.
 *
 * It is an open question as to how the filtering should be done.
 * One approach might be to squeeze out any invalid characters
 * silently. This would make debugging difficult. This implementation
 * replaces all non-printable characters with '?' characters.
 */

void
filter(text)
	char *text;
{
	while (*text) {
		if(!isprint(*text) && !isspace(*text))
			*text = '?';
		text++;
	}
}


/*
 * Used to convert recipient names to lower case
 */

void
downcase(s)
	char *s;
{
	while (*s != '\0') {
		if (*s >= 'A' && *s <= 'Z')
			*s += 'a' - 'A';
		s++;
	}
}

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