SRI-NOSC/ncpp/ftp-s/srvrftp.c

Compare this file to the similar file:
Show the results in this format:

#/*
Module Name:
	srvrftp.c

Installation:
	if $1x = newerx goto newer
	if $1e = finale goto finale
	cc srvrftp.c
	exit
: newer
	if ! { newer srvrftp.c /usr/net/etc/srvrftp } exit
	echo ncpp/ftp-s/srvrftp.c:
: finale
	cc -O -s srvrftp.c
	if ! -r a.out exit
	if ! -r /usr/sys/ncpp/ftp-s/srvrftp.c goto same
	if { cmp -s srvrftp.c /usr/sys/ncpp/ftp-s/srvrftp.c } goto same
	su cp srvrftp.c /usr/sys/ncpp/ftp-s/srvrftp.c
	su chmod 444 /usr/sys/ncpp/ftp-s/srvrftp.c
	rm -f srvrftp.c
: same
	su rm -f /usr/net/etc/srvrftp
	su cp a.out /usr/net/etc/srvrftp
	rm -f a.out
	su chmod 544 /usr/net/etc/srvrftp
	su chown root /usr/net/etc/srvrftp
	su chgrp system /usr/net/etc/srvrftp

Function:
	Perform actions as directed by remote FTP user process.

Globals contained:
	none.

Routines contained:
	main

Modules referenced:
	none.

Modules referencing:
	none.

Compile time parameters and effects:

Module History:
	originally written by someone at University of Illinois.
	Later modified by greep at Rand-Unix.
*/
#include "net_open.h"
/*** #include "net_hostname.h" ***/
#define tel_iac 255
#define SIGINR 15

struct openparams openparams;

struct comarr		/* format of the command table */
{
	char *cmdname;		/* ascii name */
	int (*cmdfunc)();       /* command procedure to call */
};

/* command array */
struct comarr commands[]
{
	"user", &user,
	"pass", &pass,
	"acct", &accept,
	"mail", &mail,
	"mlfl", &datamail,
	"sock", &accept,	/* sock command */
	"abor", &abort,
	"bye",  &bye,
	"noop", &accept,	/* no op */
	"stat", &ftpstat,	/* STAT command */
	"help", &help,

/*	This is the fence between log and non-log required commands */
/*      Index 10 is used in the commands array be sure to change     */
/*	NOLOGCOM if commands are added above here		    */

	"retr", &retrieve,
	"stor", &store,
	"appe", &append,
	"dele", &delete,
	"rnfr", &renme_from,
	"rnto",	&renme_to,
	"list", &list,
	"nlst", &nlst,
	"allo", &accept,	/* allocate */
	"rest", &accept,	/* restart */
	"type", &typecomm,
	"mode", &modecomm,
	"stru", &structure,
	"byte", &byte,
	"xcwd", &cwd,           /* change working directory */
	"cwd",  &cwd,		/* change working directory */
	0,      0
};

/* communications buffers/counts/pointers */

char *netptr;		/* next character to come out of netbuf */
int  netcount;		/* number of valid characters in netbuf */
char netbuf[512];	/* the place that has the valid characters */
int  lastpid;           /* pid of last process */

extern int errno;	/* Has Unix error codes */

#define BUFL 80         /* length of buf */
char buf[BUFL];         /* general usage */
char pwbuf[80];		/* password entry for current user */
char username[20];	/* holds <user>:<password> for logging purposes */
char renmebuf[40];	/* holds RNFR argument waiting for RNTO comm */
int  rcvdrnfr;		/* when on says we received RNFR com ok to do RNTO */
char mfname[20];        /* storage for mail file name */

char *pwfile "/etc/passwd";
char *afile  "/usr/net/aliases";
char hex[] "0123456789ABCDEF";  /* Hexadecimal */

/* structure for special network opens */

/*	Stat struct for finding out about file */
struct statb
{
	char	s_minor;	/* minor device */
	char	s_major;	/* major device */
	int 	s_number;	/* i number */
	int	s_mode;		/* mode */
	char	s_links;	/* number of links */
	char	s_uid;		/* user id of owner */
	char	s_gid;		/* group id of owner */
	char	s_size0;	/* high 8 bits of length of file */
	int	s_size1;	/* low 16 bits of len of file */
	int	s_addr[8];	/* block in file */
	int	s_actime[2];	/* time last accessed */
	int	s_modtime[2];	/* time last modified */
} statb;

/* character defines */

#define CEOR	0300	/* end of record for mode = text */
#define CEOF	0301	/* end of file for mode = text */

#define CR	015	/* carriage return */
#define LF	012	/* line feed */
#define NULL	000	/* null */

/* the RESPONSE array */
char *response[]
{
	"200 Last command flushed\r\n",
	"250 File transfer started correctly\r\n",
	"252 File transfer completed ok\r\n",
	"450 I can't find this file\r\n",
	"451 This file is off limits to you\r\n",
	"452 File transfer incomplete: data connection closed\r\n",
	"453 File transfer incomplete: disk is full!\r\n",
	"455 File transfer incomplete: file system error\r\n",
	"455 Mail trouble, please try again later\r\n",
	"504 Action not possible just now\r\n",
	"506 Parameter unknown\r\n"
};

/* defines that map into indexs into the response array */

#define NUM200   0
#define NUM250   1
#define NUM252   2
#define NUM450   3
#define NUM451   4
#define NUM452   5
#define NUM453   6
#define NUM455   7
#define NUM455m  8
#define NUM504   9
#define NUM506  10

/* The TYPE variable	-	modified by type command */

char type;		/* types of transfer */

#define TYPEASCII	'a'	/* type A (default) */
#define TYPEIMAGE	'i'	/* type I */
#define TYPELOCAL	'l'	/* type L */
#define TYPEPRINTER	'p'	/* type p (ascii printer) */
#define TYPEEPRINTER	'e'	/* type e (ebcdic printer) */

/* The MODE variable	-	modified by the mode command */

char mode;		/* modes of transfer */

#define MODESTREAM	's'	/* stream mode (default) */
#define MODETEXT	't'	/* text mode (uses CEOF and CEOR) */
#define MODEBLOCK	'b'	/* block mode headers etc (not implemented) */
#define MODEHASP	'h'	/* hasp mode standard hasp( "        "    ) */

/* The STRUCT variable	-	modified by the stru command */

char fstru;		/* structure of transfer */

#define STRUCTFILE	'f'	/* stru f (default) */
#define STRUCTRECORD	'r'	/* stru r (used with mode=TEXT) */

/* the NETSTATE variable	defines generally what commands are expected */

int netstate;		/* state of the nation */

#define EXPECTUSER	0	/* waiting for user command */
#define EXPECTPASS	1	/* got a user now need password */
#define EXPECTCMD	2	/* logged in now transfer commands */

#define MAXLOGTRIES     3       /* number of login tries */
int     logtries;		/* current number of login tries */

/*	Command fence between log and non-log required commands */

