BBN-Vax-TCP/src/ftp/ftp.c

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

#include "ftp.h"
#include "usr.h"
#include "ftptelnet.h"
#include "ctype.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/timeb.h"
#include "sgtty.h"
#include "setjmp.h"
#include "ftp_lib.h"

/*
 * User FTP
 *
 * Modified by Dan Franklin Sept 8 1978 (BBN) to take
 * commands from "getline" routine. This enables it
 * to have its input redirected to a file of commands.
 * Also moved declarations of strings for command prompting
 * to ahead of the command table so that our new C compiler
 * won't complain.
 *
 * Modified by Dan Franklin (BBN) August 11 19598 to put
 * blank line after "From:" line in send-mail command.
 * The blank line indicates the end of the header as
 * per RFC733.
 *
 * 3/79, added cwd and quote commands.	delete inside rwait returns
 * to prompt instead of saying (incorrectly) "Host exited"  ado/bbn
 *
 * changed to use long hosts jsq BBN 3-25-79
 * use fmodes for creat mode, give error on NCP open fail jsq BBN 19July79
 * init and subshl changed to handle signals differently:  if parent
 * shell ignored signals, this process does too.  jsq BBN 5Aug79
 * add arguments to allow passing process id of other half so that one
 *	can kill the other when it is ready to die; also do all host
 *	name-number conversions in ftp.c so we don't have so much stuff
 *	in here.  jsq BBN 18Aug79
 *
 * made f_retr check if destination already existed, and not delete on error
 * if it does. This should eliminate one cause of the /dev/tty disappearances.
 * Also fixed signal handling in fork exec'ing shell. Dan Franklin (BBN) 12/21/79
 *
 * Added NLST command (like LIST but different at server end). Also changed
 * CMDENTS to use sizeof cmdtab.
 *
 * Added XSEN and XSEM commands, which are just like MAIL (to user FTP) but
 * send messages instead of/along with mail on servers which implement
 * them. Dan Franklin BBN Feb 8 '80
 *
 * Added SEND and GET synonyms for STOR and RETR respectively.  Added
 * LOGIN command with 1, 2 or 3 args, and new argument list types
 * ARG1AND2 and ARG1OR2OR3.  Buz Owen, BBN, March 4 '80.
 *
 * Fixed bug whereby LIST and NLST were synonyms of RETR.  Changed QUOTE
 * command to take two args.  Changed ARGSIZ to 80, and LINSIZ to 250.
 * Changed quote and cwd to use putarg, putstr, putcmd more conventionally.
 * Buz Owen, BBN, March 11, 80.
 *
 * ca. Mar 25, 1980 (dm at BBN):
 *   changed issep (c) to return TRUE if (c == '\0'); this is so movarg
 *     can act on things its already acted on, so it could be used in the
 *     multiple store stuff
 *   added the multiple store stuff to server and user processes, working
 *     on unix-unix tree transfers
 *   in chekds(), which opens the data connection, turned off user-interrupts
 *     while waiting for the connection to be openned.  at least with tenex,
 *     failure to open this connection after you've said you were going to can
 *     get the server confused, and it stops listening to you (probably
 *     waiting for the connection to open).
 *   changed f_stor() to act as a front end for a separate subroutine, store()
 *     which does the actual work;  store() is called by the multiple store
 *     stuff.
 *
 * jsq BBN 5April80 declared cmdtab as struct ftucmd cmdtab[] in initiali-
 *   zation, fixed definition of ftucmd.ft_info, declared getcmd properly in
 *   main before calling it, fixed getcmd to use ft_info correctly.
 *
 * dm (bbn) 15 apr 1980:
 *   installed mretrieve() command; gets an NLST from the other site, and then
 *     does a retrieve on all the names of files returned.
 *     has subcommands to translate tenex-like file-names into unixable file-
 *     names, and work other transformations
 *   changed error() to accept fprintf-like arguments, so i could improve
 *     error messages it returns
 *   added the statistics package
 *     note that statistics aren't gathered on recursive stores (done in
 *     a lower fork.
 *   moved all the MGET internals to the end of the file, so they would be
 *     easier to find
 * dm (bbn) 31 May 1980:
 *   put a 1-second sleep into the loop in sendabunch() tenex requires time
 *     to become convinced the last data-socket was closed, so, cycling too
 *     quickly through several files will result in a "cannot open data
 *     connection" message, and Unix will be sitting there trying to open
 *     the data socket, anyway...
 *   changed rwait() to know about the proposed 257 reply code which tells
 *     you what the directory was which was created.  Also changed ftptty
 *     to know about such things.
 *   put the interrupt stuff back into checkds()--its abscence caused too
 *     many problems, and i guess people can restart their tenex ftps if
 *     tenex gets confused...
 * jsq BBN 27May80 make prstat give baud rate as well as bytes/sec.
 * dm (bbn) 5 Sept. 80: make sure indat() & outdat() close the dataconnection
 *     when the file is through.
 * dm (bbn) 25 Nov. 80: make a version which works with both TCP & NCP
 *      changed setspec() & calling routines to handle the length of the
 *       tables automatically (i.e., recognize that a null entry implies
 *       the end of a table)
 *      changed f_mlfl() to use putcmd() & sndcmd() instead of writing
 *       directly to the network
 *      dsfds & dsfdr are structures now
 *
 * ado (bbn) feb - jun, 81.  finished tcp version, v7ized everything and 
 *	stdio'd everything possible,
 *	following agn's changes to c70 version, and further urging.
 *	combined ftp (the command), ftpmain, and ftptty into one program.
 *	stripped out data transfer routines in favor of ftp_lib routines which
 *	are common to both user and server ftp, supplied by agn.
 *	completely new version rwait to deal with systematic reply code scheme
 *	of new protocol.
 * ado (bbn) jul 81.  added cmdget and cmdsend commands, to retrieve into a
 *	a filter, and send output from a command  respectively.
 *  */
#define MGET    1       /* turn on the multiple store junk */

struct net_stuff NetParams;

extern char * progname;
char * malloc(), * strcpy();

char * HOST_ID, * them, * us;
char * errmsg();
char * getarg();
char * desyntactify();

#define ARGSIZ  80
#define TTBSIZ  160
#define MAXSTRING  10           /* Length of the longest command name */

struct spctab
    {
	char *typnm;
	char *typarg;
	int typnum;
    };
    
struct spctab tycode[] =
    {
	{ "ASCII", "A", 0 },
	{ "TELNET", NULL, 1 },		/* non implimented values in */
	{ "PRINT", NULL, 2 },		/* anyway, so we can give */
	{ "EBCDIC", NULL, 3 },		/* slightly more intelligent */
	{ "IMAGE", "I", 6 },
	{ "LOCAL", "L8", 7 },
	{  NULL, NULL, -1 }
    };

struct spctab mdcode[] =
    {
	{ "STREAM", "S",  0 },
	{ "BLOCK", NULL, -1 },			/* error messages. */
	{ "COMPRESSED", NULL, -1 },		/* not supported... */
	{  NULL, NULL, -1 },
    };

struct spctab strucode[] =
    {
	{ "FILE", "F", 0 },
	{ "RECORD", "R", 1 },
	{ NULL, NULL, -1 },
    };
/*  */
/* Initial [default] settings: */

int mode = MODES;
int type = TYPEA;
int stru = STRUF;

struct net_stuff dsfds;
struct net_stuff dsfdr;
FILE *fdfil = NULL;
FILE *fdpip = NULL;
int pipnum = -1;

/* Uninitialized data */

int abrtxfer;
char ttbuf[TTBSIZ], *ttptr, ttcnt;              /* For tty input */
char arg1[ARGSIZ], arg2[ARGSIZ], arg3[ARGSIZ];  /* For string arguments */
char linbuf[LINSIZ];
char *linptr;		/* For Telnet output */

