#include "srv.h" /* * long hosts jsq BBN 3-15-79; note short host sndmsg is still expected. * long host sndmsg is expected now jsq BBN 4-25-79 * Modified by Dan Franklin (BBN) August 11, 1979: to call nsndmsg. * put all host name-number map calls in ftpsrv jsq BBN 18Aug79. * keep both old and new sndmsg calls, add die calls and error reporting * jsq BBN 21Aug79 * Modified by Dan Franklin (BBN) September 20, 1979: to put error message * in when ***sender closed connection***, etc is put in msg. * mail() changed. * Modified by Dan Franklin (BBN) November 1, 1979: increased size of pwbuf * to 512 characters. * Do the same for username jsq BBN 27Nov79. * set home directory jsq BBN 18Dec79. * Modified by dm 5 Jan 80 to permit the person to talk to us on an upper- * case only tty. This required only a change to the getuser routine. * Modified by dm (BBN) Mar 15 80 to implement the multiple store * functionalities of the user ftp, to implement tree-structure storage * changed help() to be more or less self-documenting. someone industrious * should make help accept an argument, just like the protocol says it does * changed netreply() to be defined as fdprintf, which permits one to use * printf-string formatting. [used fdprintf (1, ...), rather than printf, * because fdprintf returns -1 on error * Modified by dm (BBN) Apr 10, 1980: another pass at the netreply() problem... * made netreply use fdprintf (1,"%r", &args), and generally cleaned up * that approach * flushed numreply in favor of improved netreply * improved almost all of the responces, taking advantage of the improved * netreply * added error-logging to ftplog on failure to find important files, and * on other error conditions that shouldn't happen in the first place, * so people unfamiliar with this program can figure out why it doesn't * work when they install it elsewhere, etc. * put the names of the system files srvrftp accesses to the beginning, * so they are easy to find. * dm (bbn) Apr 15, 1980: fixed NLST, so it obeys the protocol (RFC 542). * dm (bbn) Apr 25, 1980: added the XRSQ & XRCP protocols (RFC 743) * lifted some code from KLH@ai, but not much, since their mail system * is drastically different from ours. * also, modified getline() to set arg to 0 if the argument is all blanks * it used to give you a pointer to the null, instead, so the if a user * typed "mail ", arg would be non-zero. * dm (bbn) May 15, 1980: increased the allocation on data connections to * more efficiently squeeze things through the pli * Modified getuser to use tpass when looking up passwords. * pwbuf now holds the virgin password file line as read in by getuser; * loguser changed accordingly, simplified. * Also moved "type" and "mode" commands to behind the NOLOGCOM fence so * users can MLFL without having to log in first. * BBN (dan) May 25, 1980 * dm (bbn) 10 June, 1980: call die through go_die() so the mail_reset() * routine can be called on errors. also, do a mail_reset() on reading * an EOF on the standard input to flush any xrsq/xrcp text. * dm (bbn) 11 August, 1980: fixed dataconnection() & sendsock() to do * server+2, server+3, and user+4, user+5 sockets on dataconnections * dm (bbn) 18 October, 1980: moved "stru" and "byte" above the NOLOGCOM * fence, so Tenex users can MLFL without having to log in first. * also moved "rest" (restart) and "allo" there, too, since they * seem harmless, and i'm sure some system in the world uses them * in sending mail... * dm (bbn) 18 November, 1980: * added the XPWD command * dm (bbn) 1 December, 1980: * converted to be either TCP or NCP FTPs... * * dataconnection now has two arguments: the offset plus a pointer * to a buffer full of network information * it returns the buffer filled with the information * do_list() now sets up a pipe through which the system-programs * (ls, in some flavor) passes its data, to be written on * the net by the parent fork. * rfg (bbn) 21 October, 1981: deleted sendsock per TCP FTP spec */ /*name: main function: ARPA network server ftp 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 seq 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 Changed U4, U5 to be off base instead of telnet Dan Franklin (BBN) August 23, 1978 Changed (temporarily) to use crypt2 instead of crypt for big mother password encryption changeover Dan Franklin (BBN) August 23, 1978 Changed back to crypt, now that it is the standard (dan:BBN) The listing command now execs the shell, so * works. long hosts jsq BBN 3-20-79 change error handling jsq BBN 20Aug79 */ char * syntactify(); /* forward declarations. */ main (argc, argv) char **argv; { register struct comarr *comp; char *i; long atime; char *getline (); setbuf (ERRFD, NULL); fprintf (ERRFD, "srvrftp started\n"); signal (SIGQUIT, SIG_IGN); /* convenient for debugging */ signal (SIGINT, SIG_IGN); inherit_net (&NetParams, 0); get_stuff (&NetParams); /* need connection parameters */ progname = argv[0]; if (argc != 3) { fprintf (ERRFD, "%s: wrong number of arguments\n", progname); netreply ( "451 %s has been incorrectly invoked by ftpsrv!\r\n", progname ); exit (-1); } them = argv[1]; us = argv[2]; time (&atime); fprintf (ERRFD, "%s %s %s", progname, them, ctime (&atime)); /* set defaults for transfer parameters */ type = TYPEA; /* ascii */ mode = MODES; /* stream */ stru = STRUF; /* file structured */ /* init NOLOGCOM */ NOLOGCOMM = &commands[LASTNOLOGCOM]; /* say we're listening */ netreply ("220 %s Experimental Server FTP\r\n", argv[2]); /* */ nextcomm: while (i = getline ()) { if ((int) i < 1) /* handle error */ go_die ("net input read error;"); /* find and call command procedure */ comp = commands; while (comp -> cmdname) /* while there are names */ { if (!strcmp (buf, comp -> cmdname))/* a winner */ { if (comp <= NOLOGCOMM || cmdstate == EXPECTCMD) (*comp -> cmdfunc) (); /* call comm proc */ else { if (cmdstate == EXPECTPASS) { netreply ("530 Give me your password, chucko\r\n"); } else { netreply ("530 Log in with USER and PASS\r\n"); } } goto nextcomm; /* back for more */ } comp++; /* bump to next candidate */ } fprintf (ERRFD, "Bad command: %s", buf); netreply ("500 I never heard of that command before\r\n"); } mail_reset (); /* flush any xrcp/xrsq stored text */ } /*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 for EOF -1 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 changed no more line condition from "read (..net..) < 0" to "read (..net..) <= 0" 5Oct78 S.Y. Chiu change error handline jsq BBN 20Aug79 */ char * 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 */ extern char *progname; inp = netptr; outp = buf; arg = 0; cmdflag = 0; do { if (--netcount <= 0) { do { if ((netcount = net_read(&NetParams, netbuf, sizeof netbuf)) <= 0) { if (errno == ENETSTAT) { get_stuff(&NetParams); if (NetParams.ns.n_state&(URXTIMO+UURGENT)==0) return((char *)netcount); } else return(-1); } } while (netcount < 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 == TNIAC) /* 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 = NUL; /* make null termed */ arg = outp + 1; /* set arg ptr */ } else /* do case mapping (UPPER -> lower) */ if (isupper(c)) c = tolower(c); } *outp++ = c; } while (c != '\n' && outp < &buf[BUFL]); if (just_con_data == 0) *--outp = 0; /* null term the last token */ /* scan off blanks in argument */ if (arg) { while (*arg == ' ') arg++; if (*arg == '\0') arg = 0; /* if all blanks, no argument */ } netptr = inp; /* reset netptr for next trip in */ return (outp); /* return success */ } /*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: spawn open (sys) dataconnection senddata called by: main thru command array history: initial coding 4/12/76 by S. F. Holmgren fork changed to fork1 by greep and to frog jsq BBN 18Aug79 */ retrieve() { FILE *localdata; /* file id of local data file */ struct net_stuff netdata;/* file id of network data file */ if (chkguest (0) == 0) return; if ((lastpid = frog ()) == 0) { /* child comes here */ if ((localdata = fopen (arg, "r")) == NULL) /* open file for read */ { netreply ("%s can't open \"%s\"; %s\r\n", (errno == EACCES ? "450" : (errno == ENOENT ? "550" : "451" ) ), arg, errmsg (0) ); exit (-1); } /* Open data connection */ dataconnection (U5, &netdata); /* say transfer started ok */ netreply ("150 Retrieval of \"%s\" started okay\r\n", arg); /* send data according to params */ senddata (localdata, &netdata, netreply); net_pclose(&netdata); /* say transfer completed ok */ netreply ("226 File transfer completed okay\r\n"); exit (0); } } #ifdef TREE /*name: xmkd function: make a subdirectory of the current working directory algorithm: if the file exists if it is a directory, change to it via cwd() else complain else do a fork fork execl's the standard mkdir cwd() to change to the directory else complain that you can't do it parameters: local directory name to open (arg) returns: nothing globals: arg calls: execl (sys) stat (sys) called by: main thru command array history: Fixed to properly check if dir by Dan Franklin (BBN) May 25, 1980 */ mkd() { struct stat statbuf; int ps; char path[QUOTDSIZ]; register i; ps = 0; if (chkguest (1) == 0) return; /* check if the directory exists */ if (stat (arg, &statbuf) >= 0) { if ((statbuf.st_mode & S_IFMT) == S_IFREG) { /* directory exists */ abspath (arg, path, &path[QUOTDSIZ]); netreply ( "251 \"%s\" directory already exists; taking no action\r\n", syntactify (path) ); return; } else { netreply ("450 file name \"%s\" exists as non-directory\r\n", arg); return; } } /* */ /* create new directory */ if ((i = fork ()) == 0) { /* don't let any error messages clobber the user program */ close (1); close (0); execlp (MKDIR, "mkdir", arg, 0); log_error ("(mkd) can't execl \"%s\"", MKDIR); exit (-1); /* execl failed */ } if (i > 0) { while (i != wait (&ps)); if (ps >> 8) { netreply ("\r\n451 unable to create directory\r\n", arg); return; } abspath (arg, path, &path[QUOTDSIZ]); netreply ("\r\n251 \"%s\" directory created\r\n", syntactify (path)); return; } else { netreply ("\r\n451-unable to fork to make directory"); netreply ("\"%s\"; %s\r\n451 try again later\r\n", arg, errmsg (0)); } } /* */ char * syntactify (str) /* if str has a " in it, double the " */ char * str; { /* NOTE: returns pointer to a static character array */ static char quoted[QUOTDSIZ]; register char *p, *d; p = str; d = quoted; do { if (*p == '"') *d++ = '"'; *d++ = *p++; } while (*p && d < &(quoted[QUOTDSIZ])); *d = '\0'; return (quoted); } /*name: rmdir function: removes a directory algorithm fork a process which execls the systems rmdir parameter just arg returns: nothing globals: arg calls: nothing called by main history: initial coding by dm@bbn-unix Mar 27, 1980 */ rmdir() { int child, ps; ps = 0; if ((child = fork ()) < 0) { netreply ( "451 can't fork process to remove \"%s\"; %s\r\n", arg, errmsg (0) ); log_error ("(rmdir) fork error, removing \"%s\"", arg); } else if (child == 0) { /* don't let any error messages clobber the user program */ close (0); close (1); execlp (RMDIR, "rmdir", arg, 0); netreply ( "\r\n451 can't execl \"%s %s\"; %s\r\n", RMDIR, arg, errmsg (0) ); log_error ("(rmdir) can't execl \"%s %s\"", RMDIR, arg); exit (-1); } else { while (child != wait (&ps)); if (ps >> 8) { netreply ("\r\n451 could not remove \"%s\"\r\n", arg); } else netreply ("\r\n250 directory removed\r\n"); } } /*name: xpwd function: print working directory algorithm: do an abspath of "." parameters: none returns: none globals: none calls: abspath syntactify netreply called by: main through command array history: initial coding by dm (bbn) 18 November, 1980 */ xpwd() { char path[QUOTDSIZ]; abspath (".", path, &path[QUOTDSIZ]); netreply ("251 \"%s\" is the current working directory\r\n", syntactify (path)); } /*name: xcup function: change to parent of current working directory algorithm: point 'arg' to the string ".." and call cwd() parameters: none returns: nothing globals: arg calls: cwd called by: main thru command array history: initial coding by dm (BBN/sandy ego) 19 Oct. 1979 */ xcup() { arg = ".."; cwd(); } /* tha's all, folks! */ #endif TREE /*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: spawn creat (sys) dataconnection rcvdata called by: main thru command array history: initial coding 4/12/76 by S. F. Holmgren fork changed to fork1 by greep changed by greep to check success from rcvdata default file modes corrected jsq BBN 7-19-79 fork1 to frog jsq BBN 18Aug79 */ store() { FILE *localdata; /* file id for local data file */ struct net_stuff netdata;/* file id for network data file */ if (chkguest (1) == 0) return; if ((lastpid = frog()) == 0) { /* create new file */ if ((localdata = fopen (arg, "w")) == NULL) { netreply ("451 can't create \"%s\"; %s\r\n", arg, errmsg (0)); exit (-1); } /* open data connection */ dataconnection (U4, &netdata); /* say transfer started ok */ netreply ("125 Storing \"%s\" started okay\r\n", arg); /* receive and translate data according to params */ if (rcvdata (&netdata, localdata, netreply) >= 0) netreply ("226 File transfer completed okay\r\n"); 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: spawn open (sys) creat (sys) seek (sys) dataconnection rcvdata called by: main thru command array history: initial coding 4/12/76 by S. F. Holmgren fork changed to fork1 by greep changed by greep to check success of rcvdata use fmodes for default creation mode jsq BBN 7-19-79 fork1 to frog jsq BBN 18Aug79 */ append() { FILE *localdata; /* file id of local data file */ struct net_stuff netdata;/* file id of network data file */ if ((lastpid = frog ()) == 0) return; if ((lastpid = frog ()) == 0) { /* try and open file -- it may exist */ if ((localdata = fopen (arg, "a")) == NULL) { netreply ( "451 can't create \"%s\"; %s\r\n", arg, errmsg (0) ); exit (-1); } /* open data connection */ dataconnection (U4, &netdata); netreply ("125 Append to \"%s\" started correctly\r\n", arg); /* rcv and translate according to params */ if (rcvdata (&netdata, localdata, netreply) >= 0) netreply ("250 Append completed okay\r\n"); exit (0); } } /*name: user function: handle USER command algorithm: max login tries exceeded signal error exit expecting user command signal error reset state return save user name request password parameters: none returns: nothing globals: cmdstate= logtries= username= arg calls: netreply strcpy called by: main thru command array history: initial coding 4/12/76 by S. F. Holmgren */ user() { if (cmdstate != EXPECTUSER) { netreply ("504 User command unnecessary\r\n"); return; } if (logtries++ > MAXLOGTRIES) { netreply ("430 Login attempts exceeded, go away\r\n"); exit (-1); /* error exit */ } /* set state to expecting a PASS command */ cmdstate = EXPECTPASS; /* save the username in username buf */ strcpy (username, arg); /* ask him for password */ netreply ("331 Enter PASS Command\r\n"); } /*name: pass function: handle the PASS command algorithm: do we know this user signal error set state to EXPECTUSER return do appropriate logging say he is logged in check for first 3 chars of user name "ftp" - if so, guest user parameters: none returns: nothing globals: cmdstate= guest calls: getuser netreply loguser called by: main thru command array history: initial coding 4/12/76 by S. F. Holmgren modified by greep to check for guest user */ pass() { int guestflg; if (cmdstate != EXPECTPASS) { netreply ("503 I am not expecting a password\r\n"); return; } guestflg = (username[0] == 'f' && username[1] == 't' && username[2] == 'p'); /* get the users password entry */ if (getuser (1, pwfile) == 0) { /* unknown user */ netreply ("530 I don't know you, try again\r\n"); cmdstate = EXPECTUSER; /* waiting for user command */ return; } /* got user */ cmdstate = EXPECTCMD; /* do appropriate accounting and system info work */ loguser (); netreply ("230 User Logged in\r\n"); if (guest = guestflg) netreply ("211 FTP guest user - restricted access\r\n"); } /* name: getuser function: getuser: find a match for the user in username and password in arg in /etc/passwd parameters: user name in username password in arg name of file to look in returns: 1 if user found 0 failure pwbuf containing the line from the passwd or alias file which matched history: initial coding 4/12/76 by S. F. Holmgren altered 5 Jan 80 by Dave Mankins to permit UPPERCASED arguments to the PASS command to work modified 25 May 80 by Dan Franklin to use new tpass routine, clean up */ getuser (user_pass, filename) char *filename; { register char *p; /* general use */ register int retval; /* return value: 1 success, 0 failure */ char temp[sizeof (pwbuf)]; char * lowercase(); char * colon(); FILE *iobuf; /* Open the "password" file--if the pointer is to the alias-file, */ /* and you can't get at it, try opening the old one... */ if (((iobuf = fopen (filename, "r")) == NULL) && (filename == afile) && (iobuf = fopen (old_afile, "r")) == NULL) { netreply ("451 can't open \"%s\" (%s); try again later\r\n", filename, errmsg (0)); log_error ("(getuser) can't open \"%s\"", filename); exit (-1); /* fail exit */ } /* Look thru the entries in the password or alias file for a match. */ retval = 0; /* Assume failure. */ /* Get entry from pwfile in pwbuf. */ while (fgets (pwbuf, sizeof pwbuf, iobuf) != NULL) { strncpy (temp, pwbuf, sizeof temp); /* Copy into work area */ *((p = colon (temp))-1) = '\0'; /* Turn colon into null */ if (seq_nocase (username, temp)) /* Found username in pwbuf */ { if (!user_pass) /* Is that all? */ retval++; /* return success */ else { *(colon (p)-1) = '\0'; /* Terminate password field with null */ if (tpass (arg, p, NUL) || tpass (lowercase (arg, arg), p, NULL) ) retval++; } break; } } fclose (iobuf); /* close password file */ return (retval); } /* name: lowercase function: convert a string to lower-case parameters: input and output arrays returns: pointer to output array */ char * lowercase (in, out) register char * in; register char * out; { char * retval; register char c; retval = out; while (c = *in++) *out++ = isupper(c) ? tolower(c) : c; return retval; } /* name: loguser function: perform installation/system specific login/accounting functions parameters: user information in pwbuf returns: nothing history: initial coding 4/12/76 by S. F. Holmgren order of setuid and setgid switched by greep to make them work set homedir jsq BBN 18Dec79 use pwbuf format as set by new getuser, eliminate argument dan BBN 25 May 80 */ loguser() { register char *p; /* general usage */ int *(oldsig()); char * colon(); /* If there is nothing in pwbuf, forget it. */ if (pwbuf[0] == '\0') return; p = colon (colon (pwbuf)); /* Set passentp to userid field. */ curuid = atoi (p); /* Set user id. */ p = colon (p); /* Set passentp to groupid field. */ curgid = atoi (p); /* Set group id. */ p = colon (colon (p)); /* Set passentp to home directory. */ *(colon (p)-1) = '\0'; /* Null terminate. */ chdir (p); /* Set working dir to it. */ /* Also set login directory, if that system call is available. */ /* oldsig = signal (SIGSYS, SIG_IGN); chlogdir (p); signal (12, oldsig); */ setgid (curgid); /* Set group id */ setuid (curuid); /* Set user 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) 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 use fmodes for default creation mode jsq BBN 7-19-79 */ FILE *getmbox() { register FILE *mboxfid; crname (mfname); /* create mailbox file name */ mboxfid = fopen (mfname,"w"); if (mboxfid == NULL) { netreply ("450 Can't create \"%s\"; %s\r\n",mfname,errmsg (0)); log_error ("(getmbox) can't create \"%s\"", mfname); return (NULL); } 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 = (char *) & 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: xsen and xsem function: handle XSEN and XSEM commands xsen: display message on user's tty (s) xsem: same as xsen but if unsuccessful then mail instead algorithm: call display [xsem only] if not successful, call mail parameters: none returns: nothing globals: none calls: display netreply mail called by: main through command array history: written by greep to support ITS hackery */ xsen() { if (display()) netreply ("450 User \"%s\" not logged in\r\n", arg); } xsem() { if (display()) mail(); } /*name: display function: reads data from telnet stream and displays on all terminals on which user is logged in and has terminal writeable algorithm: see whether user is logged in and has tty writeable if so for each such tty copy message to tty parameters: none returns: 0 if successful 1 otherwise globals: arg username ttyname calls: open (sys) close (sys) read (sys) write (sys) stat (sys) getline netreply called by: xsen xsem history: written by greep change error handling jsq BBN 20Aug79 */ char ttyname[] = "/dev/ttyx"; display() { struct { char logname[8]; char logtty; long int logtime; int dummy; } wholine; register char *p; register int i; int ttyfds[100]; /* there can't possibly be this many */ int ttycnt; int whofd; /* */ strcpy (username, arg); /* stick user arg in username */ for (p = username; *p; p++) { if (*p >= 'A' && *p <= 'Z') *p |= ' '; /* convert to lower case if req */ } ttycnt = 0; if ((whofd = open (UTMP, 0)) < 0) { log_error ("(display) can't open \"%s\"", UTMP); return (-1); } while (read (whofd, &wholine, sizeof wholine) == sizeof wholine) { if (wholine.logname[0] == '\0') continue; /* this entry is empty */ for (i = 0; i < 8; i++) if (wholine.logname[i] != username[i]) break; if (username[i] == '\0' && (i == 8 || wholine.logname[i] == ' ')) { ttyname[8] = wholine.logtty; if (ttycnt < 100 && stat (ttyname, &statb) >= 0 && (statb.st_mode & S_WRLD (S_IWRITE)) && (ttyfds[ttycnt] = open (ttyname, 1)) >= 0) ttycnt++; /* It it all works, use it */ } } /* */ close (whofd); if (ttycnt <= 0) return (1); /* Indicate failure */ netreply ("354 Enter text, end by a line with only '.'\r\n"); for (i = 0; i < ttycnt; i++) write (ttyfds[i], "\007Message from Network...\n", 25); just_con_data = 1; /* tell getline only to drop cr */ while (p = getline ()) { if (p <= 0) return (1); /* are we done */ if (buf[0] == '.' && buf[1] == '\n') break; /* yep */ for (i = 0; i < ttycnt; i++) write (ttyfds[i], buf, (p - buf)); } just_con_data = 0; /* set getline to normal operation */ for (i = 0; i < ttycnt; i++) { write (ttyfds[i], "<EOT>\n", 6); close (ttyfds[i]); } netreply ("250 Message displayed successfully\r\n"); return (0); /* Indicate success */ } /*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: strcpy 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 get~mbox modified 10/18/76 by J. S. Kravitz to improve net mail header modified by greep for Rand mail system long host jsq BBN 3-20-79 error handling jsq BBN 20Aug79 modified 4/25/80 by dm (BBN) for xrsq/xrcp support */ mail() { register FILE *mboxfid; /* fid for temporary *mfname file */ register char *p; /* general use */ int tyme[2]; /* for the time */ int werrflg; /* keep track of write errors */ int errflg; /* And other errors (dan) */ int keep; /* flag for rsq stuff */ extern char *errmsg (); keep = 0; errflg = 0; werrflg = 0; mail_reset (); /* unlink any left over temporary files */ if (arg == 0) { /* make sure there is a user name */ keep++; if (xrsqsw == XRSQ_D) { /* unless one is not necessary */ netreply ("502 no recipient named\r\n"); return; } } else { strcpy (username, arg); /* stick user arg in username for getuser */ if (findmbox () == 0) { netreply ("553 User \"%s\" Unknown\r\n", arg); return; } } /* get to open mailbox file descriptor */ if ((mboxfid = getmbox ()) < 0) return; /* say its ok to continue */ netreply ("354 Enter Mail, end by a line with only '.'\r\n"); just_con_data = 1; /* tell getline only to drop cr */ /* */ for (;;) { /* forever */ if ((p = getline ()) < 0) { fprintf (mboxfid, "\n***Sender closed connection***\n"); errflg++; break; } if (p == NULL) { fprintf (mboxfid, "\n***Error on net connection: %s***\n", errmsg (0) ); if (!errflg++) log_error ("(mail) errflg++"); break; } /* are we done */ if (buf[0] == '.' && buf[1] == '\n') break; /* yep */ /* If write error occurs, stop writing but keep reading. */ if (!werrflg) { if (fwrite (buf, (p - buf), 1, mboxfid) < 0) { werrflg++; log_error ("(mail) werrflg++"); } } } just_con_data = 0; /* set getline to normal operation */ /* */ if (errflg) { time (tyme); fprintf (mboxfid, "\n=== Network Mail from host %s on %20.20s ===\n", them, ctime (tyme) ); } #ifdef ADDITIONAL_STUFF time (tyme); fprintf (mboxfid, "\n--- Network mail ended on %20.20s ---\n", ctime (tyme) ); #endif ADDITIONAL-STUFF fclose (mboxfid); if (werrflg) { netreply ("452-Mail trouble (write error to temporary file)\r\n"); netreply ("452 (%s); try again later\r\n", errmsg (0)); mail_reset (); /* delete temporary file */ return; } if (xrsqsw == XRSQ_T && keep) { netreply ("250 Mail stored\r\n"); return; } if (sndmsg (mfname) == -1) /* call sndmsg to deliver mail */ netreply ("451 Mail trouble (sndmsg balks), try later\r\n", errmsg (0)); else netreply ("250 Mail Delivered \r\n"); mail_reset (); /* clean up (delete temporary file, *mfname) */ } /*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: spawn strcpy findmbox netreply 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 changed by greep to check success of rcvdata modified 4/25/80 by dm (BBN) for xrsq/xrcp support */ datamail() { register FILE *mboxfid; int keep; struct net_stuff netdata; keep = 0; if (arg == 0) { keep++; if (xrsqsw == XRSQ_D) { netreply ("501 no recipient specified\r\n"); return; } } else { strcpy (username, arg); /* move user to username */ if (findmbox () == 0) /* valid user */ { netreply ("553 User \"%s\" Unknown\r\n", arg); exit (-1); } } /* */ if ((lastpid = frog ()) == 0) { dataconnection (U4, &netdata); /* try and get an open file descriptor for mailbox file */ if ((mboxfid = getmbox ()) == NULL) { exit (-1); } netreply ("150 Mail transfer started okay\r\n"); /* get data from net connection and copy to mail file */ if (rcvdata (&netdata, mboxfid, netreply) < 0) { fclose (mboxfid); unlink (mfname); exit (-1); } fclose (mboxfid); if (xrsqsw == XRSQ_T && keep) { netreply ("250 Mail stored\r\n"); exit (0); } if (sndmsg (mfname) == -1)/* call sndmsg to deliver */ netreply ("451 Mail system trouble, try again later\r\n"); else netreply ("250 Mail delivered\r\n"); mail_reset (); /* delete temporary files */ exit (0); } else if (!(xrsqsw == XRSQ_T && keep)) *mfname = '\0'; /* parent process does NOT want to know about the temporary file */ /* if its not gonna be rcp'd--but don't unlink it--sndmsg does that */ } /*name: xrsq, xrcp, mail_reset algorithm: obvious function: handle the XRSQ & XRCP protocols history: initial coding by dm (bbn) 25 april, 1980 to handle these protocols */ xrsq() { mail_reset (); /* Always reset stored stuff */ if (arg == 0) { xrsqsw = XRSQ_D; /* Back to default */ netreply ("200 OK, using default scheme (none).\r\n"); } else switch (arg[0]) { case '?': netreply ("215 T Text-first, please.\r\n"); break; case 't': case 'T': xrsqsw = XRSQ_T; netreply ("200 Win!\r\n"); break; case '\0': case ' ': xrsqsw = XRSQ_D;/* Back to default */ netreply ("200 OK, using default scheme (none).\r\n"); break; default: xrsqsw = XRSQ_D; netreply ("501 Scheme not implemented.\r\n"); break; } } /* */ xrcp() { char rcpmbox[MBNAMSIZ]; if (xrsqsw == XRSQ_D) { netreply ("503 No scheme specified yet; use XRSQ.\r\n"); return; } if (*mfname == '\0') { netreply ("503 No stored text.\r\n"); return; } strcpy (username, arg); if (findmbox () == 0) { netreply ("553 User \"%s\" Unknown\r\n", arg); return; } crname (rcpmbox); /* create a temporary name for sndmsg to mung */ if (link (mfname, rcpmbox) < 0) { netreply ( "451 can't link \"%s\" to \"%s\"; %s\r\n", mfname, rcpmbox, errmsg (0) ); log_error ( "(xrcp) can't link \"%s\" to \"%s\"", mfname, rcpmbox ); return; } if (sndmsg (rcpmbox) == -1) { netreply ("451 mail trouble, please try later\r\n"); unlink (rcpmbox); return; } else netreply ("250 Mail delivered\r\n"); } /* */ mail_reset() /* if "xrcp r" ever gets implimented, this will scrub out */ { /* the array of names, when finished, i guess */ /* if there is a temporary file left over from previous xrcp's */ if (*mfname) unlink (mfname); /* remove it-- */ *mfname = '\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: 1 if successful, 0 otherwise history: initial coding 12/15/76 by greep for Rand mail system Modified to use lowercase() by Dan Franklin (BBN) May 26, 1980 */ findmbox() { char *lowercase (); lowercase (username, username); 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 calls: fork (sys) exec (sys) called by: mail datamail history: initial coding 12/15/76 by greep for Rand mail system long hosts, with kludge for short host sndmsg program jsq BBN 3-25-79 sndmsg kludge removed jsq BBN 4-25-79 dm (bbn) 4-25-80 take filename to send as an argument */ sndmsg (file) char *file; { int n; int status; char sndmsgflg[3]; while ((n=fork()) == -1) sleep (5); if (n == 0) { if (DOSNDMSG == 0) { execlp (SNDMSG, "sndmsg", "-netmsg", username, file, 0); log_error ( "(sndmsg) DOSNDMSG == 0, can't execl \"%s\";", SNDMSG ); } else { if (DOSNDMSG == 1) { /* during conversion */ execlp (NSNDMSG, "nsndmsg", "-netmsg", username, file, 0); log_error ( "(sndmsg) DOSNDMSG == 1, can't execl \"%s\";", NSNDMSG ); } sndmsgflg[0]='\001'; /* special flag for old sndmsg */ sndmsgflg[1]='\0'; execlp (SNDMSG,sndmsgflg,file,username,0); log_error ( "(sndmsg) DOSNDMSG irrelevant, can't execl \"%s\";", SNDMSG ); } exit (-1); } wait (&status); return (status>>8); } /*name: do_list, list, nlst function: handle the LIST and NLST commands 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 dataconnection close (sys) dup (sys) execl (sys) called by: main thru command array history: initial coding 4/13/76 by S. F. Holmgren combined NLST & LIST commands via do_list() 5/15/80 dm (BBN) */ list() { do_list (~NLSTFLAG); } nlst() { do_list (NLSTFLAG); } char lstargbf[LSTARGSIZ] = "exec ls "; /* */ do_list (lstflag) int lstflag; { struct net_stuff netdata; register pid, i; int waitwrd; int pype[2]; char pipeline[PIPELEN + 1], /* room for null at pipeline[512] */ crlfied[2 * PIPELEN]; /* room for two pipefulls */ char *ap, *cp; if (chkguest (0) == 0) return; if (pipe (pype) < 0) { netreply ("452 can't make pipe for output; %s\r\n", errmsg (0)); log_error ("(do_list) can't make pipe for shell output"); return; } /* open the data connection */ dataconnection (U5, &netdata); /* say transfer started ok */ netreply ("125 List started ok\r\n"); /* */ if ((pid = fork ()) == 0) { /* child */ /* set up file descriptors for the shell */ dup2 (pype[1],1); if (pype[1] != 1) close(pype[1]); close (0); close (pype[0]); /* Set up shell command line */ for (cp = lstargbf; *cp; cp++)/* Find null */ ; if (lstflag != NLSTFLAG) for ( ap = "-l "; (*cp++ = *ap++) && cp < &lstargbf[LSTARGSIZ]; ); if (arg) for ( ap = arg; (*cp++ = *ap++) && cp < &lstargbf[LSTARGSIZ]; ); /* start up the standard unix 'ls' command */ execlp (SHELL, "sh", "-c", lstargbf, 0); netreply ("451 Can't find shell program!; %s\r\n", errmsg (0)); log_error ("(list) can't find \"%s\"", SHELL); exit (-1); } else { /* parent */ close (pype[1]); while ((i = read (pype[0], pipeline, PIPELEN)) > 0 ) { pipeline[i] = '\0'; i = crlfy (pipeline, crlfied); dat_write (&netdata, crlfied, i);/* this may be a hack */ } while (pid != wait (&waitwrd)); /* say transfer completed ok */ if ((waitwrd >> 8) != -1) netreply ("250 List completed\r\n"); } net_pclose (&netdata); } /* */ dont_list (lstflag) /* doesn't work, unfortunately */ int lstflag; { struct net_stuff netdata; FILE *pipfil; FILE *popen (); int pid, waitwrd; char *ap, *cp; if (chkguest (0) == 0) return; if ((pid = fork()) != 0) { /* parent, wait for child */ while (pid != wait (&waitwrd)); if ((waitwrd >> 8) != -1) netreply ("250 List completed\r\n"); return; } /* Set up shell command line */ for (cp = lstargbf; *cp; cp++)/* Find null */ ; if (lstflag != NLSTFLAG) for ( ap = "-l "; (*cp++ = *ap++) && cp < &lstargbf[LSTARGSIZ]; ); if (arg) for ( ap = arg; (*cp++ = *ap++) && cp < &lstargbf[LSTARGSIZ]; ); /* start up the standard unix 'ls' command */ if ((pipfil = popen (lstargbf, "r")) == NULL) { netreply ("451 Can't start list program!; %s\r\n", errmsg (0)); log_error ("(list) popen failed \"%s\"", lstargbf); exit(0); } /* open the data connection */ dataconnection (U5, &netdata); /* say transfer started ok */ netreply ("125 List started ok\r\n"); /* do the transfer */ senddata (pipfil, &netdata, netreply); /* finish up */ pclose (pipfil); net_pclose (&netdata); exit (-1); } /*name: crlfy() function: to turn newlines into return-newlines, so a shell executing an ls command will obey the FTP protocol algorith: copy the buffer into a static area replacing newlines with crlf-newlines as you do parameters: from - the buffer with text to be crlfied to - where to put the crlfied text returns: count of how many characters in the crlfied text globals: none calls: nothing called by: do_list() history: initial coding by dm (BBN) 29 November, 1980 */ crlfy (from, to) char *from, *to; { register char *fp = from, *tp = to; do { if (*fp == '\n' && *tp != '\r') *tp++ = '\r'; } while (*tp++ = *fp++); return (strlen (to)); } /*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, SIG_NETINT) == -1)) netreply ("211 No transfer in progress\r\n"); else netreply ("211 Transfer in progress\r\n"); } /*name: modecomm function: handle the MODE command algorithm: set the mode variable according to the param if its 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() { char fmode = *arg; /* assign mode */ if (isupper(fmode)) /* convert to lower case */ fmode = tolower(fmode); netreply ( ( (fmode != 's' && fmode != 't' && fmode != 'c') ? "504 Unknown mode (%c)\r\n" :(fmode == 's' ? "200 Stream (%c) mode okay\r\n" : "504 Mode %c not implimented" ) ), fmode ); } /*name: sturcture function: handle the STRU command algorithm: set stru to param if not f or r then say unknown mode else say ok parameters: indirectly thru arg returns: nothing globals: arg stru= calls: nothing called by: main thru command array history: initial coding 4/12/76 by S. F. Holmgren */ structure() { char fstru = *arg; /* assign struct type */ /* see if it is ok */ if (isupper(fstru)) /* convert to lower case */ fstru = tolower(fstru); netreply ( (fstru != 'f' && fstru != 'r') ? "504 Unimplimented structure type\r\n" : ( (fstru == 'f') ? ((stru=STRUF),"200 File Structure okay\r\n") : ((stru=STRUR),"200 Record Structure okay\r\n") ) ); } /*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 modified so that it will take 'i' or 'l' of either case 10/12/78 S.Y. Chiu */ typecomm() { register char xtype = *arg; if (isupper(xtype)) /* convert to lower case */ xtype = tolower(xtype); if (xtype == 'l' && (*++arg != '8')) netreply ("504 Only Type L8 implemented\r\n"); else if (*++arg) netreply ("501 Syntax error.\r\n"); else { netreply ((xtype != 'a' && xtype != 'i' && xtype != 'l') ? "504 Unimplemented Type\r\n" : ((xtype == 'a') ? "200 Ascii transfers okay\r\n" : "200 Image and Local transfers okay\r\n" ) ); type = (xtype == 'a' ? TYPEA : (xtype == 'l' || xtype == 'i' ? TYPEI : type ) ); } } /*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 (chkguest (1) == 0) return; if (unlink (arg) == -1) { netreply ( (errno == 2 ? "550 Can't find \"%s\"\r\n" : "550 Can't unlink \"%s\"\r\n" ), arg ); return; } netreply ("250 \"%s\" deleted\r\n", arg); } /*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) 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 (chkguest (0) == 0) return; if (chdir (arg) < 0) { netreply ( "%s Can't change to \"%s\"; %s\r\n", (errno == ENOENT ? "550" : (errno == EACCES ? "551" : "551" ) ), arg, errmsg (0) ); } else netreply ("250 Working directory changed to %s\r\n", arg); } /*name: renme_from function: Handle the RNFR command algorithm: save the 'source' file name in buf set cmdstate to expecting a RFTO command say command completed parameters: indirectly thru arg returns: nothing globals: arg renmebuf= rcvdrnfr= calls: strcpy netreply called by: main thru command array history: initial coding 4/13/76 by S. F. Holmgren */ renme_from() { if (chkguest (1) == 0) return; strcpy (renmebuf, arg); /* save the file name in buf */ rcvdrnfr++; /* say we got a RNFR */ netreply ("350 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) 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 FILE *destfid; register FILE *srcfid; register cnt; char cpybuf[512]; if (chkguest (1) == 0) return; if (rcvdrnfr == 0) { netreply ("503 Haven't received a RNFR\r\n"); return; } rcvdrnfr = 0; /* clear rename to flag */ /* if cant do stat or file is not standard data file */ if ((stat (renmebuf, &statb) == -1) || ((statb.st_mode & S_IFBLK) != 0)) { netreply ("550 Can't find \"%s\"; %s\r\n", renmebuf, errmsg (0)); return; } /* try and create the new file */ if ((destfid = fopen (arg,"w")) == NULL) { /* can't create say error and return */ netreply ( "%s Can't create \"%s\"; %s\r\n", (errno == ENOENT ? "550" : (errno == EACCES ? "550" : "551" ) ), arg, errmsg (0) ); return; } /* */ /* try and open the source file */ if ((srcfid = fopen (renmebuf, "r")) == NULL) { fclose (destfid); netreply ( "%s Can't open \"%s\"; %s\r\n", (errno == ENOENT ? "550" : (errno == EACCES ? "550" : "551" ) ), renmebuf, errmsg (0) ); return; } /* while there is data in source copy to dest */ while ((cnt = fread (cpybuf, 1, sizeof cpybuf, srcfid)) > 0) if (fwrite (cpybuf, 1, cnt, destfid) < cnt) { netreply ( "%s Warning: Unix write error (%s)- aborting\r\n", (errno == ENOENT ? "550" : (errno == EACCES ? "550" : (errno == ENOSPC ? "552" : "551" ) ) ), errmsg (0) ); log_error ( "(renme_to) error copying \"%s\" to \"%s\"", renmebuf, arg ); goto bad; } /* */ /* remove link to source file */ unlink (renmebuf); /* say all went well */ netreply ("250 Rename of \"%s\" to \"%s\" completed\r\n", renmebuf, arg); bad: close (srcfid); /* close source file */ fclose (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() { mail_reset (); /* flush mail temporary files */ netreply ("221 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 */ cmd_abort() { netreply ( ((lastpid == 0) || (kill (lastpid, 9) == -1)) ? "225 Nothing to abort\r\n" : "226 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 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() { netreply ("200 OK\r\n"); } /*name: help function: give a list of valid commands algorithm: send list parameters: none returns: nothing globals: cmdstate calls: netreply called by: called by main thru the help command history: greep altered by dm (12/3/79) to permit automatic addition of commands (someone industrious should someday make help accept an argument, like the protocol says it does...) */ help() { register i; register struct comarr *p; netreply ("214-The following commands are accepted:\r\n"); for (p = commands, i = 1; p <= NOLOGCOMM; p++, i++) netreply ("%s%s", p -> cmdname, ((i % 10) ? " " : "\r\n")); if (cmdstate != EXPECTCMD) { /* is he logged in? */ netreply ("\r\nThe following require that you be logged in:\r\n"); i = 1; /* for the for statement, following... */ } for (; p -> cmdname; p++, i++) netreply ("%s%s", p -> cmdname, ((i % 10) ? " " : "\r\n")); netreply ("\r\n214 Please send complaints/bugs to %s\r\n", BUGS); } /* write error messages out to the log file */ /* VARARGS */ log_error (printargs) char *printargs; { fprintf (ERRFD, "%r", &printargs); fprintf (ERRFD, "; %s\n", errmsg (0)); } /* clean up mail stuff (i.e., delete any temporary files) and call die */ go_die (arg) char *arg; { mail_reset (); die (-1, "%s %s %s %s", arg, progname, them, errmsg (0)); } /*name: chkguest function: check whether operation is valid for guest user algorithm: for the time being: if optype is 1, no go if optype is 0, ok if path name does not begin with / or .. parameters: optype - 0 if retr or [x]cwd, 1 if stor, appe, dele, rnfr, rnto returns: 1 if ok, else 0 globals: filename in arg calls: write (sys) called by: retr, cwd, stor, appe, dele, rnfr, rnto routines history: initial coding by greep */ chkguest (optype) int optype; { if (guest && (optype != 0 || arg[0] == '/' || chkacc ())) { netreply ("530 Access not allowed for guest users\r\n"); return (0); } return (1); } chkacc() { register char *p; register int sep; /* Indicates new component */ sep = 1; /* Initially indicate it is */ for (p = arg; *p; p++) { if (sep && p[0] == '.' && p[1] == '.') return (1); sep = (p[0] == '/'); } 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 */ char * colon (p) char *p; { extern char * sfind(); return sfind (p,":\n") + 1; } frog() { register i; extern int par_uid; i = spawn (); if (i) dieinit (i, ERRFD); else dieinit (par_uid, ERRFD); return (i); }