struct comarr *NOLOGCOMM;
#define U4	4	/* offset from base telnet socket */
#define U5	5	/* offset from base telnet socket */

/* Current User and Group ids */
int curuid;		/* set by the loguser procedure */
int curgid;		/* in resonse to a user pass sequence */

int just_con_data;	/* used by getline procedure to limit function */
char *arg;		/* zero if no argument - pts to comm param */
/*name:
	main

function:
	ARPA network server telnet program 

	Takes commands from the assumed network connection (file desc 0)
	under the assumption that they follow the ARPA network file
	transfer protocol NIC doc #10596 RFC 354 and appropriate modifications.
	Command responses are returned via file desc 1.

	

algorithm:
	process structure:

		There is a small daemon waiting for connections to be
		satisfied on socket 3 from any host.  As connections
		are completed by the ncpdaemon, the returned file descriptor
		is setup as the standard input( file desc 0 ) and standard
		output( file desc 1 ) and this program is executed to 
		deal with each specific connection.  This allows multiple
		server connections, and minimizes the system overhead
		when connections are not in progress.  File descriptors
		zero and one are used to ease production debugging and
		to allow the forking of other relavent Unix programs
		with comparative ease.


	main itself:

		while commands can be gotten
			find command procedure
				call command procedure
				get next command
			command not found


parameters:
	none

returns:
	nothing

globals:
	commands

calls:
	getline
	strmatch
	netreply
	( any of the procedure in the commands array )

called by:
	small server daemon program 

history:
	initial coding 4/12/76 by S. F. Holmgren

notes and memorabilia:

	This is an initial release and has a few shortcomings
	
		1.  The reply messages should be fancied up
		    a little.

		2.  Several commands are not implemented I
		    cant find documentation on what they are
		    supposed to do known ones are: NLST, QUO
		    NOQUO.

		3.  A HELP command needs to be written.

		4.  There is a companion daemon that starts this
		    up (srvrdaemon.c).  We stick the thing in
		    /etc/srvrdaemon and start it in the rc file.

		5.  The MODE T is untested. Cant get tenex to use it.
		    Its extremely well desk checked, has a good shot.

		6.  The set user id bit must be set on srvrftp it needs
		    to call setgid and setuid.

		7.  Some of the accounting functions need to be put in.
		    The stuff that the shell does with utmp and wtmp
		    needs to be copied and put in 'loguser'.  There is
		    a comment that says where.

		8.  This came up much to quickly, (2 days from compile
		    to bouncing baby) there must be other things I have
		    forgotten.

	Ive been power coding much to much lately.  If anyone has the
	urge to dig in and help with some of these please feel free.


*/

main()
{
	register struct comarr *comp;

	signal(SIGINR,1);       /* ignore INS interrupts */
	/* get connection info here */
	fstat( 0,&openparam );  /* get frn host, read local and frn sockets */

	/* set defaults for transfer parameters */
	type = TYPEASCII;	/* ascii */
	mode = MODESTREAM;	/* stream */
	fstru = STRUCTFILE;	/* file structured */
	/* init NOLOGCOM */
	NOLOGCOM = &commands[10];
	/* say were listening */
	netreply( "300 NOSC-SDL Unix Server FTP -- Hello!\r\n" );

	nextcomm:
	while( getline() )
	{
		/* find and call command procedure */
		comp = commands;
		while( comp->cmdname )		/* while there are names */
		{
		    if( strmatch( buf,comp->cmdname ) )     /* a winner */
		    {
			if( comp <= NOLOGCOM || netstate == EXPECTCMD )
				(*comp->cmdfunc)();     /* call comm proc */
			else
			{       if( netstate == EXPECTPASS )
				    netreply("504 Give me your password chucko\r\n");
				else
				    netreply("504 Log in with USER and PASS\r\n" );
			}
			goto nextcomm;                          /* back for more */
		    }
		    comp++;         /* bump to next candidate */
		}
		netreply( "500 I never heard of that command before\r\n" );
	}
}

/*name:
	getline

function:
	get commands from the standard input terminated by <cr><lf>.
	afix a pointer( arg ) to any arguments passed.
	ignore carriage returns
	map UPPER case to lower case.
	manage the netptr and netcount variables

algorithm:
	while we havent received a line feed and buffer not full

		if netcount is zero or less
			get more data from net
				error: return 0
		check for delimiter character
			null terminate first string
			set arg pointer to next character
		check for carraige return
			ignore it 
		if looking at command name
			convert upper case to lower case

	if command line (not mail)
		null terminate last token
	manage netptr

parameters:
	none

returns:
	0 when an error occurs on telnet connection
	ptr to last character (null) in command line

globals:
	just_con_data
	netcount=
	netptr =
	buf=

calls:
	read (sys)

called by:
	main

history:
	initial coding 4/12/76 by S. F. Holmgren

*/

getline()
{

	register char *inp;     /* input pointer in netbuf */
	register char *outp;    /* output pointer in buf */
	register int c;         /* temporary char */
	int cmdflag;            /* looking for telnet command */

	inp = netptr;
	outp = buf;
	arg = 0;
	cmdflag = 0;

	do
	{
		if( --netcount <= 0 )
		{
			if( (netcount = read( 0,netbuf,512 )) <= 0 )
				return( 0 );
			inp = netbuf;
		}
		c = *inp++ & 0377;

		if ( cmdflag )          /* was last char IAC? */
		{       cmdflag = 0;    /* if so ignore this one too */
			c = 0;          /* make sure c != \n so loop won't */
			continue;       /*  end */
		}

		if ( c == tel_iac )     /* is this a telnet IAC? */
		{       cmdflag++;      /* if so note that next char */
			continue;       /*  to be ignored */
		}

		if ( c == '\r' ||       /* ignore CR */
		     c >= 0200 )        /* or any telnet codes that */
			continue;       /*  to sneak through */

		if( just_con_data == 0 && arg == 0 )
		{
			/* if char is a delim afix token */
			if( c == ' ' || c == ',' )
			{
				c = NULL;       /* make null termed */
				arg = outp + 1; /* set arg ptr */
			}
			else
			/* do case mapping (UPPER -> lower) */
			if( c >= 'A' && c <= 'Z' )
				c =+ 'a' - 'A';
		}
		*outp++ = c;
	}
	while( c != '\n' && outp < &buf[BUFL] );

	/* scan off blanks in argument */
	if( arg )
		while( *arg == ' ' ) arg++;

	if( just_con_data == 0 )
		*--outp = 0;            /* null term the last token */
	/* reset netptr for next trip in */
	netptr = inp;
	/* return success */
	return( outp );
}

/*name:
	retrieve

function:
	implement the retr command for server ftp
	take data from the local file system and ship it
	over the network under the auspicies of the mode/type/struct
	parameters

algorithm:
	fork off a data process
		try and open the local data file
			signal error
			exit
		send sock command
		open the data connection
		send the data on the u+5,u+4 socket pair

parameters:
	none

returns:
	nothing

globals:
	arg

calls:
	fork1
	open (sys)
	numreply
	sendsock
	dataconnection
	sendata

called by:
	main thru command array

history:
	initial coding 4/12/76 by S. F. Holmgren
	fork changed to fork1 by greep

*/