jmp_buf env;					/* for long jumping */
struct stat sttbf;                              /* For file status info */
int uicount =   0;                              /* No. of user interrupts */
int xreply;                                     /* Global reply location */

char mlbf[82], *mlptr;                  /* For mail headers */

struct ftucmd				/* Structure of the command table */
    {
	char  *ft_cnm;
	int   ft_nargs;
	char  **ft_info;
	int   (*ft_fn)();
	char  *ft_desc;
    };

/* Definitions for ft_nargs field */
#define ARG0    1
#define ARG1    2
#define ARG2    4
#define ARG0OR1 010
#define ARG1OR2 020
#define ARG1AND2 040
#define ARG1OR2OR3 100
/*  */
/* Strings for command prompting */

char locfil[] =  "localfile:  ";
char rmtfil[] =  "remotefile: ";
char persn[] = "person: ";

char *afsnd[] = { "Input filter (command):", rmtfil };
char *afget[] = { locfil, "Output filter (command): " };
char badcmd[] = "bad filter/command: %s\n";
FILE *popen();

char aborstr[] = { TNIAC, TNIP, TNIAC, TNDM, 'A', 'B', 'O', 'R', 0 };

char *arsto[] = { locfil,
		  rmtfil };

char *arcwd[] = { "remote pathname: " };
char *ardir[] = { "local pathname: "};
char *ardel[] = { rmtfil };
char *arlis[] = { "remote pathname: ",
		  locfil };

char *armod[] = { "transfer mode: "};
char *arren[] = { "old remotefile: ",
		  "new remotefile: " };

char *arret[] = { rmtfil, locfil };

char *arate[] = { "[reset] " };
char *arper[] = { persn };
char *arquo[] = { "command [argument]: " };
char *arstr[] = { "file structure: " };
char *artyp[] = { "data type: " };
char *aruse[] = { "Username: " };
char *armlf[] = { locfil, persn };

#ifdef MGET
char *armret[] = { "foreign file-group descriptor: ",
		 "flags: " };

char *armsto[] = {locfil,
		"remote directory-name, or <cr> if wild-carding: " };
/*  */
/* some other multiple send and store guys */
char recurstor;
char dialog;
char mretname[32];              /* storage for the NLST temporary file */
#define MADEDIR 251     /* reply code for XMKD protocol */

char MadeDirectory[ARGSIZ];     /* storage for the string returned by a */
				/* XMKD call */

/* variables for the file-name transformer */
#define FNARGSIZ        ARGSIZ/2
#define FNTENEX         01              /* flag tenex-mode transforms  */
#define FNITS           02              /* flag its mode transforms    */
#define FNTOLCASE       04              /* flag lower-case transforms  */
#define FNALPHA         010             /* flag alphanumerics only     */
#define FNASK           020             /* flag ask user for each name */
#define FNCPY           040             /* flag use the name literally */
#define CTLV            026             /* tenex quotes things with ^V */
int     fnflag  =       0;              /* where the flags live        */

char    fnprefix[FNARGSIZ/2];           /* potential prefix            */

/* declarations of the dispatch functions, so the compiler doesn't complain */
int f_msto(), f_mkd(), f_rmdir();

#endif MGET
int subshl(), help(), f_acct(), f_appe(), byedie(), f_cld(), f_cwd();
int f_dele(), f_list(), f_log(), f_mail(), f_mlfl(), f_mode(), f_pass();
int f_quot(), f_rena(), f_status(), f_stor(), f_stru(), f_type(), f_user();
int f_retr(), f_rate(), f_err(), f_nlst(), f_mret(), f_fsnd(), f_fget();
int abort();

/*  */
struct ftucmd cmdtab[] =	/* Command Table -- must be alphabetical */
{
  {     "!   ",         ARG0,           NULL,   subshl,
        "invoke shell"                             },
  {	"ABORT",	0,		NULL,	abort,
  	   "debugging aid: take a core dump."},
  {     "ACCOUNT",      ARG0OR1,        NULL,   f_acct,
          "specify account on foreign host"          },
  {     "ACCT",         ARG0OR1,        NULL,   f_acct,
          "specify account on foreign host"          },
  {     "APPEND",       ARG2,           arsto,  f_appe,
          "append local file to foreign file"        },
  {     "BYE ",         ARG0,           NULL,   byedie,
          "close the connection, and exit"           },
  {     "CD  ",         ARG1,           ardir,  f_cld,
           "change directory on local host"           },
  {     "CHDIR",        ARG1,           ardir,  f_cld,
           "change directory on local host"           },
  {     "CMDGET",         ARG2,           afget,  f_fget,
	"retrieve file from foreign host into filter (command)",     },
  {     "CMDSEND",        ARG2,           afsnd,  f_fsnd,
	"send command output to foreign host file",     },
  {     "CWD ",         ARG1,           arcwd,  f_cwd,
           "change directory on foreign host"         },
  {     "DELETE",       ARG1,           ardel,  f_dele,
          "remove a file from the foreign host"      },
  {     "GET ",         ARG2,           arret,  f_retr,
          "retrieve a file from the foreign host"    },
  {     "HELP",         ARG0OR1,        NULL,   help,
            "briefly describe commands"                },
  {     "LIST",         ARG2,           arlis,  f_list,
          "get a directory listing to a local file"  },
  {     "LOG ",         ARG1OR2OR3,     aruse,  f_log,
           "log onto a foreign host"                  },
  {     "MAIL",         ARG1,		arper,   f_mail,
          "send mail to a user on the foreign host"  },
#ifdef  MGET
  {     "MGET",         ARG1OR2,        armret, f_mret,
          "multiple retrieve"                        },
  {     "MKDIR",        ARG1,           arcwd,  f_mkd,
           "make a directory on the foreign host"     },
#endif  MGET
  {     "MLFL",         ARG1AND2,       armlf,  f_mlfl,
          "mail file to a foreign host"              },
  {     "MODE",         ARG1,           armod,  f_mode,
          "specify transfer mode (STREAM or BLOCK)"   },
#ifdef  MGET
  {     "MRETRIEVE",    ARG1OR2,        armret, f_mret,
          "multiple retrieve"                        },
  {     "MSEND",        ARG1OR2,        armsto, f_msto,
          "multiple store"                           },
  {     "MSTORE",       ARG1OR2,        armsto, f_msto,
          "multiple store"                           },
#endif  MGET
  {     "NLST",         ARG2,           arlis,  f_nlst,
          "get a directory listing to a local file"  },
  {     "PASSWORD",     ARG0OR1,        NULL,   f_pass,
          "tell the foreign host your password"      },
  {     "QUIT",         ARG0,           NULL,   byedie,
          "close the connection, and exit"           },
  {     "QUOTE",        ARG1OR2,        arquo,  f_quot,
          "send quoted string to ftp server"		},
  {	"RATE",		ARG0OR1,	arate,	f_rate,
	  "print accumulated transfer rate, and optionally reset."},
  {     "RENAME",       ARG2,           arren,  f_rena,
          "rename a file on the foreign host"        },
  {     "RETRIEVE",     ARG2,           arret,  f_retr,
          "retrieve a file from the foreign host"    },
#ifdef  MGET
  {     "RMDIR",        ARG1,           ardir,  f_rmdir,
         "remove a directory on the foreign host"   },
#endif  MGET
  {     "SEND",         ARG2,           arsto,  f_stor,
          "store a local file on the foreign host"   },
  {     "STATUS",       ARG0OR1,        NULL,   f_status,
        "tell status of the ftp connection"        },
  {     "STORE",        ARG2,           arsto,  f_stor,
          "store a local file on the foreign host"   },
  {     "STRUCTURE",    ARG1,           arstr,  f_stru,
          "specify structure of files (RECORD or FILE (unstructured))"},
  {     "TYPE",         ARG1,           artyp,  f_type,
          "specify representation type (ASCII, IMAGE, or LOCAL)"},
  {     "USER",         ARG1,           aruse,  f_user,
          "tell the foreign host your name"          },
  {     MSOM,           ARG0OR1,        arper,   f_mail,
          "send text to a user's tty on the foreign host"},
  {     MSND,           ARG0OR1,        NULL,   f_mail,
          "send text to a user's tty on the foreign host"},
  {     NULL,           0,              NULL,   f_err,
           "command processor error"                  }
};
/*  */
char *errtab[] = {
/* 0 */ 	"Unrecognized command\n",
/* 1 */ 	"Ambiguous command\n",
/* 2 */ 	"Command error\n",
/* 3 */         "Wrong number of arguments\n",
/* 4 */         "Command argument too long\n",
/* 5 */         "Bad specification\n",
/* 6 */         "Can't create file\n",
/* 7 */         "File not found\n"
};
/*  */
ftpmain (otherhalf)
int otherhalf;
{
    register struct ftucmd *sp;
    extern struct ftucmd *getcmd();

	NetInit (&dsfdr);
	NetInit (&dsfds);
	init (otherhalf);

	if (!setjmp (env))
	    rwait (1);			/* wait for 200 hello */

	for (;;)
	{
	    if (uicount) abterr();       /* Returns to setjmp() */
		prompt();
		sp = getcmd();
		putcmd (sp->ft_cnm);
		(*sp->ft_fn)();
	}
}

prompt()
{
	printf ("> ");
}
/*  */
init (otherhalf)
int otherhalf;
{   /* signal handling changed jsq BBN 5Aug79 */
    extern exit(), usrint(), dieinit(), diequit();

	dieinit (otherhalf, stderr);  /* second arg is fd for fprintf */
	get_stuff (&NetParams);
	
	if (signal (SIGINT, usrint) == SIG_IGN)
		signal (SIGINT, SIG_IGN);/* if parent shell */
	if (signal (SIGQUIT, diequit) == SIG_IGN)
	       signal (SIGQUIT, SIG_IGN);/* ignored, we do too */
	signal (SIG_NETINT, SIG_IGN);

	inittime();
	ttcnt = 0;
	ttptr = ttbuf;
#ifdef  MGET
	*mretname = 0;
#endif  MGET
}
/*  */
logfn(s)			/* interfaces to ftp_lib, skips over reply
				   code.  */
char * s;
{
    printf (s+4);
}

usrint()        /* Collect user interrupts */
{
	uicount++;
	abrtxfer++;		/* stop any transfer in progress */
	signal (SIGINT, usrint);
}

f_err()
{
    puts ("ftpmain: command parser ERROR; dispatch on 0 command pointer");
    puts ("please report this to BUG-FTP @ BBN-Unix");
}
/*  */
help()
{
 register struct ftucmd *sp;
 register i,j;
 char flag;

 flag = i = j = 0;
 if (*arg1==0)
 {
	printf ("\nCommands known to this process:\n\n");
	for (sp = cmdtab; (sp->ft_cnm); sp++)
	{
	       printf ("%-*.*s", MAXSTRING, MAXSTRING, (sp->ft_cnm));
	       if (!(++j%6))printf ("\n");
	}
	printf ("\nany unambiguous substring will invoke the given command\n");
	printf ("for a brief description of a given command type \"help <command>\"\n");
	printf ("for a brief description of all commands type \"help all\", or \"help * \"\n");
	printf ("\"help server\" will ask the server process for help\n");
 }
 else {
	getuc (arg1);
	if (match (arg1, "SERVER"))
	{
		putcmd ("HELP");
		sndcmd();
		rwait (12);
		return;
	}
	else if (match (arg1, "ALL")||match (arg1, "*")) flag++;

	for (sp = cmdtab; (sp->ft_cnm); sp++)
	{
		if (flag||match (arg1, sp->ft_cnm))
		{
			printf ("%s\t %s\n", sp->ft_cnm, sp->ft_desc);
			i++;
		}
	}
	if (!i) printf ("command \"%s\" unrecognised\n", arg1);
 }
}
/*  */
f_mode()
{
	setspec ("Mode", &mode, mdcode);
}

f_type()
{
	setspec ("Type", &type, tycode);
}

f_stru()
{
	setspec ("Structure", &stru, strucode);
}

setspec (what, spec, tabl)
char *what; int *spec;
struct spctab tabl[];
{
    register struct spctab *p;

	getuc (arg1);
	for (p=tabl; p->typnm; p++)
	{
	    if (match (arg1,p->typnm)) break;
	}

	if (!p->typarg)
	   error
	   ( (p->typnm
		? "%s %s not implimented.\n"
		: "Bad %s: \"%s\"\n"
	     ), what, arg1
	   );
	else
	{
	    putstr (p->typarg);
	    sndcmd();
	    if (rwait (2) == 0) return;
	    *spec = p->typnum;
	}
}
/*  */
f_status()
{
	putstr (arg1);
	sndcmd();
	rwait (5);
	sleep (5);
}

f_rena()
{
	putcmd ("RNFR");
	putstr (arg1);
	sndcmd();
	if (rwait (2), xreply != 350) return;
	putcmd ("RNTO");
	putstr (arg2);
	sndcmd();
	rwait (6);
}
/*  */
f_cld()
{
	if (chdir (arg1)<0)
		printf ("%s; can't change to local dir %s\n",
		   errmsg (0), arg1);

}

f_cwd()         /* makes this compatible with the MSTO stuff */
{
 cwd (arg1);
}

cwd (str)
char *str;
{
	putcmd ("CWD "); putstr (str);    /* put destination in */
	sndcmd();
	return
	    (
		rwait (3), xreply != 251
		? -1
		: 0
	    );				    /* used by mstore() */
}
/*  */
f_rate()
{
    printf(dumptime());
    if (!strcmp("reset", arg1))
	inittime();
}

f_quot()
{
	putcmd (arg1);
	if (arg2[0]) putstr (arg2);
	sndcmd();
	rwait (-1);
}

f_dele()
{
	putstr (arg1);
	sndcmd();
	rwait (7);
}


f_user()
{
	putstr (arg1);
	sndcmd();
	rwait (1);
}
/*  */
f_log()
{
	putcmd ("USER");
	f_user();
	if (xreply == 331)
	    {
		putcmd ("PASS");
		strcpy (arg1,arg2);
		f_pass();
	    }
	if (xreply == 332)
	    {
		strcpy (arg1, arg3);
		f_acct();
	    }
}
/*  */
f_pass()
{
	secret ("Password: ");
}

f_acct()
{
	putcmd ("ACCT");
	secret ("Account #: ");
}


secret (s)
  char *s;
{
    struct sgttyb tmp;
    register int sav;
    register char *p;

	if (arg1[0] == '\0')
	{
	    ttcnt = 0;
	    gtty (1,&tmp);
	    sav = tmp.sg_flags;
	    tmp.sg_flags &= ~ECHO;
	    stty (1,&tmp);                /* Set noecho for password */
	    printf ("%s",s);
	    fflush(stdout);
	    p = getarg();
	    tmp.sg_flags = sav;
	    stty (1,&tmp);
	    printf ("\n");           /* Echo the absorbed newline */
	    if (p)
	    {
		movarg (p,arg1,ARGSIZ-1);
		getarg();               /* Skip the newline */
	    }
	}
	putstr (arg1);
	sndcmd();
	rwait (1);
}
/*  */
fetch (src,dest)
char    *src, *dest;
{
	int flag;
	int tfd;

/* First find out if the destination file already existed */

	if ((tfd = open (dest, 1)) == -1)
	    flag = 1;	/* Delete on error */
	else
	{
	    close (tfd);
	    flag = 0;	/* Don't delete on error, it already existed */
	}

	if ((fdfil = fopen (dest, "w")) == NULL)
		error ("Can't create \"%s\"; %s\n", dest, errmsg (0));
	errno = 0;
	putstr (src);
	sndcmd();

	if (!chekds (0))
	{
	    rcvdata (&dsfdr, fdfil, logfn);
	    net_close (&dsfdr);
	    fclose (fdfil);
	    fdfil = NULL;
	    return rwait (4);
	}

	fclose (fdfil);
	fdfil = NULL;
	if (flag)
	    unlink (dest);
	net_close (&dsfdr);
	if (errno == EINTR) abterr();
	return 0;
}
/*  */
f_nlst()
{
	nlst (arg1,arg2);
}