retrieve()
{

	int localdata;		/* file id of local data file */
	int netdata;		/* file id of network data file */

	if( ( lastpid = fork1() ) == 0 )
	{
		/* child comes here */
		if( (localdata = open( arg,0 )) < 0 )	/* open file for read */
		{
			switch( errno )
			{       case 2:                 /* ENOENT */
					numreply( NUM450 );
					break;
				case 13:                /* EACCESS */
					numreply( NUM451 );
					break;
				default:                /* other */
					numreply( NUM455 );
			}
			exit( 1 );
		}

		/* send SOCK command */
		sendsock( U5 );

		/* Open data connection */
		netdata = dataconnection ( U5 );

		/* say transfer started ok */
		numreply( NUM250 );

		/* send data according to params */
		sendata( localdata,netdata );

		/* say transfer completed ok */
		numreply( NUM252 );

		exit( 0 );
	}
}

/*name:
	store

function:
	receive data from the network and store upon the local file
	system

algorithm:
	fork a data process
		try and create a local file
			signal error
			exit
		send sock command
		open data connection
		receive data

parameters:
	local file name to open (arg)

returns:
	nothing

globals:
	arg

calls:
	fork1
	creat (sys)
	numreply
	sendsock
	dataconnection
	rcvdata

called by:
	main thru command array

history:
	initial coding 4/12/76 by S. F. Holmgren
	fork changed to fork1 by greep

*/

store()
{

	int localdata;		/* file id for local data file */
	int netdata;		/* file id for network data file */

	if( ( lastpid = fork1() ) == 0 )
	{

		/* create new file */
		if( (localdata = creat( arg,0644 )) < 0 )
		{
			numreply( NUM451 );	/* access denied */
			exit( 1 );
		}

		/* send socket command */
		sendsock( U4 );

		/* open data connection */
		netdata = dataconnection( U4 );

		/* say transfer started ok */
		numreply( NUM250 );

		/* receive and translate data according to params */
		rcvdata( netdata,localdata );

		/* say completed ok */
		numreply( NUM252 );

		exit( 0 );
	}
}

/*name:
	append

function:
	append data from the net to an existing file (file is created if
	it doesnt exist.

algorithm:
	fork a data process
		try and open the file
			try and creat the file
				signal error
				exit
		seek to the end of the file
		send a sock command
		open the data connection
		receive the data

parameters:
	file name to append/create

returns:
	nothing

globals:
	arg

calls:
	fork1
	open (sys)
	creat (sys)
	numreply
	seek (sys)
	sendsock
	dataconnection
	rcvdata

called by:
	main thru command array

history:
	initial coding 4/12/76 by S. F. Holmgren
	fork changed to fork1 by greep

*/

append()
{

	int localdata;		/* file id of local data file */
	int netdata;		/* file id of network data file */

	if( ( lastpid = fork1() ) == 0 )
	{

		/* try and open file -- it may exist */
		if( (localdata = open( arg,1 )) < 0 )
		{
			/* doesnt exist try and creat */
			if( (localdata = creat( arg,0644 )) < 0 )
			{
				numreply( NUM451 );	/* denied access */
				exit( 1 );
			}
		}
		else	/* it exists seek to end */
			seek( localdata,0,2 );

		/* send socket command */
		sendsock( U4 );

		/* open data connection */
		netdata = dataconnection( U4 );

		/* say transfer started ok */
		numreply( NUM250 );

		/* rcv and translate according to params */
		rcvdata( netdata,localdata );

		/* say transfer completed ok */
		numreply( NUM252 );

		exit( 0 );

	}
}

/*name:
	dataconnection

function:
	connect with either U4,U5 or U+5,U+4 from the base
	currently available thru file descriptor 0.

algorithm:
	set up appropriate open parameters
	try to open the file
		signal error
		exit
	return opened file

parameters:
	offset - offset from local socket

returns:
	file descriptor of opened data connection

globals:
	openparam=

calls:
	open (sys)
	netreply

called by:
	retrieve
	store
	append

history:
	initial coding 4/12/76 by S. F. Holmgren

*/

dataconnection( offset )
{

	register netfid;	/* id of network data connection */

	openparam.o_lskt = offset;      /* set local from telnet base */
	openparam.o_fskt[0] = 0;        /* clear any high bits */
	openparam.o_fskt[1] = (offset+1)&05;   /* make (u+4,u+5) or (u+5,u+4) */
	openparam.o_relid = 1; /* specify local telnet base */
	/* specify a relative, direct open that we initiate */
	openparam.o_type = (RELATIVE | DIRECT | INIT );
	openparams.o_nmal = 512;        /* set allocation level */

	/* try and open network data connection */
	if( (netfid = open( "/dev/net/anyhost",&openparam )) < 0 )
	{
		netreply( "454 Can't Establish Data Connection.\r\n" );
		exit( 1 );
	}
	return( netfid );	/* give file id back */
}

/*name:
	sendata

function:
	transfer data from the local disk file system to the network

algorithm:
	while data can be buffered
		thru the current buffer
			if type is ascii
				if mode is text
					translate lf->CEOR
				else
					translate lf->crlf
				translate cr -> cr null

		write the translated contents
			signal error
			exit

	if mode is text
		send eof

parameters:
	infile - file to get data from
	outfile - file to write data to

returns:
	nothing

globals:
	netbuf=
	typecomm
	mode
	fstru

calls:
	read (sys)
	write (sys)
	numreply

called by:
	retrieve

history:
	initial coding 4/12/76 by S. F. Holmgren
	check for image transfer and do a full buf write 8/22/76 S. Holmgren

*/

sendata( infile,outfile )
{

	char tranbuf[1024];		/* buffer to do expansion */
	register char *inp;		/* input data */
	register char *outp;		/* output data */
	register int  incnt;		/* input count */
	char *transtart;		/* start addr of tranbuf */

	transtart = &tranbuf[0];

	while( (incnt = read( infile,netbuf,512 )) > 0 )
	{

		if( type == TYPEIMAGE )		/* if image just do the write */
		{
			write( outfile,netbuf,incnt );
			continue;
		}

		inp = netbuf; outp = tranbuf;	/* attach pointers */
		do
		{
			if( type == TYPEASCII )	/* binary or ascii? */
			{
				/* ascii ( play crlf games ) */
				if( *inp == LF )	/* find a linefeed */
					if( mode == MODETEXT && fstru == STRUCTFILE )
						*inp = CEOR;	/* make end of record */
					else
						*outp++ = CR;	/* make crlf */
				else
				if( *inp == CR )	/* find a cr */
				{
					*outp++ = CR;	/* tran to crnull */
					*inp = NULL;
				}
			}
			*outp++ = *inp++;	/* copy input to output */
		}
		while( --incnt );		/* thru num of characters */

		if( write( outfile,transtart,( outp-transtart )) <= 0 )
		{
			numreply( NUM452 );             /* data error */
			exit( 1 );
		}
	}

	/* if mode is text send an end of file */
	if( mode == MODETEXT )
	{
		tranbuf[0] = CEOF;
		write( outfile,transtart,1 );
	}
}