nlst (src,dst)                   /* called by f_mret(), f_nlst() */
char *src,*dst;
{
	putcmd ("NLST");
	return fetch (src,dst);
}


f_list()
{
	fetch (arg1,arg2);
}

f_retr()
{
	putcmd ("RETR");         /* because get synonym might be given */
	fetch (arg1,arg2);
}
/*  */
f_fsnd()
{
    if ((fdfil = popen (arg1, "r")) == NULL)
    {
	error (badcmd, arg1);
    }
    else
    {
	errno = 0;
	putcmd ("STOR");
	putstr (arg2);
	sndcmd ();

	    if (chekds (1))
	    {
		pclose (fdfil);
		fdfil = NULL;
		net_close (&dsfds);
		if (errno == EINTR)
		    abterr ();
	    } else
	    {
		    if (senddata (fdfil, &dsfds, logfn))
			net_pclose (&dsfds);
		    else
			net_close (&dsfds);
		    rwait (4);
	    }
	if (fdfil != NULL)
	{
	    pclose (fdfil);
	    fdfil = NULL;
	}
	return 0;
    }
    return -1;
}
/*  */
f_fget()
{
	if ((fdfil = popen(arg2, "w")) == NULL)
	    error(badcmd,arg2);
        else
	{
	    errno = 0;
	    putcmd("RETR");
	    putstr(arg1);
	    sndcmd();
		if (chekds (0))
		{
		    pclose (fdfil);
		    fdfil = NULL;
		    net_close (&dsfdr);
		    if (errno == EINTR)
			abterr ();
		}

		rcvdata (&dsfdr, fdfil, logfn);
		net_close (&dsfdr);
	    pclose (fdfil);
	    fdfil = NULL;
	    return rwait (4);
	}
	return -1;
 }
/*  */
f_stor()
{
    store (arg1, arg2);
}

store (s1,s2)
char *s1, *s2;
{
	sndallo (s1, 0);
	putcmd ("STOR");
	snddat (s2, 0);
}


f_appe()
{
	sndallo (arg1, 0);
	putcmd ("APPE");
	snddat (arg2, 0);
}
/*  */
sndallo (s, extra)
  char *s;
  unsigned extra;
{
        long size;
	char sizestr[20];
	if ((fdfil = fopen (s,"r")) == NULL)
		error ("can't open \"%s\"; %s\n", s, errmsg (0));
	if (fstat (fileno(fdfil), &sttbf) == -1)
		error ("File \"%s\" not found; %s\n", s, errmsg (0));
	size = 2*sttbf.st_size+extra;
	putcmd ("ALLO");
	sprintf (sizestr, "%D", size);
	putstr (sizestr);
	sndcmd();
	if (rwait (2) == 0 && xreply < 500)       /* Some hosts don't like ALLOs */
	{
	    fclose (fdfil);
	    fdfil = NULL;
	    longjmp (env,1);
	}
}

/*  */
snddat (s, mlflg)
  char *s;
  int mlflg;
{
    int child;

    errno = 0;
    putstr (s);
    sndcmd ();
	if (chekds (1))
	{
	    fclose (fdfil);
	    fdfil = NULL;
	    net_close (&dsfds);
	    if (errno == EINTR) 
		abterr ();
	} else 
	{
		if (mlflg)
		    net_write (&dsfds, mlbf, strlen (mlbf));
		if (senddata (fdfil, &dsfds, logfn))
		    net_pclose (&dsfds);
		else net_close(&dsfds);
		rwait (4);
	}
	    if (fdfil != NULL)
	    {
		fclose (fdfil);
		fdfil = NULL;
	    }
		
}
/*  */
f_mlfl()
{
	setmlnm();
	putstr ("TYPE A"); sndcmd();
	if (rwait (2) == 0) return;
	type = TYPEA;
	putstr ("MODE S"); sndcmd();
	if (rwait (2) == 0) return;
	mode = MODES;
	if ((fdfil = fopen (arg1,"r")) == NULL)
		error ("can't open \"%s\"; %s\n", arg1, errmsg (0));
	putcmd ("MLFL");
	snddat (arg2, 1);
}
/*  */
f_mail()
{
    register char *p, *q;

	if (arg1[0] == 0)
	{
	    printf  (persn);
	    if (p = getarg())
	    {
		movarg (p,arg1,ARGSIZ-1);
		getarg();
	    }
	}
	putstr (arg1);
	sndcmd();
	if (rwait (9), xreply != 354) return;
	setmlnm();
	putstr (mlbf); sndcmd();
	ttptr = ttbuf;
	for (;;)
	{
	    if ((ttcnt = strlen(fgets (ttbuf,TTBSIZ, stdin))) <= 0) break;
	    if (ttbuf[0] == '.' && ttbuf[1] == '\n')
	    {
		ttptr = &ttbuf[2];
		ttcnt -= 2;
		break;
	    }
	    p = ttbuf;
	    q = linbuf;
	    while (ttcnt--)
		if (*p == '\n')
		{
		    linptr = q;
		    sndcmd();
		    q = linbuf;
		    p++;
		}
		else *q++ = *p++;
	}

	linbuf[0] = '.';
	linptr = &linbuf[1];
	sndcmd();
	rwait (10);
}
/*  */
subshl()
{   /* changed to handle signals, exit, and wait properly jsq BBN 4Aug79*/
    int ps, pid, i;
    int (*oldemt)(), (*oldint)(), (*oldquit)();
    extern usrint();

	if ((pid = VFORK()) == 0)
	{
	    close (TTYMON);
	    net_vclose (&NetParams);
	    net_vclose (&dsfds);
	    net_vclose (&dsfdr);
	    signal (SIGEMT, SIG_DFL);
	    execl ("/bin/sh","ftp-sh",0);
	    fprintf (stderr, "/bin/sh missing.\n");
	    VEXIT (-1);
	}

	oldemt  = signal (SIGEMT, SIG_IGN);
	oldquit = signal (SIGQUIT, SIG_IGN);
	oldint  = signal (SIGINT, SIG_IGN);

	while (wait (&ps) != pid);

	signal (SIGEMT, oldemt);
	signal (SIGQUIT, oldquit);
	signal (SIGINT, oldint);
}
/*  */
rget()
{
    int i;

    errno = 0;
    if ((i = read (pipnum, &xreply, sizeof xreply /*, 1, fdpip*/)) <= 0)
	{
	    if (uicount) 
	    {
		uicount--;
		xreply = 0;
		longjmp(env,1);
	    }
	    if ((i<0) || (errno != 0)) abort();
	    die (1, "%s:  Host exited; %s\n", progname, errmsg (0));
	}
    return xreply;
}

#ifdef MGET
getdirname()			/* get name of created directory */
{
	int i, j;

	*MadeDirectory = '\0';
	i = read (pipnum, &j, sizeof j /*, 1, fdpip*/);
	if (i>0) i = read (pipnum, MadeDirectory, j /*, 1, fdpip*/);
	if (*MadeDirectory == '\0') return (0);
	MadeDirectory[j] = '\0'; /* convert to real string */
	return (1);
}
#endif MGET
/*  */
rwait (n)
int n;
{
    register int    rnum;

    while ((rnum = rget ()) < 200)
	if (n < 0) break;		/* special hack to return low reply */

    if (rnum == 421) byedie();

#   ifdef MGET
	if (rnum == 251) return getdirname();
#   endif MGET

    if (rnum < 200 && n < 0) return 0;	/* hack for now. */

    switch (rnum / 100)
    {
	case 1:
	case 2:
	    return 1;

	case 3:
	case 4:
	case 5:
	    return 0;

	default:
	    abort ();
    }
}
/*  */
byedie()
{
	putcmd (QUIT);
	sndcmd();
	rwait (1);
	printf(dumptime());
	die (0, NULL);
}