/*name:
	rcvdata

function:
	receive data from the network and translate contents
	according to global params mode/type/fstru

algorithm:
	while there are buffers to be gotten
		if we need a line feed handle the problem
		thru the data in the buffer
			copy input to output
			if type is ascii
				if char is CR
					if we have another char
						if next char LF
							ignore CR
						if next char NULL
							ignore NULL
						if needed LF
							output needed CR
						reset needlf
					else
						not enough chars say needlf
				else
				mode is TEXT
					char is end of record
						make char line feed
					char is end of file
						flush present buffer
						return success

			increment output pointer

		flush present buffer

	if mode is TEXT
		signal error
		return failure

	return success

parameters:
	infile - file to get data from
	outfile - file to write data to

returns:
	-1 for failure
	0  for success

globals:
	type
	mode
	netbuf=

calls:
	read (sys)
	write (sys)
	netreply

called by:
	store
	append

history:
	initial coding 4/12/76 by S. F. Holmgren
	check for image transfer and do a full buf write 8/22/76 S. Holmgren

*/

rcvdata( infile,outfile )
{

	register char *inp;		/* input data ptr */
	register char *outp;		/* output data ptr */
	register int  incnt;		/* input data count */
	int  crflag;			/* just received cr do something */
	char tranbuf[1024];		/* buffer to stick output in */
	char *transtart;		/* contains &tranbuf[0] */

	crflag = 0;
	transtart = &tranbuf[0];	/* yes it really contains start addr */

	while( (incnt = read( infile,netbuf,512 )) > 0 )
	{

		if( type == TYPEIMAGE )
		{
			write( outfile,netbuf,incnt );	/* just write data */
			continue;
		}

		inp = netbuf;		/* setup input ptr */
		outp = tranbuf;		/* setup output ptr */
		do
		{
			if( type == TYPEASCII )		/* ascii translation */
			{
				if( *inp == CR )	/* found a cr */
				{
					if( crflag++ == 0 )	/* ignore first one */
					{
						inp++;		/* ignore cr */
						continue;
					}
				}
				else
				if( *inp == LF )	/* cr lf seq */
					crflag = 0;	/* yes ignore cr */
				else
				if( *inp == NULL && crflag )	/* cr null seq */
				{
					crflag = 0;	/* yes ignore null */
					*inp = CR;	/* substitute cr */
				}
				else
				if( mode == MODETEXT )	/* ascii & text mode */
				{
					if( *inp == CEOR )	/* end of rec */
						*inp = LF;	/* sub a line feed */
					else
					if( *inp == CEOF )	/* end of file */
					{
						write( outfile,transtart,(outp-transtart));
						return(0);	/* return success */
					}
				}
				else	/* default */
				if( crflag )	/* cr <anything> seq */
				{
					crflag = 0;
					*outp++ = CR;	/* put out a CR */
				}
			}
			*outp++ = *inp++;	/* copy input to output */
		}
		while( --incnt );	/* while there are characters */
		/* write contents of buffer */
		write( outfile,transtart,( outp-transtart ) );
	}

	/* if text mode and we got here say data error */
	if( mode == MODETEXT )
	{
		numreply( NUM452 );		/* data error */
		return( -1 );			/* return error */
	}

	return( 0 );				/* return success */
}

/*name:
	sendsock

function:
	send ftp sock command

algorithm:
	send ascii sock string
	send ascii local socket number
	send cr lf

parameters:
	offset from base socket to send

returns:
	nothing

globals:
	openparam.o_lskt

calls:
	netreply
	locv (sys)

called by:
	store
	retrieve
	append

history:
	initial coding 4/12/76 by S. F. Holmgren

*/

sendsock( offset )
{

	netreply( "255 SOCK " );
	netreply( locv( 0,openparam.o_lskt+offset ) );
	netreply( "\r\n" );
}

/*name:
	user

function:
	handle USER command 

algorithm:
	max login tries exceeded
		signal error
		exit

	expecting user command
		signale error
		reset state
		return

	save user name
	request password

parameters:
	none

returns:
	nothing

globals:
	netstate=
	logtries=
	username=
	arg

calls:
	netreply
	strmove

called by:
	main thru command array

history:
	initial coding 4/12/76 by S. F. Holmgren

*/

user()
{

	if( netstate != EXPECTUSER )
	{       numreply( NUM504 );
		return;
	}

	if( logtries++ > MAXLOGTRIES )
	{
		netreply( "430 Login attempts exceeded, go away\r\n" );
		exit( 1 );	/* error exit */
	}

	/* set state to expecting a PASS command */
	netstate = EXPECTPASS;

	/* save the username in username buf */
	strmove( arg,username );

	/* ask him for password */
	netreply( "330 Enter PASS Command\r\n" );
}

/*name:
	pass

function:
	handle the PASS command

algorithm:
	do we know this user
		signale error 
		set state to EXPECTUSER
		return
	do appropriate logging
	say he is logged in

parameters:
	none

returns:
	nothing

globals:
	netstate=

calls:
	getuser
	netreply
	loguser

called by:
	main thru command array

history:
	initial coding 4/12/76 by S. F. Holmgren

*/

pass()
{

	if( netstate != EXPECTPASS )
	{       numreply( NUM504 );
		return;
	}

	/* get the users password entry */
	if( getuser( 1,pwfile ) == 0 )
	{
		/* unknown user */
		netreply( "431 I don't know you, try again\r\n");
		netstate = EXPECTUSER;	/* waiting for user command */
		return;
	}

	/* got user */
	netstate = EXPECTCMD;
	/* do appropriate accounting and system info work */
	loguser( 1 );

	/* say he is logged in */
	netreply( "230 User Logged in\r\n" );
}

/*name:
	getuser

function:
	find a match for the user in username and password in arg
	in /etc/passwd

algorithm:
	open the password file
		signal no wanted users
		exit
	build <username>:<password>\0
	search thru password file for match

parameters:
	user name in username password in arg
	name of file to look in

returns:
	1 if user found
	0 failure

globals:
	username=
	pwbuf=

calls:
	open (sys)
	strmove
	gets
	strmatch
	colon
	close (sys)

called by:
	pass

history:
	initial coding 4/12/76 by S. F. Holmgren

*/

getuser( user_pass,filename )
char *filename;
{

	register char *p;		/* general use */
	register int  retval;		/* return value  = 1 success
					                 = 0 failure
					 */
	struct
	{
		int fildes;
		int nleft;
		char *nextp;
		char buf[512];
	} iobuf;

	retval = 0;			/* default to failure */
	iobuf.nleft = 0;		/* make sure inited */
	iobuf.nextp = 0;		/* make sure inited */

	/* open the password file */
	if ( (iobuf.fildes = open( filename,0 )) < 0 )
	{
		netreply( "401 Users not accepted now, try later\r\n" );
		exit( 1 );		/* fail exit */
	}


	if( user_pass )		/* password required also? */
		strmove( crypt( arg ),strmove( ":",strmove(username,username)));

	/* look thru the entries in the password file for a match */
	while( gets( &iobuf,pwbuf ) )		/* get entry from pwfile in pwbuf */
	{
		p = colon( pwbuf );		/* skip to colon after user name */
		if( user_pass )			/* need pass also */
			p = colon( p );		/* get to colon after passwd */
		*--p = 0;	/* terminate <user>:<passwd>0 */
		if( strmatch( username,pwbuf ))		/* we got a winner */
		{
			retval++;			/* return success */
			break;
		}

	}
	close( iobuf.fildes );			/* close password file */
	return( retval );
}

/*name:
	loguser

function:
	perform installation/system specific login/accounting functions

algorithm:
	build user id

	build group id

	change to working directory

	set user id
	set group id

parameters:
	user information in pwbuf

returns:
	nothing

globals:
	pwbuf=
	curuid=
	curgid=

calls:
	strmove
	setgid (sys)
	setuid (sys)
	colon
	chdir (sys)

called by:
	pass

history:
	initial coding 4/12/76 by S. F. Holmgren

*/

loguser( fullog )
{

	register char *passentp;	/* pointer to password entry */
	register char *p;		/* general usage */
	register int  num;		/* constructing uid and gid */

	passentp = strmove( pwbuf,pwbuf );	/* get to end of <user>:<pw>0 */

	/* if there is nothing in pwbuf forget it */
	if( passentp == &pwbuf[0] ) return;

	if( fullog == 0 )
		passentp = colon( passentp );	/* if not full log need another */
	else
		passentp++;			/* a little adjustment music */

	num = 0;
	/* build user id */
	while( *passentp != ':' )
		num = num*10 + *passentp++ - '0';
	curuid = num;			/* save user id globally */

	num = 0;
	/* build group id */
	while( *++passentp != ':' )
		num = num*10 + *passentp - '0';
	curgid = num;			/* save group id globally */

	/* skip over GCOS mailbox to beginning of working dir */
	passentp = colon( ++passentp );

	/* null terminate working directory */
	p = colon( passentp );
	*--p = NULL;
	chdir( passentp );

	if( fullog )
	{
		/* do accounting here */

		setuid( curuid );		/* set user id */
		setgid( curgid );		/* set group id */
	}
}

/*name:
	getmbox

function:
	return a file descriptor for a temporary mailbox

algorithm:
	create unique file name
	create file
	if can't
		signal error
	return mailbox file descriptor

parameters:
	none

returns:
	file descriptor of open mailbox file

globals:
	mfname

calls:
	loguser
	stat (sys)
	creat(sys)
	numreply
	open (sys)
	seek (sys)

called by:
	mail
	datamail

history:
	initial coding 6/30/76 by S. F. Holmgren
	rewritten by greep for Rand mail system

*/
getmbox()
{
	register int mboxfid;

	crname( mfname );       /* create mailbox file name */

	mboxfid = creat(mfname,0644);
	if (mboxfid < 0)
	{       numreply( NUM451 );
		return( -1 );
	}
	return( mboxfid );
}
/*name:
	crname

function:
	create a unique file name

algorithm:
	increment count
	if count is 256
		sleep one second to make time different
	get date-time, process number, and count
	convert to hexadecimal

parameters:
	address of string where result is to be put

returns:
	nothing

globals:
	hex

calls:
	getpid (sys)
	sleep (sys)
	time (sys)

called by:
	getmbox

history:
	written by greep for Rand mail system
*/
crname(ptr)
char *ptr;
{
	int i;
	int tvec[4];
	static int filecnt;
	register char *p, *q;

	p = "/tmp/";
	q = ptr;
	while (*q++ = *p++);
	--q;
	p = &tvec[0];
	tvec[2] = getpid();
	tvec[3] = filecnt++;
	if (filecnt==256)
	{       filecnt = 0;
		sleep(1);
	}
	time(tvec);
	for (i=7; i; --i)
	{       *q++ = hex[(*p>>4)&017];
		*q++ = hex[ *p++  &017];
	}
	*q = '\0';
}


/*name:
	mail

function:
	handle the MAIL <user> command over the command connection

algorithm:
	see if we have a known user

	if mailbox file cant be gotten
		return
	tell him it is ok to go ahead with mail

	while he doesnt type a period
		read and write data
	say completed

parameters:
	username in arg

returns:
	nothing

globals:
	arg
	username=

calls:
	strmove
	findmbox
	loguser
	getmbox
	write (sys)
	getline
	chown (sys)
	time (sys)
	printf (sys)

called by:
	main thru command array

history:
	initial coding 		Mark Kampe UCLA-ATS
	modified 4/13/76 by S. F. Holmgren for Illinois version
	modified 6/30/76 by S. F. Holmgren to call getmbox
	modified 10/18/76 by J. S. Kravitz to improve net mail header
	modified by greep for Rand mail system

*/
mail()
{
	register mboxfid;	/* mailbox file desc */
	register char *p;	/* general use */
	int tyme [2];		/* for the time */

	/* make sure there is a user name */
	if( arg == 0 )
		arg = "mother";         /* default to mother */
	strmove( arg,username );	/* stick user arg in username for getuser */
	if( findmbox() == 0 )
	{
		netreply( "450 User Unknown\r\n" );
		return;
	}

	/* get to open mailbox file descriptor */
	if( (mboxfid = getmbox()) < 0 )
		return;

	/* say its ok to continue */
	netreply( "350 Enter Mail, end by a line with only '.'\r\n" );

/***    time (tyme);
	printf (mboxfid, "\n--- Network Mail from host %s on %20.20s ---\n"
		,hostnames[openparam.o_host & 0377]
		,ctime (tyme)
		);                      ***/

	just_con_data = 1;			/* tell getline only to drop cr */

	while( 1 )				/* forever */
	{
		if( (p = getline()) == 0 )
		{
			write(mboxfid,"\n***Sender closed connection***\n",32 );
			break;
		}
		/* are we done */
		if( buf[0] == '.' && buf[1] == '\n' )
			break;                  /* yep */
		write( mboxfid,buf,(p-buf) );	/* stick data in mailbox */
	}
	just_con_data = 0;	/* set getline to normal operation */
/***    time (tyme);
	printf (mboxfid, "\n--- Network mail ended on %20.20s ---\n"
		,ctime (tyme)
		);              ***/
	close( mboxfid );
	if( sndmsg() == -1 )    /* call sndmsg to deliver mail */
	{       numreply( NUM455m );    /* something wrong! */
		unlink( mfname );       /* delete temporary file */
	}
	else
	/* say mail delivered */
		netreply( "256 Mail Delivered \r\n");
}
/*name:
	datamail

function:
	handle the MLFL command

algorithm:
	fork
		make sure we have a valid user
			say bad user and exit
		send sock command
		open data connection
		get open mailbox file descriptor
		call rcvdata to receive mail

parameters:
	username in arg

returns:
	nothing

globals:
	arg

calls:
	fork1
	strmove
	findmbox
	netreply
	sendsock
	dataconnection
	getmbox
	rcvdata
	printf (sys)
	time (sys)

called by:
	main thru command array 

history:
	initial coding 4/13/76 by S. F. Holmgren
	modified 10/18/76 by J. S. Kravitz to put net mail header
	modified by greep for Rand mail system

*/
datamail()
{
	register netdata;
	register mboxfid;
	int tyme [2];

	if( ( lastpid = fork1() ) == 0 )
	{
		if( arg == 0 )	/* no user speced default to root */
			arg = "root";
		else
		{
			strmove( arg,username );	/* move user to username */
			if( findmbox() == 0 )           /* valid user */
			{
				netreply( "451 User Not Recognized\r\n" );
				exit( 1 );
			}
		}

		/* send sock command */
		sendsock( U4 );

		/* open data connection */
		netdata = dataconnection( U4 );

		/* try and get an open file descriptor for mailbox file */
		if( (mboxfid = getmbox()) < 0 )
			exit( 1 );

/***            time (tyme);
		printf (mboxfid, "\n--- Network Mlfl from host %s on %20.20s ---\n"
			,hostnames[openparam.o_host & 0377]
			,ctime (tyme)
			);              ***/
		/* say its ok to proceed */
		numreply( NUM250 );

		/* get data from net connection and copy to mail file */
		rcvdata( netdata,mboxfid );


/***            time (tyme);
		printf (mboxfid, "\n--- Network MLFL transmission ended on %20.20s ---\n"
			,ctime (tyme)
			);      ***/
		/* make the current guy owner of the mail box file */
		close( mboxfid );
		if( sndmsg() == -1 )    /* call sndmsg to deliver mail */
		{       numreply( NUM455m );    /* something wrong! */
			unlink( mfname );       /* delete temporary file */
		}
		else
		/* say it went off ok */
			numreply( NUM252 );

		exit( 0 );
	}
}