abterr()
{
	register char *p, *q;

	uicount = 0;
	q = linbuf;
	p = aborstr;            /* <IAC> <IP> <IAC> <DM> ABOR */
   	while (*p)
		*q++ = *p++;
	linptr = q;
	urgon (&NetParams);       /* send INS to match DM */
	sndcmd();
	urgoff (&NetParams);       /* send INS to match DM */
	if (fdfil != NULL)
	{
	    fclose (fdfil);
	    fdfil = NULL;
	}
	while (rwait (8) && xreply!= 225 && xreply!=226);
#ifdef MGET
	if (*mretname)
	{
		unlink (mretname);
		*mretname = 0;
	}
#ifdef MGET
	longjmp (env,1);
}
/*  */
/* VARARGS */
error (n)
  char *n;
{

	ttcnt = 0;
	ttptr = ttbuf;
	printf ("%r", &n);
	fflush(stdout);
	if (fdfil != NULL)
	{
	    fclose (fdfil);
	    fdfil = NULL;
	}
	longjmp (env,1);
}

putcmd (s)                       /* put in 4 chars of cmd */
  char *s;
{
    register char *p, *q;
    register int i;

	p = linbuf;
	q = s;
	for (i=0; i<4; i++) *p++ = *q++;
	linptr = p;
}

putarg (s)                       /* put in argument with no space */
  char *s;
{
    register char *p, *q;
	p = linptr;     q = s;
	while (*q) *p++ = *q++;
	linptr = p;
}


putstr (s)                       /* put in space, then arg */
  char *s;
{
	*linptr++ = ' ';
	putarg (s);
}
/*  */
struct ftucmd *getcmd()
{
    register struct ftucmd *sp;
    register char *p;

	while ((p = getarg()) == 0) prompt();
	movarg (p,arg1,MAXSTRING);
	getuc (arg1);
	for (sp = cmdtab; (sp->ft_cnm); sp++)
	{
	    if (match (arg1, sp->ft_cnm)) goto win;
	}
	error (errtab[0]);
    win:
	if ((sp+1)->ft_cnm && match (arg1, (sp+1)->ft_cnm)) error (errtab[1]);
	if ((p = getarg()) == 0)
	    switch (sp->ft_nargs)
	    {
		case ARG0OR1:
		    arg1[0] = '\0';
		case ARG0:
		    return (sp);
		case ARG1:
		case ARG2:
		case ARG1AND2:
		case ARG1OR2:
		case ARG1OR2OR3:
		    printf ((sp->ft_info)[0]);
		    if ((p = getarg()) == 0) error (errtab[2]);
	    }
	else if (sp->ft_nargs == ARG0) error (errtab[3]);
	movarg (p, arg1, ARGSIZ-1);

	if ((p = getarg()) == 0)
	    switch (sp->ft_nargs)
	    {
		case ARG1AND2:
		    printf ((sp->ft_info)[1]);
		    if (p = getarg()) break;
		case ARG1OR2:                   /* arg 2 is optional */
		case ARG1OR2OR3:
		    arg2[0] = '\0';
		case ARG1:
		case ARG0OR1:
		    return (sp);
		case ARG2:
		    printf ((sp->ft_info)[1]);
		    if ((p = getarg()) == 0) error (errtab[2]);
	    }
	else if (sp->ft_nargs & (ARG1|ARG0OR1)) error (errtab[3]);
	movarg (p, arg2, ARGSIZ-1);
	if (p = getarg())
	    {
		if (sp->ft_nargs == ARG1OR2OR3)
			movarg (p,arg3,ARGSIZ-1);
		else error (errtab[3]);
	    }
	else arg3[0] = '\0';
	return (sp);
}
/*  */
movarg (s, buf, maxch)
  char *s, *buf;
  int maxch;
{
    register int i;
    register char *p, *q;
    char quote;

	p = s;  q = buf;   i = 0;
	if (*p == '"' || *p == '\'')
	{
	    quote = *p++;
	    i += 2;
	    maxch += 2;
	}
	else quote = '\0';
	if (quote) do if ((*q++ = *p++) == quote) goto out; while (i++ < maxch);
	else do if (issep (*q++ = *p++)) goto out; while (i++ < maxch);
	error (errtab[4]);
    out:
	*--q = '\0';
	ttcnt -= i;
	ttptr += i;
}
/*  */
char *getarg()
{
	for (;;)
	{
	    if (ttcnt <= 0)
	    {
		if (fgets (ttbuf, TTBSIZ, stdin) == NULL)
		    ttcnt = 0;
		else
		    ttcnt = strlen (ttbuf);
		if (ttcnt <= 0)
		{
		    if (uicount) longjmp (env,1);
		    byedie();
		}
		ttptr = ttbuf;
	    }
	    while (ttcnt-- > 0)
		switch (*ttptr++)
		{
		    case '\n':
			return (0);
		    case ' ':
		    case '\t':
			continue;
		    default:
			++ttcnt;
			return (--ttptr);
		}
	}
}
/*  */
match (s1, s2)
  char *s1, *s2;
{
    register char *p1, *p2;

	p1 = s1;  p2 = s2;
	while (*p1 == *p2++ && *p1) p1++;
	return (*p1 ? 0 : 1);
}

issep (c)
  char (c);
{
	return (!c) || isspace (c);
}

/*  */
setmlnm()
{
    register int c;
    FILE *ib;
    register char *p;
    char *q, ruid, noflg, pbuf[120], xbuf[10];

	ruid = getuid();
	if (getpw (ruid, pbuf)) pbuf[0] = 0;
	p = pbuf;       q = xbuf;
	while (*p && *p != ':') *q++ = *p++;
	*q = '\0';
	if ((ib = fopen ("/change/users", "r")) == NULL) noflg = 0;
	else for (;;)
	{
	    p = pbuf;
	    while ((c = getc (ib)) >= 0)
	    {
		if (c == '\n') break;
		*p++ = c;
	    }
	    *p = '\0';
	    if (c < 0)
	    {
		noflg = 0;
		break;
	    }
	    p = pbuf;
	    while (issep (*p) == 0) p++;
	    *p = '\0';
	    p = pbuf; q = xbuf;
	    while ((c = *q++ - *p) == 0 && *p++);
	    if (c > 0) continue;
	    if (c < 0) noflg = 0;
	    else noflg = 1;
	    break;
	}
	if (noflg)
	{
	    while (issep (*p++));
	    q = p - 1;
	    p = &pbuf[39];
	    while (issep (*p--));
	    *(p+2) = '\0';
	}
	mlptr = mlbf;
	mlstr ("From: ");
	mlstr (xbuf);
	if (noflg)
	{
	    mlstr (" (");
	    mlstr (q);
	    mlstr (")");
	}
	mlstr (" at ");
	mlstr (HOST_ID);         /* AGN */
	mlstr ("\r\n\r\n");	/* Blank line for end of header */
	*mlptr = '\0';
}
/*  */
mlstr (s)
  char *s;
{
    register char *p, *q;

	p = s;  q = mlptr;
	while (*q++ = *p++);
	mlptr = --q;
}

#ifdef MGET

#define SUCCESS 1
#define FAIL    0
#define ERROR   -1
#define CRLF    "\r\n"
#define NLIST   1       /* flags to next_filstr to tell it what its   */
#define DIRLIST 2       /* looking at--either a local directory or a nlst */
			/* from the server                                */

f_mkd()
{
    mkd (arg1);
}

mkd (s)
char *s;
{
    putcmd ("XMKD");
    putstr (s);
    sndcmd();
    return (rwait (13) ? -1 : 0);	/* make sure the directory is created */
}

f_rmdir()
{
    putcmd ("XRMD");
    putstr (arg1);
    sndcmd();
    rwait (7);
}
/*  */
f_msto()
{
    char dirplate[ARGSIZ]; /* if the wild-carded string includes a path */
    char template[ARGSIZ]; /* where to store the wild-carded string */
    char remroot[ARGSIZ]; /* where to store the foreign root */
    int ps, child, dirflg, i;
    register char *p, *lastslash;

    recurstor = ps = dirflg = 0;
    /* copy the args to a safe place */
    movarg (arg1, dirplate, ARGSIZ-1);

    if (arg2[0] != '\0')
    {
	movarg (arg2, remroot, ARGSIZ-1);
	recurstor++;
    }

    /* now, scan the first argument, and if it is a pathname rather than a */
    /* file in this directory, juggle the books, a bit.                    */
    for (p = lastslash = dirplate; *p; p++)
	if (*p == '/')
	{
		lastslash = p;
		dirflg++;
	}

    if (dirflg)
	*lastslash++ = '\0';    /* terminate the directory string */
    movarg (lastslash, template, ARGSIZ-1);
    if (dirflg)
    {
	if (*dirplate == '\0')	/* oops, a file in the root directory... */
	{
		dirplate[0] = '/';
		dirplate[1] = '\0';
	}
    }
    if (recurstor||dirflg)
    {
	 /* done in a lower fork so we can chdir freely */
	 if ((child = fork()) == 0)
	 {
		signal (SIGINT, SIG_DFL); /* let interrupts clobber child */
		progname = "ftpmain_child";
		if (dirflg) if (chdir (dirplate)<0)
		{
			printf ("%s: can't get to \"%s\"; %s\n",
				progname, dirplate, errmsg (0));
			exit (-1);
		}
		sendabunch (template,remroot);
		printf(dumptime());
		exit (0);
	} else if (child < 0)
	{
		printf ("%s: can't fork; %s\n", progname, errmsg (0));
	} else {
		errno = 0;
		while ((i = wait (&ps))!=child && i>0)
			{ ; }
		if (errno == EINTR)
		{
			kill (child, 9); /* make sure the child does indeed die, in */
				/* case it was ignoring interrupts at the  */
				/* time (see the checkds()) routine        */
		}
	}
    }
    else sendabunch (template,NULL);

    recurstor = dialog = 0;
}
/*  */
sendabunch (str,foroot)

/* basically for sending a whole directory (masked by template) full of */
/* stuff. */