/*name:
	findmbox

function:
	determine whether a mailbox exists

algorithm:
	change mailbox name to lower case
	if getuser doesn't find name in password file
		try in alias file

parameters:
	none

returns:
	nothing

globals:
	username

calls:
	getuser

called by:
	mail
	datamail

history:
	initial coding 12/15/76 by greep for Rand mail system

*/
findmbox()
{
	register char *p;

	for (p = username; *p; p++)
	{       if (*p >= 'A' && *p <= 'Z')
			*p =| ' ';      /* convert to lower case if req */
	}
	if ( getuser( 0,pwfile ) == 0)
		return( getuser( 0,afile) );
	else
		return( 1 );
}
/*name:
	sndmsg

function:
	call sndmsg to deliver mail

algorithm:
	fork
	execute sndmsg with special argument

parameters:
	none

returns:
	status of sndmsg, -1 if couldn't execute

globals:
	username
	mfname
	openparam.o_host

calls:
	fork (sys)
	exec (sys)

called by:
	mail
	datamail

history:
	initial coding 12/15/76 by greep for Rand mail system

*/
sndmsg()
{
	char sndmsgflg[3];
	int n;
	int status;

	sndmsgflg[0]='\001';    /* special flag for sndmsg */
	sndmsgflg[1]=openparam.o_host;
	sndmsgflg[2]='\0';
	while((n=fork()) == -1) sleep(5);
	if (n == 0)
	{       execl("/bin/sndmsg",sndmsgflg,mfname,username,0);
		exit(-1);
	}
	wait(&status);
	return( status>>8 );
}

/*name:
	list

function:
	handle the LIST command 

algorithm:
	fork
		send sock
		open data connection
		dup net data connection into zero and one
		let the standard unix 'ls' do its thing 

parameters:
	possible directory from arg

returns:
	nothing

globals:
	arg

calls:
	fork
	sendsock
	dataconnection
	close (sys)
	dup (sys)
	execl (sys)

called by:
	main thru command array

history:
	initial coding 4/13/76 by S. F. Holmgren

*/
list()
{
	register netdata;
	register pid;
	int      waitwrd;

	if( (pid = fork()) == 0 )
	{
		/* send sock command */
		sendsock( U5 );

		/* open the data connection */
		netdata = dataconnection( U5 );

		/* say transfer started ok */
		netreply( "250 List started ok\r\n");

		close( 0 );		/* close zero */
		dup( netdata );		/* move in netdata */
		close( 1 );		/* close one */
		dup( netdata );		/* move in netdata */
		close( netdata );	/* close netdata itself */

		/* start up the standard unix 'ls' command */
		execl( "/bin/dir","dir",arg,0 );
		netreply( "507 Can't find listing program!\r\n" );
		exit( -1 );
	}
	else
	{       while( pid != wait( &waitwrd ) );
		/* say transfer completed ok */
		if ( ( waitwrd>>8 ) != -1 )
			netreply( "252 List completed\r\n");
	}
}

/*name:
	nlst

function:
	handle the NLST command --  give directory listing

algorithm:
	fork
		exec the standard unix ls command

parameters:
	possible file name in arg

returns:
	nothing

globals:
	arg

calls:
	netreply
	fork (sys)
	execl (sys)

called by:
	main thru command array

history:
	greep

*/
nlst()
{

	register pid;
	int waitwrd;

	netreply( "151-Listing starting, duck!\r\n");
	if( (pid = fork()) == 0 )	/* child */
	{
		execl( "/bin/dir","dir",arg,0 );
		netreply( "Can't find listing program!\r\n" );
		exit( 1 );
	}
	while( pid != wait( &waitwrd ) );
	netreply( "151 Listing ended (that's all, folks)\r\n");
}
/*name:
	ftpstat

function:
	handle the STAT command -- for now just say if anything happening
	later can add other stuff (current mode, type, &c)

algorithm:
	send a signal to lastpid
	if it worked
		say something is happening
	else say it's not

parameters:
	none

returns:
	nothing

globals:
	none

calls:
	kill (sys)

called by:
	main thru command array

history:
	greep

*/
ftpstat()
{

	if( ( lastpid == 0 ) || ( kill( lastpid,SIGINR ) == -1) )
		netreply( "100 No transfer in progress\r\n" );
	else
		netreply( "100 Transfer in progress\r\n" );
}


/*name:
	byte

function:
	handle the BYTE command

algorithm:
	convert the param to binary
	if it is out of range <= 0 >= 256
		signal error
	if it is not 8
		signial we dont handle this

parameters:
	global param in arg

returns:
	nothing

globals:
	arg

calls:
	netreply
	numreply

called by:
	main thru command array

history:
	initial coding 4/12/76 by S. F. Holmgren

*/
byte()
{

	register int num;
	register char *p;

	p = arg;	/* a little speed */
	num = 0;	/* a little initialization */
	while( *p )	/* convert the number to binary */
		num = num * 10 + (*p++ - '0');
	if(6>>62:28<$606<242922.9.722628<6&>276:1770722:2'"072074:62::272001279437:2064349 not stream or text then 
		say unknown mode
	else
		say mode ok

parameters:
	global param in arg

returns:
	nothing

globals:
	arg
	mode=

calls:
	netreply

called by:
	main thru command array

history:
	initial coding 4/12/76 by S. F. Holmgren

*/
modecomm()
{
	mode = *arg;		/* assign mode */
	if( mode >= 'A' && mode <= 'Z' )        /* convert to lower case */
		mode =+ 'a' - 'A';
	numreply( (mode != 's' && mode != 't') ? NUM506 : NUM200 );
}

/*name:
	sturcture

function:
	handle the STRU command

algorithm:
	set fstru to param
	if not f or r then
		say unknown mode
	else
		say ok

parameters:
	indirectly thru arg

returns:
	nothing

globals:
	arg
	fstru=

calls:
	nothing

called by:
	main thru command array

history:
	initial coding 4/12/76 by S. F. Holmgren

*/
structure()
{
	fstru = *arg;	/* assign struct type */
	/* see if it is ok */
	if( fstru >= 'A' && fstru <= 'Z' )      /* convert to lower case */
		fstru =+ 'a' - 'A';
	numreply( (fstru != 'f' && fstru != 'r') ? NUM506 : NUM200 );
}

/*name:
	type

function:
	handle the TYPE command 

algorithm:
	assign the param to the type variable
	if it isnt a or i then 
		say error
	else
		say ok

parameters:
	indirectly thru arg

returns:
	nothing

globals:
	arg
	type=

calls:
	nothing

called by:
	main thru command array

history:
	initial coding 4/12/76 by S. F. Holmgren

*/
typecomm()
{
	type = (*arg == 'l') ? 'i' : *arg;	/* map type l to type i */
	if( type >= 'A' && type <= 'Z' )        /* convert to lower case */
		type =+ 'a' - 'A';
	numreply( (type != 'a' && type != 'i') ? NUM506 : NUM200 );
}

/*name:
	delete

function:
	delete a file from disk

algorithm:
	unlink the file 
	if error
		say either access or un existant
		return
	say ok

parameters:
	indirectly thru arg

returns:
	nothing

globals:
	arg
	errno (sys)

calls:
	unlink (sys)
	netreply

called by:
	main thru command array

history:
	initial coding <virtual programmer> at Rand Corp
	modified by S. F. Holmgren for Illinois server 4/12/76

*/
delete()
{

	if( unlink( arg ) == -1 )
	{
		numreply( (errno == 2) ? NUM450 : NUM451 );
		return;
	}
	netreply( "254 File flushed\r\n" );
}

/*name:
	cwd - change working directory

function:
	change the current working directory

algorithm:
	use the system chdir entry

parameters:
	new working directory in 'arg'

returns:
	nothing

globals:
	arg

calls:
	chdir (sys)
	numreply

called by:
	main thru command array

history:
	initial coding 6/30/76 by S. F. Holmgren

*/
cwd()
{
	/* see if we can do the change dir */

	if( chdir( arg ) < 0 )
		netreply( "450 Dir unknown or access denied\r\n" );
	else
		netreply( "200 Working directory changed\r\n" );
}

/*name:
	renme_from

function:
	Handle the RNFR command

algorithm:
	save the 'source' file name in buf
	set netstate to expecting a RFTO command
	say command completed

parameters:
	indirectly thru arg

returns:
	nothing

globals:
	arg
	renmebuf=
	rcvdrnfr=

calls:
	strmove
	netreply

called by:
	main thru command array

history:
	initial coding 4/13/76 by S. F. Holmgren

*/
renme_from()
{

	strmove( arg,renmebuf );	/* save the file name in buf */
	rcvdrnfr++;		/* say we got a RNFR */
	netreply( "200 RNFR accepted, please send RNTO next\r\n" );
}

/*name:
	renme_to

function:
	Handle the RNTO command

algorithm:
	make sure a RNFR command has been received
		signal error
		return
	unlink destination file name
	link source file name to destination
	if error
		if error is access
			signal error and return
		if error is file not found 
			signal error and return
		otherwise link failed because of cross device
			copy the file.

parameters:
	indirectly thru arg
	and contents of renamebuf

returns:
	nothing

globals:
	errno (extern to unix sys)
	statb=
	renamebuf
	arg

calls:
	unlink (sys)
	link   (sys)
	numreply
	stat  (sys)
	creat (sys)
	open  (sys)
	read  (sys)
	write (sys)

called by:
	main thru command array

history:
	initial coding 4/13/76 by S. F. Holmgren

*/
renme_to()
{
	register destfid;
	register srcfid;
	register cnt;
	char cpybuf[512];

	if( rcvdrnfr == 0 )
	{
		numreply( NUM504 );
		return;
	}
	rcvdrnfr = 0;	/* clear rename to flag */

	/* if cant do stat or file is not standard data file */
	if( (stat( renmebuf,&statb ) == -1) || ((statb.s_mode&060000) != 0) )
	{
		numreply( NUM450 );	/* file not found */
		return;
	}

	/* try and create the new file */
	if( (destfid=creat( arg,statb.s_mode&0777 )) == -1 )
	{
		/* cant create say error and return */
		numreply( NUM451 );
		return;
	}

	/* try and open the source file */
	if( (srcfid=open( renmebuf,0 )) == -1 )
	{
		close( destfid );
		numreply( NUM451 );	/* access denied */
		return;
	}

	/* while there is data in source copy to dest */
	while( (cnt=read( srcfid,cpybuf,512 )) > 0 )
		if( write( destfid,cpybuf,cnt ) < cnt )
		{
			netreply( "453 Warning: Unix write error - aborting\r\n" );
			goto bad;
		}

	/* remove link to source file */
	unlink( renmebuf );
	/* say all went well */
	netreply( "253 Rename Completed\r\n" );

bad:	

	close( srcfid );	/* close source file */
	close( destfid );	/* close dest file */
}

/*name:
	bye

function:
	handle the BYE command

algorithm:
	say goodbye
	exit

parameters:
	none

returns:
	never

globals:
	none

calls:
	netreply
	exit (sys)

called by:
	main thru command array 

history:
	initial coding 4/13/76 by S. F. Holmgren

*/
bye()
{

	netreply( "231 Toodles, call again\r\n" );
	exit( 0 );
}

/*name:
	abort

function:
	handles the ABORT command

algorithm:
	if no transfer process has been started or if kill of last one fails
		give error message
	else give success message

parameters:
	none

returns:
	nothing

globals:
	lastpid

calls:
	kill (sys)
	netreply

called by:
	main thru command array

history:
	initial coding 4/13/76 by S. F. Holmgren
	modified by greep to make tolerable

*/
abort()
{
	if( ( lastpid == 0 ) || ( kill( lastpid,9 ) == -1) )
		netreply( "202 Nothing to abort\r\n" );
	else
		netreply( "201 Operation aborted\r\n" );
}

/*name:
	accept

function:
	to signal the current command has been logged and noted

algorithm:
	say command has been logged and noted

parameters:
	none

returns:
	nothing

globals:
	none

calls:
	numreply

called by:
	called by main thru command array in response to 
		ALLO
		REST
		SOCK
		ACCT
	commands

history:
	initial coding 4/13/76 by S. F. Holmgren

*/
accept()
{
	numreply( NUM200 );
}
/*name:
	help

function:
	give a list of valid commands

algorithm:
	send list

parameters:
	none

returns:
	nothing

globals:
	netstate

calls:
	netreply

called by:
	called by main thru the help command

history:
	greep

*/
help()
{       netreply( "050-The following commands are accepted:\r\n" );
	netreply( "USER PASS ACCT MAIL MLFL SOCK ABOR NOOP HELP BYE\r\n" );
	if( netstate != EXPECTCMD )     /* is he logged in? */
		netreply("The following require that you be logged in:\r\n");
	netreply( "RETR STOR APPE DELE RNFR RNTO LIST NLST STAT ALLO\r\n" );
	netreply( "REST TYPE MODE STRU BYTE XCWD\r\n");
	netreply( "050 Please send complaints/bugs to greep@Rand-Unix\r\n");
}
/*name:
	netreply

function:
	send appropriate ascii responses 

algorithm:
	get the length of the string

	send it to the standard output file

parameters:
	str to be sent to net 

returns:
	nothing

globals:
	none

calls:
	write (sys)

called by:
	the world

history:
	initial coding 4/13/76 by S. F. Holmgren

*/
netreply( str )
char *str;
{
	register char *netstr;		/* string to be sent */
	register char *p;		/* general usage */

	netstr = str;
	/* get length of str */
	for( p=netstr; *p; p++ );

	/* send string */
	write( 1,netstr,(p-netstr) );
}

/*name:
	numreply

function:
	to send ascii strings to net

algorithm:
	take param and index in repsonse array to get char string
	get length of charstring
	send string to net

parameters:
	index into response array 

returns:
	nothing

globals:
	resonse array

calls:
	write (sys)

called by:
	everyone

history:
	initial coding 4/14/76 by S. F. Holmgren

*/
numreply( index )
int index;
{

	register char *netstr;
	register char *p;

	netstr = responses[ index ];	/* get char string */
	for( p=netstr; *p ; p++ );	/* get length of str */
	write( 1,netstr,( p-netstr ) );	/* send to net */
}


/*name:
	fork1

function:
	spawn an orphan and return its pid

algorithm:
	set up a pipe
	fork

	old process:                    new process:
		wait for new                    fork
		  process                       write process number of
		read 2 bytes                      newest process on pipe
		  from pipe

parameters:
	none

returns:
	pid of new process

globals:
	none

calls:
	fork (sys)

called by:
	retrieve
	store
	append
	datamail

history:
	initial coding by greep (spawn stolen from jsz)

*/
fork1()
{       int k, l;
	int pstat;
	int pip[2];
	int pid;

	if (pipe(pip) == -1)    /* this shouldn't happen (I hope) */
		return(-1);
	while ((k = fork()) == -1) sleep(10);
	if (k)
	{       wait(&pstat);           /* Wait for k below to exit */
		read(pip[0],&pid,2);    /* read pid */
		close(pip[0]);          /* close pipe */
		close(pip[1]);
		return (pid);   /* Returns non-zero in parent */
	}
	else
	{	while ((l = fork()) == -1) sleep(10);
		if (l)
		{       write(pip[1],&l,2);     /* write pid of child */
			exit();         /* After creating l, exit. */
		}
		close(pip[0]);          /* close pipe */
		close(pip[1]);
		return(0);
	}
}


/*name:
	colon

function:
	skip to next colon or new line

algorithm:
	scan for colon or linefeed

parameters:
	p - pointer to a character string (usually a password entry)

returns:
	pointer to first character after a delimiter

globals:
	none

calls:
	nothing

called by:
	getuser
	loguser

history:
	initial coding 4/12/76 by S. F. Holmgren

*/

colon( p )
char *p;
{

	register char *cp;

	cp = p;

	while( *cp != ':' && *cp != '\n' ) cp++;
	return( ++cp );		/* return next char after delim */
}

/*name:
	gets

function:
	get characters from iobuffer until a newline is found

algorithm:
	while get a character 
		if char is new line
			stuff in a null
			return success

	return failure

parameters:
	iobuffer - address of an iobuf see getc unix prog manual
	buf      - addres of character array to put data into

returns:
	null terminated line

globals:
	none

calls:
	getc (sys)

called by:
	getuser

history:
	initial coding 4/12/76 by S. F. Holmgren

*/

gets( iobuffer,destbuf )
char *destbuf;
{

	register int  c;
	register char *outp;
	register int  iobuf;

	iobuf = iobuffer;
	outp = destbuf;

	while( (c = getc( iobuf )) > 0 )
		if( c == '\n' )
		{
			*outp = NULL;	/* null terminate */
			return( 1 );	/* return success */
		}
		else
			*outp++ = c;	/* just bump to next spot */

	/* return failure */
	return( 0 );
}

/*name:
	strmove

function:
	move data from one address to another

algorithm:
	copy data from one address to another

parameters:
	src - place to get data from
	dest - place to put data

returns:
	pointer to the null at the end of the concationed string

globals:
	none

calls:
	nothing

called by:
	user
	getuser

history:
	initial coding 4/12/76 by S. F. Holmgren

*/

strmove( from,to )
char *from;
char *to;
{

	register char *src;	/* same as from for speed */
	register char *dest;	/* same as to for speed */

	src = from;  dest = to;

	while( *dest++ = *src++ );

	return( --dest );	/* return ptr to null at end of string */
}

/*name:
	strmatch

function:
	to compare two null terminated strings for equality.

algorithm:
	Do until one of them contains a null;
		if unequal return false
	if both are not null, return false
	else
	return true

parameters:
	char ptrs to the strings to be matched

returns:
	0: if strings differ
	1: if string are identical

globals:
	none

calls:
	nothing

called by:

history:
	designed and coded by Mark Kampe Ucla-ats

*/
strmatch( s1,s2 )
char *s1;
char *s2;
{

	register char *p1;
	register char *p2;

	p1 = s1;	p2 = s2;

	while( *p1 && *p2 )
		if( *p1++ != *p2++ )	return( 0 );
	return( *p1 == *p2 );
}