char *str, *foroot;
{
    register FILE *dfds;      /* fds for the directory */
    register int i;
    struct stat statbuf;
    struct
    {
	int inode;
	char name_ptr[15];      /* extra one for a null at the end */
    } file;

    if (recurstor)
    {
	if (chdir (str)<0)
	{
		printf ("%s; can't chdir to \"%s\"\n", errmsg (0), str);
		return;
	}
	printf ("make directory \"%s\"\n", ((foroot==NULL)?foroot:str));
	if (mkd ((foroot==NULL)?foroot:str)<0)
	{
		printf ("making directory %s failed\n", str);
		chdir ("..");    /* undo the effects of the chdir above */
		return;
	} else if (cwd (desyntactify (MadeDirectory))<0)
	{
		printf ("moving to directory \"%s\" failed\n", str);
		chdir ("..");    /* undo the effects of the chdir above */
		return;
	}
 }

    /* open up the directory, and iterate through its contents, masking */
    /* each with template to determine whether it should be asked for  */
    if ((dfds = fopen (".", "r")) == NULL)
    {
	char dot[80];   /* place for abspath to stringify */
	abspath (".", dot, &dot[sizeof (dot)]);
	printf ("can't open directory \"%s\"; %s\n", dot, errmsg (0));
	return;
 }
/*  */
  /* skip the directory over the '.' & '..' entries */
  fseek (dfds,(long)(2*(sizeof file)), 0);
  file.name_ptr[14] = '\0';      /* make sure there's a null on the end */
  /* iterate through the directory */
  while ((i = fread (&file, sizeof file, 1, dfds)) <= 0)
  {
    if (file.inode != 0)
    {
	sleep (1);       /* allow time for socket to be fully closed */
	if (uicount)
	{
		recurstor = 0;
		fclose (dfds);
		send();
		abterr();
	}
	if (recurstor)
	{
		/************************************************************/
		/* note that an interrupt here does not require us to clean */
		/* up all the files we have openned, as interrupts are not  */
		/* caught, and this child process simply exits...           */
		/************************************************************/
		stat (file.name_ptr, &statbuf);
		if (statbuf.st_mode&S_IFDIR)
		{
			/* send the command to make the directory, &
			/* ship the directory */
			/* recurse into the subdirectory */
			sendabunch (file.name_ptr,NULL);
		}
		else{
			printf ("store \"%s\" as \"%s\"\n",file.name_ptr, file.name_ptr);
			store (file.name_ptr,file.name_ptr);
		}
	}
	else if (glob (str, file.name_ptr) == SUCCESS)
	{
		printf ("store \"%s\" as \"%s\":\n", file.name_ptr, file.name_ptr);
		store (file.name_ptr,file.name_ptr);
	}
    }
 }
/*  */
    if (i == NULL)
    {
	char dot[80];   /* place for abspath to stringify */
	abspath (".", dot, &dot[sizeof (dot)]);
	printf ("error reading directory \"%s\"; %s\n", dot, errmsg (0));
    }

    /* if nothing left in this directory, change back to parent */
    fclose (dfds);
    if (recurstor)
    {
	putcmd ("XCUP");
	sndcmd();
	rwait (2);
	chdir ("..");
    }
}
/*  */
char *
desyntactify (str)       /* strip the quotes from the returned */
char *str;              /* directory.  NOTE: acts directly on its argument */
{
	register char *p, *q;

	q = p = str;
	while (*p)
	{
		if (*p == '"') p++;
		*q++ = *p++;
	}
	*q = '\0';
	return (str);
}
/*  */
glob (tmpl,str)
char *str;
char *tmpl;
 /* str is a candidate to match tmpl.  tmpl contains junk in the normal
    glob-type format, e.g. '*' matches anything in str, '?' matches any
    single char, and "[...]" in tmpl gives an acceptable set of guys in
    str to match, "[...a-z...]" matches any char between a & z.
  */
{
	register char *t, *s;

	t = tmpl; s = str;

	while (*s && *t == *s ) { t++; s++; }
	if (*t == '\0' && *s == '\0') return (SUCCESS);

	switch (*t++)
	{   /* tmpl & str have diverged; if it is because *tmpl is a */
			/*  globular character, then do the appropriate thing.   */
			/*  otherwise, return FAIL                               */
	 case '*':
	   /****************************************************************/
	   /* algorithm for '*':                                           */
	   /*  if we've used up string,                                    */
	   /*     if there's nothing left to tmpl, success.                */
	   /*     if tmpl expects more, fail.                              */
	   /*  otherwise, recurse on the stuff after the asterisk in tmpl  */
	   /*   and str++                                                  */
	   /****************************************************************/
		while (1)		/* mung */
		{
			if (*t == '\0') return (SUCCESS);
			if (*s == '\0')
			{
				if (*t != '\0') return (FAIL);
				return (SUCCESS);
			}
			if ((glob (t, s++)) == SUCCESS) return (SUCCESS);
		}

	 case '?':
		return (glob (t, ++s));

	 case '[':
		if (one_of (t, *s++) == SUCCESS)
		{
			while (*t && *t++ != ']');
			return (glob (t, s));
		}
		return (FAIL);

	 default:
		return (FAIL);

	 }
}
/*  */
one_of (tmpl, ch)
char *tmpl;
int ch;
/* [...a-z...] handler */
{
 register char *t;

 t = tmpl;

 while (*t)
 {
	switch (*t)
	{
	 case ']':
		return (FAIL);

	 case '-':
		if (ch >= *(t-1) && ch <= *(t+1) && *(t+1) != ']')
			return (SUCCESS);
		goto next;

	 default:
		if (ch == *t) return (SUCCESS);
	}
 next:  t++;
 }
 return (ERROR);
}
/*  */
f_mret()
{
    char    name[256];
    char    dstn[256];
    FILE   *mretfile;

 /* generate a unique filename */
    sprintf (mretname, "/tmp/ftp=%d.%d", getpid (), getuid());
    if(mretfile != NULL)	/* if an earlier mret was interrupted, then */
    {
	fclose(mretfile);	/* this file already exists... */
	unlink(mretname);
    }

    if (fnedinit (arg2) < 0)
	return;

    printf ("getting list of names from server....\n");
    if (nlst (arg1, mretname) == 0)
    {
	printf ("nlst failed\n");
	unlink (mretname);
	abterr ();
	return;
    }
    if ((mretfile = fopen (mretname, "r")) == NULL)
    {
	printf ("can't open temporary file \"%s\"; %s\n",
		mretname, errmsg (0));
	unlink (mretname);
	return;
    }

    while (mretfile != NULL)	/* until mretfile is exhausted */
    {
	sleep (20);		/* give system enough time to clean up
				   from */
	if (uicount)		/* the last transfer */
	{
	    fclose (mretfile);
	    unlink (mretname);
	    mretfile = NULL;
	    abterr ();
	}
	if (fgets (name, sizeof name, mretfile) != NULL)
	{
	    name[strlen(name)-1] = '\0';	/* backup over newline */
	    if (fned (name, dstn, sizeof (dstn)) < 0)
	    {
		printf ("\"%s\" not retrieved\n", name);
	    }
	    else
	    {
		if (!(fnflag & FNASK))
		    printf ("retrieve \"%s\" as \"%s\"\n", name, dstn);
		putcmd ("RETR");
		fetch (name, dstn);
	    }
	}
    }

    fclose (mretfile);
    unlink (mretname);
    *mretname = 0;
}
/*  */
char * mrethlp[] =
{
  "'?' prints this informatio;n'-flags' sets up a string-processor to",
  "transform a foreign file-name into something Unix would understand",
  "\nFlags to set up file-name processor:\n",
  "  T - Tenex/Tops20-mode: strips the <...> directory, and the protection",
  "      information from the filename; also it lowers the case of the",
  "      letters in the file-name, removes control-V's, and replaces",
  "      Tenex's semi-colons before the version number with periods",
  "  I - ITS mode: strips the \"DSK: DIR;\" from the beginning of the",
  "      filename, lowers the case of the letters in the filename, and",
  "      replaces spaces with '_'",
  "  M - Multics mode: not implimented yet",
  "  a - alphanumerics only: non-alphanumeric letters in the remote",
  "      filename are stripped (NOTE: '.' is regarded as alphabetic)",
  "  l - convert uppercase to lower-case",
  "  p/foo/ -",
  "      prefix the string 'foo' to each file-name ('\\' will escape '/')",
  "      this flag may be combined with any other",
  "  ? - rather than trying to mold the remote filename into a Unix",
  "      filename, just ask the user for a name to store this file",
  NULL
};
/*  */
fnedinit (key)
char *key;
{
    register char  *p;
    register char ** q;

    fnflag = 0;
    *fnprefix = '\0';
    if (!*key)
    {
	fnflag |= FNCPY;
	return (1);
    }
    if (*key == '?')
    {
	for (q=mrethlp; *q != NULL;  q++)
		puts(*q);
	return (-1);
    }
    if (*key++ != '-')
    {
	printf ("Flags must begin with hyphens\n");
	return (-1);
    }
    while (*key)
    {
	switch (*key++)
	{
	    case 'T': 
		fnflag |= FNTENEX | FNTOLCASE;
		break;
	    case 'I': 
		fnflag |= FNITS | FNTOLCASE;
		break;
	    case 'l': 
		fnflag |= FNTOLCASE | FNCPY;
		break;
	    case 'a': 
		fnflag |= FNALPHA | FNCPY;
		break;
	    case '?': 		/* asking precludes all others */
		*fnprefix = '\0';
		fnflag = FNASK;
		return 0;
	    case 'p': 
		fnflag |= FNCPY;
		p = fnprefix;
		if (*key++ != '/')
		{
		    printf ("no '/' bordering prefix\n");
		    return (-1);
		}
		while (*key && *key != '/')
		{
		    if (*key == '\\')
			key++;
		    *p++ = *key++;
		}
		if (*key)
		    key++;
		else
		    return (1);
		*p = '\0';
		break;
	    default: 
		printf ("bad flag: '%c'\n", *(--key));
		return (-1);
	}
    }
    return (0);
}
/*  */
fned (src, dest, destsize) /* file-name editor -- does transformations on file-names */
char *src, *dest;
{
    register char *p, *d;
    register int c;

	d = dest;
	/* enter prefix */
	if (*(p = fnprefix)) while (*p && d < &(dest[destsize-1])) *d++ = *p++;
	if (fnflag & FNASK)
	{
		printf ("retrieve \"%s\" as: ", src);
		if ((p = getarg()) <= 0) /* ignore newline */
			if ((p = getarg()) <= 0) return (-1);
		movarg (p, d, (destsize-1));
		return (1);
	}
	p = src;
	if (fnflag & FNTENEX)
	{
		/* strip the leading directory, and the trailing protection */
		/* first, the directory, if its there... */
		if (*p == '<')
			while (*p && *p++ != '>')
				;
		/* look for the third dot-or-semicolon */
		c = 0;
		while (*p && d < &(dest[destsize-1]))
		{
			if ((*p == ';' || *p == '.') && c++ >= 2) break;
			else {  /* control-V is a quoting character */
				if (*p == CTLV) p++;
				if (*p == ';')
				{
					p++;
					*d++ = '.';
				} else *d++ = *p++;
			}
		}
	} else if (fnflag & FNITS)
	{
		while (*p && *p++ != ';')
			;
		do {
			if (*p == ' ') *d++ = '_';
			else *d++ = *p;
		} while (*p++ && d < &(dest[destsize-1]));

	} else if (fnflag & FNCPY)
		 while (*p && d < &(dest[destsize-1])) *d++ = *p++;
	*d = '\0';

	/* now do individual character transformations */
	d = dest;
	/* skip past the prefix */
	if (*(p = fnprefix)) while (*p++) d++;
	do {
		if ((fnflag & FNALPHA)
		     && !(isdigit (c = *d) || isalpha (c) || c == '.'))
		{
			p = d;
			do {                    /* flush this character */
				*p = *(p+1);    /* bump up the rest */
			} while (*p++);
		}
		if ((fnflag & FNTOLCASE) && (isupper (*d)))
			*d++ = tolower (*d);
		else d++;
	} while (*d);
	return (1);
}
#endif  MGET
/*  */
#define IOBSIZ  120
char ibuf[IOBSIZ], *iptr;
int icnt = 0;
int cmdflg = 0;
extern int synchno;

int ins();

ftp_printer(otherhalf)
int otherhalf;          /* the arguments to this process */
{
    register char *p;
    int n;

    extern exit(), diequit(), dieinit();
    extern char *atoiv(), *get_dirstr();

	dieinit(otherhalf, stdout);
	signal(SIGINT, SIG_IGN);	/* Ignore interrupt--for main FTP */
	signal(SIGQUIT, SIG_IGN);	/* Ignore quit, so quit in inferior
					shell of ftpmain works. */
	signal(SIGURG,ins);
	fclose(stdin);
	fclose(stdout);
	for (;;)
	{
	    linein();
	    n = iconv(linbuf);
	    fputs(linbuf, stderr);
	    if (n)
	    {
		if (write(pipnum, &n, sizeof n) != sizeof(n))
			die(1, "%s:  can't write reply-code to ftpmain; %s\n",
			    progname, errmsg(0));
		if (n == MADEDIR)
		{
		    if (p = get_dirstr(linbuf))
		    {
			n = strlen(p);
			write(pipnum, &n, sizeof n);
			write(pipnum, p, n);
		    }
		    else	   /* write a 0 if no directory string */
		    {
			n = 0;
			write(pipnum, &n, sizeof n);
		    }
		}
	    }
	}
}

iconv(s)
  char *s;
{
    register int k, i, c;

	k = 0;
	for (i = 0; i < 3; i++)
	{
	    c = s[i];
	    if(c < '0' || c > '9') return(0);
	    k = k*10 + c - '0';
	}
	if(s[3] == '-') return(0);      /* this is a continued comment */
	return(k);
}

char *get_dirstr(s)
char *s;
{
     register char *p, *q;

	q = p = &(s[4]);
	if (*p++ != '"') return 0;
	while (*p)
	{
	    if (*p++ == '"')	/* check if this is an escaped quote */
		{
		    if (*p == '"')
			{ p++; continue; }
		    else		/* not an escaped quote, return */
			{
			    *p = '\0';
			    return q;
			}
		}
	}
	*p = '\0';
	return q;
}
/*  */
linein()                                /* Get a line into linbuf */
{
    register int c, ovflg;
    register char *linptr;
    int retflg;

	linptr = linbuf;
	retflg = ovflg = 0;

	for (;;)
	{
	    if(linptr >= (linbuf + LINSIZ - 2)) ovflg++;
	    c = getch();
	    if(cmdflg)
	    {
		cmdflg = 0;

		/* "Interesting" Commands */
		if(c == TNDM)
		{
#		    ifdef TCP
			extern tsturg();
			synchno = tsturg(&NetParams);
#		    endif TCP
#		    ifdef NCP
			--synchno;
#		    endif NCP
		    linptr = linbuf;
		    ovflg = 0;
		    continue;
		}

		/* Other Telnet Commands */
		if(synchno == 0)
		{
		    switch(c)
		    {
			case TNEC:
			    if(linptr>linbuf)
			    {
				linptr--;
				ovflg = 0;
			    }
			    continue;
			case TNEL:
			    linptr = linbuf;
			    ovflg = 0;
			    continue;
			case TNDO:
			case TNWILL:
			    tpopt(c);
			    continue;
			case TNIAC:
			    if(ovflg == 0) *linptr++ = c;
		    }
		}
		continue;  /* Command ignored if synchno or not implemented */
	    }

	    if(c == TNIAC) cmdflg++;
	    else if(synchno == 0) switch(c)
	    {
		case '\r':
		    retflg++;
		    continue;
		case '\n':
		    *linptr++ = c;
		    *linptr++ = '\0';
		    return;
		case '\0':
		    if(retflg)
		    {
			if(ovflg == 0) *linptr++ = '\r';
			retflg = 0;
		    }
		    continue;
		default:
		    if(ovflg == 0) *linptr++ = c;
	    }
	}
}
/*  */
tpopt(c)
  char c;
{
    char bf[3];

	bf[0] = TNIAC;
	bf[1] = (c == TNDO ? TNWONT : TNDONT);
	bf[2] = getch();
	if (net_write(&NetParams, bf, 3) < 0)
	    die(3, "%s:  can't write options to net; %s\n", errmsg(0));

}

getch()
{
	if(--icnt < 0)
	{
retry:
		if ((icnt = net_read(&NetParams, ibuf, IOBSIZ)) < 0) {
		    if (errno == EINTR)
			goto retry;
		    else if (errno == ENETSTAT) {
			get_stuff(&NetParams);
			if (NetParams.ns.n_state & URXTIMO) {
				printf("Host not responding\n");
				goto retry;
			} else if (NetParams.ns.n_state & UURGENT)
				goto retry;
		    }
		}
		if(icnt-- < 0)
		  die(4, "%s:  net input closed; %s\n",
		    progname, errmsg(0));
		iptr = ibuf;
	}
	return(*iptr++ & 0377);
}
/*  */
/* User Telnet and FTP - modified for Illinois NCP from old Rand user telnet.
 *
 * Note: BBN-UNIX only uses it to invoke User FTP. User Telnet is now a
 * completely different program. Dan Franklin (BBN)
 *
 * Changed to use long host numbers jsq BBN 3-27-79.
 *
 * Leave signals alone: child will set according to what parent had set
 *  jsq BBN 5Aug79
 *
 * Pass arguments to both sides of ftp:  pid of other side, name of other
 *  host, name of this host.  All host name-number conversions are now in this
 *  process, and children can now mop up on each other when either one dies.
 *
 * Changed to flush the telnet code, as we don't use it any more dm 3-19-80
 *
 * jsq BBN 5April80 reformatted using indent, put proper names for files
 * to exec back.
 *
 * changed to work with NCP or TCP dm 11-19-80
 */
char ttibuf[32];

main (argc, argv)
int   argc;
char *argv[];
{
	int badhost;
  int   pid, parent;
  portsock socket;
  char  *host;
  int   ftpip[2];
  netaddr hnum;
  register int  count;

  signal (SIG_NETINT, SIG_IGN);
  progname = argv[0];
  socket = (portsock)0;
  if (argc > 1)
  {
	host = argv[1];
	hnum = gethost(host);
	badhost = isbadhost(hnum);
	if (badhost)
		printf ("Unknown host name: \"%s\"\n", host);
	if (argc > 2) socket = ATOSOCK (argv[2]);
  }

  while (badhost)
  {
	printf ("Host: ");
	if (fgets (ttibuf, sizeof ttibuf, stdin) == NULL) exit (1);
	count = strlen (ttibuf);
	if (ttibuf[count-1] == '\n') ttibuf[count-1] = 0;
	while (count-- >= 0)
	    if (ttibuf[count] == '?')
		{
			printf("use host(1) or prhost(1) to find host names\n");
		    break;
		}
	if ((count < 0) && (ttibuf[0]))
	{
		hnum = gethost(ttibuf);
		badhost = isbadhost(hnum);
		if (badhost)
			printf ("Unknown host name: \"%s\"\n", ttibuf);
		else host = ttibuf;
	}
  }

  printf ("Trying %s (%s)\n", host, hostfmt(hnum, 1));
  if (net_open(&NetParams, hnum, (socket?socket:(portsock)FTPSOCK)) < 0)
     {
	printf ("%s cannot connect: %s\n", progname, errmsg (0));
	exit (1);
      }

  printf ("Connections established.\n\n");
  us = thisname (0);
  them = hostname (hnum);
  strcpy ((HOST_ID = malloc(strlen(them)+1)), them);
  getuc (HOST_ID);
  parent = getpid();
  pipe (ftpip);
  if ((pid = fork()) == -1)
  {
	printf ("%s: can't fork %s\n", progname, errmsg (0));
	exit (1);
  }
  if (pid == 0)
  {
	close(ftpip[0]);
/*	fdpip = fdopen(ftpip[1],"w"); */
	pipnum = ftpip[1];
	argv[0] = progname = "ftp_printer";
	ftp_printer(parent);
	exit (1);
  }

  close(ftpip[1]);
/*  fdpip = fdopen(ftpip[0]); Sorry, this loses. */
  pipnum = ftpip[0];
  argv[0] = progname = "ftpmain";
  ftpmain(pid);
}

/*
 * convert to upper case
 */
getuc(s)
register char *s;
{
	register int c;

	while (c = *s)
		*s++ = islower(c)? toupper(c) : c;
}