/* Copyright (c) 1985, 1989 Regents of the University of California.         */
/* All rights reserved.                                                      */
/*                                                                           */
/* Redistribution and use in source and binary forms, with or without        */
/* modification, are permitted provided that the following conditions        */
/* are met:                                                                  */
/*                                                                           */
/* 1. Redistributions of source code must retain the above copyright         */
/*    notice, this list of conditions and the following disclaimer.          */
/* 2. Redistributions in binary form must reproduce the above copyright      */
/*    notice, this list of conditions and the following disclaimer in the    */
/*    documentation and/or other materials provided with the distribution.   */
/* 3. All advertising materials mentioning features or use of this software  */
/*    must display the following acknowledgement:                            */
/*      This product includes software developed by the University of        */
/*      California, Berkeley and its contributors.                           */
/* 4. Neither the name of the University nor the names of its contributors   */
/*    may be used to endorse or promote products derived from this software  */
/*    without specific prior written permission.                             */
/*                                                                           */
/* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND   */
/* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE     */
/* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE*/
/* ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE  */
/* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL*/
/* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS   */
/* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)     */
/* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT*/
/* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY */
/* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF    */
/* SUCH DAMAGE.                                                              */

/* FTP User Program -- Command Routines.                                     */
#include "ftp_var.h"

#define DONT_NEED_SIGBLOCK
#define DONT_NEED_SIGPAUSE
#define DONT_NEED_SIGUNBLOCK
#define DONT_NEED_SIGPENDING
#include "sigfix.h"

#include <arpa/ftp.h>
#include <ctype.h>
#include "pathnames.h"

#ifndef HAVE_GETCWD
extern char *getwd();
#endif

extern int command();

extern	char *home;
extern	char *globerr;
extern	char **ftpglob();
extern	char *remglob();
extern	int  errno;
extern off_t restart_point;
extern char reply_string[];

char *mname;
jmp_buf jabort;
char *dotrans(), *domap();

/*
 * Options and other state info.
 */
int	trace;			/* trace packets exchanged */
int	hash;			/* print # for each buffer transferred */
int	sendport;		/* use PORT cmd for each data connection */
int	verbose;		/* print messages coming back from server */
int	connected;		/* connected to server */
int	fromatty;		/* input is from a terminal */
int	interactive;		/* interactively prompt on m* cmds */
int	debug;			/* debugging level */
int	bell;			/* ring bell on cmd completion */
int	doglob;			/* glob local file names */
int	autologin;		/* establish user account on connection */
int	proxy;			/* proxy server connection active */
int	proxflag;		/* proxy connection exists */
int	sunique;		/* store files on server with unique name */
int	runique;		/* store local files with unique name */
int	mcase;			/* map upper to lower case for mget names */
int	ntflag;			/* use ntin ntout tables for name translation */
int	mapflag;		/* use mapin mapout templates on file names */
int	code;			/* return/reply code for ftp command */
int	crflag;			/* if 1, strip car. rets. on ascii gets */
char	pasv[64];		/* passive port for proxy data connection */
char	*altarg;		/* argv[1] with no shell-like preprocessing  */
char	ntin[17];		/* input translation table */
char	ntout[17];		/* output translation table */

char	mapin[MAXPATHLEN];	/* input map template */
char	mapout[MAXPATHLEN];	/* output map template */
char	typename[32];		/* name of file transfer type */
int	type;			/* requested file transfer type */
int	curtype;		/* current file transfer type */
char	structname[32];		/* name of file transfer structure */
int	stru;			/* file transfer structure */
char	formname[32];		/* name of file transfer format */
int	form;			/* file transfer format */
char	modename[32];		/* name of file transfer mode */
int	mode;			/* file transfer mode */
char	bytename[32];		/* local byte size in ascii */
int	bytesize;		/* local byte size in binary */

char	*hostname;		/* name of host connected to */
int	unix_server;		/* server is unix, can use binary for ascii */
int	unix_proxy;		/* proxy is unix, can use binary for ascii */

struct	servent *sp;		/* service spec for tcp/ftp */
int	ftp_s_port;	        /* save ftp's sp->s_port value here */
char	line[200];		/* input line buffer */
char	*stringbase;		/* current scan point in line buffer */
char	argbuf[200];		/* argument storage buffer */
char	*argbase;		/* current storage point in arg buffer */
int	margc;			/* count of arguments on input line */
char	*margv[20];		/* args parsed from input line */
int     cpend;                  /* flag: if != 0, then pending server reply */
int	mflag;			/* flag: if != 0, then active multi command */
int	options;		/* used during socket creation */

jmp_buf	toplevel;		/* non-local goto stuff for cmd scanner */

int macnum;			/* number of defined macros */
struct macel macros[16];
char macbuf[4096];

/* `Another' gets another argument, and stores the new argc and argv.        */
/* It reverts to the top level (via main.c's intr()) on EOF/error.           */
/* Returns false if no new arguments have been added.                        */
int another(int *pargc, char ***pargv, char *prompt) {
  int len = strlen(line), ret;
  extern sig_t intr();

  if (len >= sizeof(line) - 3) {
    printf("sorry, arguments too long\n");
    intr();
  }

  printf("(%s) ", prompt);
  line[len++] = ' ';

  if (fgets(&line[len], sizeof(line) - len, stdin) == NULL) intr();

  len += strlen(&line[len]);
  if (len > 0 && line[len - 1] == '\n') line[len - 1] = '\0';

  makeargv();
  ret = margc > *pargc;

  *pargc = margc;
  *pargv = margv;

  return ret;
}

/* Connect to peer server and auto-login, if possible.                       */
void setpeer(int argc, char *argv[]) {
  char *host, *hookup();
  short port;

  if (connected) {
    printf("Already connected to %s, use close first.\n", hostname);
    code = -1;
    return;
  }
  if (argc < 2)
    another(&argc, &argv, "to");

  if (argc < 2 || argc > 3) {
    printf("usage: %s host-name [port]\n", argv[0]);
    code = -1;
    return;
  }

  /* port = sp->s_port; */
  port = ftp_s_port;

  if (argc > 2) {
    port = atoi(argv[2]);
    if (port <= 0) {
      printf("%s: bad port number-- %s\n", argv[1], argv[2]);
      printf ("usage: %s host-name [port]\n", argv[0]);
      code = -1;
      return;
    }
    port = htons(port);
  }

  host = hookup(argv[1], port);
  if (host) {
#if NBBY == 8
    int overbose;
#endif
	
    connected = 1;
    /* Set up defaults for FTP.                                          */
    strcpy(typename, "ascii"), type = TYPE_A;
    curtype = TYPE_A;
    strcpy(formname, "non-print"), form = FORM_N;
    strcpy(modename, "stream"), mode = MODE_S;
    strcpy(structname, "file"), stru = STRU_F;
    strcpy(bytename, "8"), bytesize = 8;

    if (autologin) 
      ftplogin(argv[1]);
	
#if NBBY == 8
    /* this ifdef is to keep someone form "porting" this to an           */
    /* incompatible system and not checking this out. This way they have */
    /* to think about it.                                                */
    overbose = verbose;

    if (debug == 0)
      verbose = -1;
    if (command("SYST\r\n") == COMPLETE && overbose) {
      register char *cp, c;
      cp = index(reply_string+4, ' ');
      if (cp == NULL) cp = index(reply_string+4, '\r');

      if (cp) {
	if (cp[-1] == '.') cp--;
	c = *cp;
	*cp = '\0';
      }
	    
      printf("Remote system type is %s.\n", reply_string+4);
      if (cp) *cp = c;
    }

    if (!strncmp(reply_string, "215 UNIX Type: L8", 17)) {
      if (proxy) unix_proxy = 1;
      else       unix_server = 1;

      /* Set type to 0 (not specified by user), meaning binary by      */
      /* default, but don't bother telling server.  We can use binary  */
      /* for text files unless changed by the user.                    */
      type = 0;
      strcpy(typename, "binary");
      if (overbose)
	printf("Using %s mode to transfer files.\n", typename);
    } else {
      if (proxy) unix_proxy = 0;
      else       unix_server = 0;

      if (overbose && !strncmp(reply_string, "215 TOPS20", 10))
	printf("Remember to set tenex mode when transfering binary files from this machine.\n");
    }

    verbose = overbose;
#endif 
  }
}

struct	types {
  char	*t_name;
  char	*t_mode;
  int	         t_type;
  char	*t_arg;
} types[] = {
  { "ascii",	"A",	TYPE_A,	0 },
  { "binary",	"I",	TYPE_I,	0 },
  { "image",	"I",	TYPE_I,	0 },
  { "ebcdic",	"E",	TYPE_E,	0 },
  { "tenex",	"L",	TYPE_L,	bytename },
  { NULL,	NULL,	0,	NULL }
};

/* Set transfer type.                                                        */
void settype(int argc, char *argv[]) {
  register struct types *p;
  int comret;
    
  if (argc > 2) {
    char *sep;

    printf("usage: %s [", argv[0]);
    sep = " ";
    for (p = types; p->t_name; p++) {
      printf("%s%s", sep, p->t_name);
      sep = " | ";
    }

    printf(" ]\n");
    code = -1;
    return;
  }

  if (argc < 2) {
    printf("Using %s mode to transfer files.\n", typename);
    code = 0;
    return;
  }

  for (p = types; p->t_name; p++)
    if (strcmp(argv[1], p->t_name) == 0)
      break;

  if (p->t_name == 0) {
    printf("%s: unknown mode\n", argv[1]);
    code = -1;
    return;
  }

  if ((p->t_arg != NULL) && (*(p->t_arg) != '\0'))
    comret = command ("TYPE %s %s\r\n", p->t_mode, p->t_arg);
  else
    comret = command("TYPE %s\r\n", p->t_mode);

  if (comret == COMPLETE) {
    strcpy(typename, p->t_name);
    curtype = type = p->t_type;
  }
}

/* Internal form of settype; changes current type in use with server         */
/* without changing our notion of the type for data transfers.               */
/* Used to change to and from ascii for listings.                            */
void changetype(int newtype, int show) {
  register struct types *p;
  int comret, oldverbose = verbose;
    
  if (newtype == 0)
    newtype = TYPE_I;

  if (newtype == curtype)
    return;

  if (debug == 0 && show == 0)
    verbose = 0;

  for (p = types; p->t_name; p++)
    if (newtype == p->t_type)
      break;

  if (p->t_name == 0) {
    printf("ftp: internal error: unknown type %d\n", newtype);
    return;
  }

  if (newtype == TYPE_L && bytename[0] != '\0')
    comret = command("TYPE %s %s\r\n", p->t_mode, bytename);
  else
    comret = command("TYPE %s\r\n", p->t_mode);

  if (comret == COMPLETE)
    curtype = newtype;

  verbose = oldverbose;
}

char *stype[] = {
  "type",
  "",
  0
};

/* Set binary transfer type.                                                 */
void setbinary() {
  stype[1] = "binary";
  settype(2, stype);
}

/* Set ascii transfer type.                                                  */
void setascii() {
  stype[1] = "ascii";
  settype(2, stype);
}

/* Set tenex transfer type.                                                  */
void settenex() {
  stype[1] = "tenex";
  settype(2, stype);
}

/* Set file transfer mode.                                                   */
void setftpmode(int argc, char *argv[]) {
  printf("We only support %s mode, sorry.\n", modename);
  code = -1;
}

/* Set file transfer format.                                                 */
void setform(int argc, char *argv[]) {
  printf("We only support %s format, sorry.\n", formname);
  code = -1;
}

/* Set file transfer structure.                                              */
void setstruct(int argc, char *argv[]) {
  printf("We only support %s structure, sorry.\n", structname);
  code = -1;
}

/* Glob a local file name specification with the expectation of a single     */
/* return value.  Can't control multiple values being expanded from the      */
/* expression, we return only the first.                                     */
int globulize(char **cpp) {
  char **globbed;

  if (!doglob) return (1);
  globbed = ftpglob(*cpp);

    
  if (globerr != NULL) {
    printf("%s: %s\n", *cpp, globerr);

    if (globbed) {
      blkfree(globbed);
      free((char *)globbed);
    }

    return 0;
  }

  if (globbed) {
    *cpp = *globbed++;

    /* don't waste too much memory */

    if (*globbed) {
      blkfree(globbed);
      free((char *)globbed);
    }
  }

  return 1;
}

/* Send a single file.                                                       */
void put(int argc, char *argv[]) {
  char *cmd;
  int loc = 0;
  char *oldargv1, *oldargv2;

  if (argc == 2) {
    argc++;
    argv[2] = argv[1];
    loc++;
  }

  if (argc < 2 && !another(&argc, &argv, "local-file"))
    goto usage;

  if (argc < 3 && !another(&argc, &argv, "remote-file")) {
  usage:
    printf("usage: %s local-file remote-file\n", argv[0]);
    code = -1;
    return;
  }

  oldargv1 = argv[1];
  oldargv2 = argv[2];

  if (!globulize(&argv[1])) {
    code = -1;
    return;
  }

  /* If "globulize" modifies argv[1], and argv[2] is a copy of             */
  /* the old argv[1], make it a copy of the new argv[1].                   */
  if (argv[1] != oldargv1 && argv[2] == oldargv1) 
    argv[2] = argv[1];

  cmd = (argv[0][0] == 'a') ? "APPE" : ((sunique) ? "STOU" : "STOR");

  if (loc && ntflag) 
    argv[2] = dotrans(argv[2]);

  if (loc && mapflag) 
    argv[2] = domap(argv[2]);

  sendrequest(cmd, argv[1], argv[2], argv[1] != oldargv1 || argv[2] != oldargv2);
}

int confirm(char *cmd, char *file) {
  char bline[BUFSIZ];

  if (!interactive) return (1);
  printf("%s %s? ", cmd, file);
  fflush(stdout);

  if (fgets(bline, sizeof(bline), stdin) == NULL) return (0);
  return (*bline != 'n' && *bline != 'N');
}

/* Send multiple files.                                                      */
void mabort() {
  int ointer;
  extern jmp_buf jabort;
    
  printf("\n");
  fflush(stdout);

  if (mflag && fromatty) {
    ointer = interactive;
    interactive = 1;

    if (confirm("Continue with", mname)) {
      interactive = ointer;
      longjmp(jabort,0);
    }

    interactive = ointer;
  }

  mflag = 0;
  longjmp(jabort,0);
}

void mput(int argc, char **argv) {
  extern jmp_buf jabort;
  register int i;
  sig_t oldintr;
  int ointer;
  char *tp;
  void mabort();
    
  if (argc < 2 && !another(&argc, &argv, "local-files")) {
    printf("usage: %s local-files\n", argv[0]);
    code = -1;
    return;
  }

  mname = argv[0];
  mflag = 1;
  oldintr = (sig_t)Signal(SIGINT, mabort);
  setjmp(jabort);

  if (proxy) {
    char *cp, *tp2, tmpbuf[MAXPATHLEN];
	
    while ((cp = remglob(argv,0)) != NULL) {
      if (*cp == 0) {
	mflag = 0;
	continue;
      }

      if (mflag && confirm(argv[0], cp)) {
	tp = cp;
	if (mcase) {
	  while (*tp && !islower((unsigned char)*tp)) {
	    tp++;
	  }

	  if (!*tp) {
	    tp = cp;
	    tp2 = tmpbuf;

	    while ((*tp2 = *tp)) {
	      if (isupper((unsigned char)*tp2)) {
		*tp2 = 'a' + *tp2 - 'A';
	      }

	      tp++;
	      tp2++;
	    }
	  }
	  tp = tmpbuf;
	}

	if (ntflag) {
	  tp = dotrans(tp);
	}

	if (mapflag) {
	  tp = domap(tp);
	}

	sendrequest((sunique) ? "STOU" : "STOR", cp, tp, cp != tp || !interactive);

	if (!mflag && fromatty) {
	  ointer = interactive;
	  interactive = 1;

	  if (confirm("Continue with","mput")) {
	    mflag++;
	  }

	  interactive = ointer;
	}
      }
    }

    Signal(SIGINT, oldintr);
    mflag = 0;
    return;
  }

  for (i = 1; i < argc; i++) {
    register char **cpp, **gargs;

    if (!doglob) {
      if (mflag && confirm(argv[0], argv[i])) {
	tp = (ntflag) ? dotrans(argv[i]) : argv[i];
	tp = (mapflag) ? domap(tp) : tp;
	sendrequest((sunique) ? "STOU" : "STOR", argv[i], tp, tp != argv[i] || !interactive);
	if (!mflag && fromatty) {
	  ointer = interactive;
	  interactive = 1;

	  if (confirm("Continue with","mput")) {
	    mflag++;
	  }

	  interactive = ointer;
	}
      }
      continue;
    }

    gargs = ftpglob(argv[i]);

    if (globerr != NULL) {
      printf("%s\n", globerr);
      if (gargs) {
	blkfree(gargs);
	free((char *)gargs);
      }
      continue;
    }

    for (cpp = gargs; cpp && *cpp != NULL; cpp++) {
      if (mflag && confirm(argv[0], *cpp)) {
	tp = (ntflag) ? dotrans(*cpp) : *cpp;
	tp = (mapflag) ? domap(tp) : tp;
	sendrequest((sunique) ? "STOU" : "STOR", *cpp, tp, *cpp != tp || !interactive);
	if (!mflag && fromatty) {
	  ointer = interactive;
	  interactive = 1;
	  if (confirm("Continue with","mput")) {
	    mflag++;
	  }
	  interactive = ointer;
	}
      }
    }

    if (gargs != NULL) {
      blkfree(gargs);
      free((char *)gargs);
    }
  }

  Signal(SIGINT, oldintr);
  mflag = 0;
}

/* Receive one file.                                                         */
int getit(int argc, char *argv[], int restartit, char *mode) {
  int loc = 0;
  char *oldargv1, *oldargv2;

  if (argc == 2) {
    argc++;
    argv[2] = argv[1];
    loc++;
  }

  if (argc < 2 && !another(&argc, &argv, "remote-file"))
    goto usage;

  if (argc < 3 && !another(&argc, &argv, "local-file")) {
  usage:
    printf("usage: %s remote-file [ local-file ]\n", argv[0]);
    code = -1;
    return (0);
  }

  oldargv1 = argv[1];
  oldargv2 = argv[2];

  if (!globulize(&argv[2])) {
    code = -1;
    return (0);
  }

  if (loc && mcase) {
    char *tp = argv[1], *tp2, tmpbuf[MAXPATHLEN];
	
    while (*tp && !islower((unsigned char)*tp)) tp++;

    if (!*tp) {
      tp = argv[2];
      tp2 = tmpbuf;

      while ((*tp2 = *tp)) {
	if (isupper((unsigned char)*tp2)) *tp2 = tolower(*tp2);
	tp2++;
	tp++;
      }

      argv[2] = tmpbuf;
    }
  }

  if (loc && ntflag)
    argv[2] = dotrans(argv[2]);
  if (loc && mapflag)
    argv[2] = domap(argv[2]);

  if (restartit) {
    struct stat stbuf;
    int ret;
	
    ret = stat(argv[2], &stbuf);
    if (restartit == 1) {
      if (ret < 0) {
	fprintf(stderr, "local: %s: %s\n", argv[2],
		strerror(errno));
	return (0);
      }
      restart_point = stbuf.st_size;
    } else {
      if (ret == 0) {
	int overbose;
		
	overbose = verbose;
	if (debug == 0)
	  verbose = -1;
	if (command("MDTM %s\r\n", argv[1]) == COMPLETE) {
	  int yy, mo, day, hour, min, sec;
	  struct tm *tm;
	  verbose = overbose;
	  sscanf(reply_string,
		 "%*s %04d%02d%02d%02d%02d%02d",
		 &yy, &mo, &day, &hour, &min, &sec);
	  tm = gmtime(&stbuf.st_mtime);
	  tm->tm_mon++;
	  if (tm->tm_year > yy%100)
	    return (1);
	  else if (tm->tm_year == yy%100) {
	    if (tm->tm_mon > mo)
	      return (1);
	  } else if (tm->tm_mon == mo) {
	    if (tm->tm_mday > day)
	      return (1);
	  } else if (tm->tm_mday == day) {
	    if (tm->tm_hour > hour)
	      return (1);
	  } else if (tm->tm_hour == hour) {
	    if (tm->tm_min > min)
	      return (1);
	  } else if (tm->tm_min == min) {
	    if (tm->tm_sec > sec)
	      return (1);
	  }
	} else {
	  printf("%s\n", reply_string);
	  verbose = overbose;
	  return (0);
	}
      }
    }
  }
    
  recvrequest("RETR", argv[2], argv[1], mode,
	      argv[1] != oldargv1 || argv[2] != oldargv2);
  restart_point = 0;
  return (0);
}

void reget(int argc, char *argv[]) {
  getit(argc, argv, 1, "r+w");
}

void get(int argc, char *argv[]){
  getit(argc, argv, 0, restart_point ? "r+w" : "w" );
}

/* Get multiple files.                                                       */
void mget(int argc, char **argv) {
  extern jmp_buf jabort;
  sig_t oldintr;
  int ointer;

  char *cp, *tp, *tp2, tmpbuf[MAXPATHLEN];
  void mabort();

  if (argc < 2 && !another(&argc, &argv, "remote-files")) {
    printf("usage: %s remote-files\n", argv[0]);
    code = -1;
    return;
  }

  mname = argv[0];
  mflag = 1;
  oldintr = (sig_t)Signal(SIGINT,mabort);
  setjmp(jabort);

  while ((cp = remglob(argv,proxy)) != NULL) {
    if (*cp == '\0') {
      mflag = 0;
      continue;
    }

    if (mflag && confirm(argv[0], cp)) {
      tp = cp;
      if (mcase) {
	while (*tp && !islower((unsigned char)*tp)) {
	  tp++;
	}
	if (!*tp) {
	  tp = cp;
	  tp2 = tmpbuf;
		    
	  while ((*tp2 = *tp)) {
	    if (isupper((unsigned char)*tp2)) *tp2 = tolower(*tp2);
	    tp2++;
	    tp++;
	  }
	}
	tp = tmpbuf;
      }

      if (ntflag) {
	tp = dotrans(tp);
      }

      if (mapflag) {
	tp = domap(tp);
      }

      recvrequest("RETR", tp, cp, "w", tp != cp || !interactive);

      if (!mflag && fromatty) {
	ointer = interactive;
	interactive = 1;

	if (confirm("Continue with","mget")) {
	  mflag++;
	}
	interactive = ointer;
      }
    }
  }

  Signal(SIGINT,oldintr);
  mflag = 0;
}

char *remglob(char *argv[], int doswitch) {
  char temp[16];
  static char buf[MAXPATHLEN];
  static FILE *ftemp = NULL;
  static char **args;
  int oldverbose, oldhash;
  char *cp, *mode;

  if (!mflag) {
    if (!doglob) {
      args = NULL;
    }
    else {
      if (ftemp) {
	fclose(ftemp);
	ftemp = NULL;
      }
    }
    return(NULL);
  }
  if (!doglob) {
    if (args == NULL)
      args = argv;
    if ((cp = *++args) == NULL)
      args = NULL;
    return (cp);
  }
  if (ftemp == NULL) {
    strcpy(temp, _PATH_TMP);
    mktemp(temp);
    oldverbose = verbose, verbose = 0;
    oldhash = hash, hash = 0;
    if (doswitch) {
      pswitch(!proxy);
    }
    for (mode = "w"; *++argv != NULL; mode = "a")
      recvrequest ("NLST", temp, *argv, mode, 0);
    if (doswitch) {
      pswitch(!proxy);
    }
    verbose = oldverbose; hash = oldhash;
    ftemp = fopen(temp, "r");
    unlink(temp);
    if (ftemp == NULL) {
      printf("can't find list of remote files, oops\n");
      return (NULL);
    }
  }
  if (fgets(buf, sizeof (buf), ftemp) == NULL) {
    fclose(ftemp), ftemp = NULL;
    return (NULL);
  }
  if ((cp = index(buf, '\n')) != NULL)
    *cp = '\0';
  return (buf);
}

char *onoff(int bool) {
  return (bool ? "on" : "off");
}

/* Show status.                                                              */
/*                                                                           */
/* ARGSUSED                                                                  */
void status(int argc, char *argv[]) {
  int i;

  if (connected) {
    printf("Connected to %s.\n", hostname);
  } else {
    printf("Not connected.\n");
  }

  if (!proxy) {
    pswitch(1);
    if (connected) {
      printf("Connected for proxy commands to %s.\n", hostname);
    } else {
      printf("No proxy connection.\n");
    }
    pswitch(0);
  }

  printf("Mode: %s; Type: %s; Form: %s; Structure: %s\n",
	 modename, typename, formname, structname);
  printf("Verbose: %s; Bell: %s; Prompting: %s; Globbing: %s\n", 
	 onoff(verbose), onoff(bell), onoff(interactive),
	 onoff(doglob));
  printf("Store unique: %s; Receive unique: %s\n", onoff(sunique),
	 onoff(runique));
  printf("Case: %s; CR stripping: %s\n",onoff(mcase),onoff(crflag));

  if (ntflag) {
    printf("Ntrans: (in) %s (out) %s\n", ntin,ntout);
  } else {
    printf("Ntrans: off\n");
  }

  if (mapflag) {
    printf("Nmap: (in) %s (out) %s\n", mapin, mapout);
  } else {
    printf("Nmap: off\n");
  }
    
  printf("Hash mark printing: %s; Use of PORT cmds: %s\n",
	 onoff(hash), onoff(sendport));

  if (macnum > 0) {
    printf("Macros:\n");
    for (i=0; i<macnum; i++) {
      printf("\t%s\n",macros[i].mac_name);
    }
  }

  code = 0;
}

/* Set beep on cmd completed mode.                                           */
/*                                                                           */
/* VARARGS                                                                   */
void setbell() {
  bell = !bell;
  printf("Bell mode %s.\n", onoff(bell));
  code = bell;
}

/* Turn on packet tracing.                                                   */
/*                                                                           */
/* VARARGS                                                                   */
void settrace() {
  trace = !trace;
  printf("Packet tracing %s.\n", onoff(trace));
  code = trace;
}

/* Toggle hash mark printing during transfers.                               */
/*                                                                           */
/* VARARGS                                                                   */
void sethash() {
  hash = !hash;
  printf("Hash mark printing %s", onoff(hash));
  code = hash;

  if (hash)
    printf(" (%d bytes/hash mark)", 1024);
  printf(".\n");
}

/* Turn on printing of server echo's.                                        */
/*                                                                           */
/* VARARGS                                                                   */
void setverbose() {
  verbose = !verbose;
  printf("Verbose mode %s.\n", onoff(verbose));
  code = verbose;
}

/* Toggle PORT cmd use before each data connection.                          */
/*                                                                           */
/* VARARGS                                                                   */
void setport() {
  sendport = !sendport;
  printf("Use of PORT cmds %s.\n", onoff(sendport));
  code = sendport;
}

/* Turn on interactive prompting during mget, mput, and mdelete.             */
/*                                                                           */
/* VARARGS                                                                   */
void setprompt() {
  interactive = !interactive;
  printf("Interactive mode %s.\n", onoff(interactive));
  code = interactive;
}

/* Toggle metacharacter interpretation on local file names.                  */
/*                                                                           */
/* VARARGS                                                                   */
void setglob() {
  doglob = !doglob;
  printf("Globbing %s.\n", onoff(doglob));
  code = doglob;
}

/* Set debugging mode on/off and/or set level of debugging.                  */
/*                                                                           */
/* VARARGS                                                                   */
void setdebug(int argc, char *argv[]) {
  int val;

  if (argc > 1) {
    val = atoi(argv[1]);
    if (val < 0) {
      printf("%s: bad debugging value.\n", argv[1]);
      code = -1;
      return;
    }
  } else
    val = !debug;
  debug = val;
  if (debug)
    options |= SO_DEBUG;
  else
    options &= ~SO_DEBUG;

  printf("Debugging %s (debug=%d).\n", onoff(debug), debug);
  code = debug > 0;
}

/* Set current working directory on remote machine.                          */
void cd(int argc, char **argv) {
  if (argc < 2 && !another(&argc, &argv, "remote-directory")) {
    printf("usage: %s remote-directory\n", argv[0]);
    code = -1;
    return;
  }

  if (command("CWD %s\r\n", argv[1]) == ERROR && code == 500) {
    if (verbose) printf("CWD command not recognized, trying XCWD\n");
    command("XCWD %s\r\n", argv[1]);
  }
}

/* Set current working directory on local machine.                           */
void lcd(int argc, char **argv) {
  char buf[MAXPATHLEN];

  if (argc < 2)
    argc++, argv[1] = home;
  if (argc != 2) {
    printf("usage: %s local-directory\n", argv[0]);
    code = -1;
    return;
  }
  if (!globulize(&argv[1])) {
    code = -1;
    return;
  }
  if (chdir(argv[1]) < 0) {
    fprintf(stderr, "local: %s: %s\n", argv[1], strerror(errno));
    code = -1;
    return;
  }
#ifdef HAVE_GETCWD
  printf("Local directory now %s\n", getcwd(buf, MAXPATHLEN-1));
#else
  printf("Local directory now %s\n", getwd(buf));
#endif
  code = 0;
}

/* Delete a single file.                                                     */
void delete(int argc, char *argv[]) {
  if (argc < 2 && !another(&argc, &argv, "remote-file")) {
    printf("usage: %s remote-file\n", argv[0]);
    code = -1;
    return;
  }

  command("DELE %s\r\n", argv[1]);
}

/* Delete multiple files.                                                    */
void mdelete(int argc, char *argv[]) {
  extern jmp_buf jabort;
  sig_t oldintr;
  int ointer;
  char *cp;
  void mabort();
    
  if (argc < 2 && !another(&argc, &argv, "remote-files")) {
    printf("usage: %s remote-files\n", argv[0]);
    code = -1;
    return;
  }

  mflag = 1;
  mname = argv[0];
  oldintr = (sig_t)Signal(SIGINT, mabort);
  setjmp(jabort);

  while ((cp = remglob(argv,0)) != NULL) {
    if (*cp == '\0') {
      mflag = 0;
      continue;
    }

    if (mflag && confirm(argv[0], cp)) {
      command("DELE %s\r\n", cp);
      if (!mflag && fromatty) {
	ointer = interactive;
	interactive = 1;
	if (confirm("Continue with", "mdelete")) {
	  mflag++;
	}
	interactive = ointer;
      }
    }
  }

  Signal(SIGINT, oldintr);
  mflag = 0;
}

/* Rename a remote file.                                                     */
void renamefile(int argc, char *argv[]) {

  if (argc < 2 && !another(&argc, &argv, "from-name"))
    goto usage;
  if (argc < 3 && !another(&argc, &argv, "to-name")) {
  usage:
    printf("%s from-name to-name\n", argv[0]);
    code = -1;
    return;
  }

  if (command("RNFR %s\r\n", argv[1]) == CONTINUE)
    command("RNTO %s\r\n", argv[2]);
}

/* Get a directory listing                                                   */
/* of remote files.                                                          */
void ls(int argc, char *argv[]) {
  char *cmd;

  if (argc < 2) argc++, argv[1] = NULL;
  if (argc < 3) argc++, argv[2] = "-";

  if (argc > 3) {
    printf("usage: %s remote-directory local-file\n", argv[0]);
    code = -1;
    return;
  }

  cmd = argv[0][0] == 'n' ? "NLST" : "LIST";

  if (strcmp(argv[2], "-") && !globulize(&argv[2])) {
    code = -1;
    return;
  }

  if (strcmp(argv[2], "-") && *argv[2] != '|')
    if (!globulize(&argv[2]) || !confirm("output to local-file:", argv[2])) {
      code = -1;
      return;
    }

  recvrequest(cmd, argv[2], argv[1], "w", 0);
}

/* Get a directory listing                                                   */
/* of multiple remote files.                                                 */
void mls(int argc, char **argv) {
  extern jmp_buf jabort;
  sig_t oldintr;
  int ointer, i;
  char *cmd, mode[1], *dest;
  void mabort();

  if (argc < 2 && !another(&argc, &argv, "remote-files"))
    goto usage;
  if (argc < 3 && !another(&argc, &argv, "local-file")) {
  usage:
    printf("usage: %s remote-files local-file\n", argv[0]);
    code = -1;
    return;
  }
  dest = argv[argc - 1];
  argv[argc - 1] = NULL;
  if (strcmp(dest, "-") && *dest != '|')
    if (!globulize(&dest) ||
	!confirm("output to local-file:", dest)) {
      code = -1;
      return;
    }
  cmd = argv[0][1] == 'l' ? "NLST" : "LIST";
  mname = argv[0];
  mflag = 1;
  oldintr = (sig_t)Signal(SIGINT, mabort);
  setjmp(jabort);
  for (i = 1; mflag && i < argc-1; ++i) {
    *mode = (i == 1) ? 'w' : 'a';
    recvrequest(cmd, dest, argv[i], mode, 0);
    if (!mflag && fromatty) {
      ointer = interactive;
      interactive = 1;
      if (confirm("Continue with", argv[0])) {
	mflag ++;
      }
      interactive = ointer;
    }
  }
  Signal(SIGINT, oldintr);
  mflag = 0;
}

/* Do a shell escape                                                         */
/*                                                                           */
/* ARGSUSED                                                                  */
int shell(int argc, char **argv) {
  int pid;
  sig_t old1, old2;
  char shellnam[40], *shell, *namep; 
  int  status;
  old1 = (sig_t)Signal (SIGINT, SIG_IGN);
  old2 = (sig_t)Signal (SIGQUIT, SIG_IGN);
  if ((pid = fork()) == 0) {
    for (pid = 3; pid < 20; pid++)
      close(pid);
    Signal(SIGINT, SIG_DFL);
    Signal(SIGQUIT, SIG_DFL);
    shell = getenv("SHELL");
    if (shell == NULL)
      shell = _PATH_BSHELL;
    namep = rindex(shell,'/');
    if (namep == NULL)
      namep = shell;
    strcpy(shellnam,"-");
    strcat(shellnam, ++namep);
    if (strcmp(namep, "sh") != 0)
      shellnam[0] = '+';
    if (debug) {
      printf ("%s\n", shell);
      fflush (stdout);
    }
    if (argc > 1) {
      execl(shell,shellnam,"-c",altarg,(char *)0);
    }
    else {
      execl(shell,shellnam,(char *)0);
    }
    perror(shell);
    code = -1;
    exit(1);
  }
  if (pid > 0)
    while (wait(&status) != pid) ;
  Signal(SIGINT, old1);
  Signal(SIGQUIT, old2);
  if (pid == -1) {
    perror("Try again later");
    code = -1;
  }
  else {
    code = 0;
  }
  return (0);
}

/* Send new user information (re-login)                                      */
int user(int argc, char **argv) {
  char acct[80], *GETPASS();
  int n, aflag = 0;

  if (argc < 2) {
    another(&argc, &argv, "username");
  }

  if (argc < 2 || argc > 4) {
    printf("usage: %s username [password] [account]\n", argv[0]);
    code = -1;
    return (0);
  }

  if ((n = command("USER %s\r\n", argv[1])) == CONTINUE) {
    if (argc < 3 ){
      if (!strcmp(argv[1], "anonymous") || !strcmp(argv[1], "ftp")) {
	printf("Password: "); fflush(stdout);
	fgets(acct, sizeof(acct) - 1, stdin);
	acct[strlen(acct) - 1] = '\0';
	argv[2] = acct;
      } else
	argv[2] = GETPASS("Password: ");
      argc++;
    }
    n = command("PASS %s\r\n", argv[2]);
  }

  if (n == CONTINUE) {
    if (argc < 4) {
      printf("Account: "); fflush(stdout);
      fgets(acct, sizeof(acct) - 1, stdin);
      acct[strlen(acct) - 1] = '\0';
      argv[3] = acct; argc++;
    }
    n = command("ACCT %s\r\n", argv[3]);
    aflag++;
  }
  if (n != COMPLETE) {
    fprintf(stdout, "Login failed.\n");
    return (0);
  }
  if (!aflag && argc == 4) {
    command("ACCT %s\r\n", argv[3]);
  }
  return (1);
}

/* Print working directory.                                                  */
/*                                                                           */
/* VARARGS                                                                   */
void pwd() {
  int oldverbose = verbose;

  /* If we aren't verbose, this doesn't do anything!                         */
  verbose = 1;
  if (command("PWD\r\n") == ERROR && code == 500) {
    printf("PWD command not recognized, trying XPWD\n");
    command("XPWD\r\n");
  }
  verbose = oldverbose;
}

/* Make a directory.                                                         */
void makedir(int argc, char *argv[]) {
  if (argc < 2 && !another(&argc, &argv, "directory-name")) {
    printf("usage: %s directory-name\n", argv[0]);
    code = -1;
    return;
  }
  if (command("MKD %s\r\n", argv[1]) == ERROR && code == 500) {
    if (verbose)
      printf("MKD command not recognized, trying XMKD\n");
    command("XMKD %s\r\n", argv[1]);
  }
}

/* Remove a directory.                                                       */
void removedir(int argc, char *argv[]) {
  if (argc < 2 && !another(&argc, &argv, "directory-name")) {
    printf("usage: %s directory-name\n", argv[0]);
    code = -1;
    return;
  }
  if (command("RMD %s\r\n", argv[1]) == ERROR && code == 500) {
    if (verbose)
      printf("RMD command not recognized, trying XRMD\n");
    command("XRMD %s\r\n", argv[1]);
  }
}

/* Turn argv[1..argc) into a space-separated string, then prepend initial    */
/* text.  Send the result as a one-line command and get response.            */
void quote1(char *initial, int argc, char **argv) {
  register int i, len=0;
  char buf[BUFSIZ];		/* must be >= sizeof(line) */
  char *eolstr = "\r\n";

  bzero(buf, BUFSIZ);
  len += strlen(strncpy(buf, initial, MIN((BUFSIZ-len), strlen(initial))));
  if (argc > 1) {
    len += strlen(strncpy(&buf[len], argv[1], MIN((BUFSIZ-len), strlen(argv[1]))));
    for (i = 2; i < argc; i++) {
      buf[len++] = ' ';
      len += strlen(strncpy(&buf[len], argv[i], MIN((BUFSIZ-len), strlen(argv[i]))));
    }
  }
  len += strlen(strncpy(&buf[len], eolstr, MIN((BUFSIZ-len), strlen(eolstr))));
  buf[MIN((BUFSIZ-1), len)] = '\0';
  if (command(buf) == PRELIM) {
    while (getreply(0) == PRELIM);
  }
}

/* Send a line, verbatim, to the remote machine.                             */
void quote(int argc, char *argv[]) { 
  if (argc < 2 && !another(&argc, &argv, "command line to send")) {
    printf("usage: %s line-to-send\n", argv[0]);
    code = -1;
    return;
  }
  quote1("", argc, argv);
}

/* Send a SITE command to the remote machine.  The line                      */
/* is sent verbatim to the remote machine, except that the                   */
/* word "SITE" is added at the front.                                        */
void site(int argc, char *argv[]) {
  if (argc < 2 && !another(&argc, &argv, "arguments to SITE command")) {
    printf("usage: %s line-to-send\n", argv[0]);
    code = -1;
    return;
  }
  quote1("SITE ", argc, argv);
}

void do_chmod(int argc, char *argv[]) {
  if (argc < 2 && !another(&argc, &argv, "mode"))
    goto usage;
  if (argc < 3 && !another(&argc, &argv, "file-name")) {
  usage:
    printf("usage: %s mode file-name\n", argv[0]);
    code = -1;
    return;
  }
  command("SITE CHMOD %s %s\r\n", argv[1], argv[2]);
}

void do_umask(int argc, char *argv[]) {
  int oldverbose = verbose;

  verbose = 1;
  command(argc == 1 ? "SITE UMASK\r\n" : "SITE UMASK %s\r\n", argv[1]);
  verbose = oldverbose;
}

void idletime(int argc, char *argv[]) {
  int oldverbose = verbose;

  verbose = 1;
  command(argc == 1 ? "SITE IDLE\r\n" : "SITE IDLE %s\r\n", argv[1]);
  verbose = oldverbose;
}

/* Ask the other side for help.                                              */
void rmthelp(int argc, char *argv[]) {
  int oldverbose = verbose;

  verbose = 1;
  command(argc == 1 ? "HELP\r\n" : "HELP %s\r\n", argv[1]);
  verbose = oldverbose;
}

/* Terminate session, but don't exit.                                        */
void disconnect() {
  extern FILE *cout;
  extern int data;

  if (!connected)
    return;
  command("QUIT\r\n");
  if (cout) {
    fclose(cout);
  }
  cout = NULL;
  connected = 0;
  data = -1;
  if (!proxy) {
    macnum = 0;
  }
}

/* Terminate session and exit.                                               */
/*                                                                           */
/* VARARGS                                                                   */
int quit() {
  if (connected) disconnect();
  pswitch(1);

  if (connected) disconnect();
  exit(0);

  return -1;
}

void fatal(char *msg) {
  fprintf(stderr, "ftp: %s\n", msg);
  exit(1);
}

void account(int argc, char **argv) {
  char acct[50], *GETPASS(), *ap;

  if (argc > 1) {
    ++argv;
    --argc;
    strncpy(acct,*argv,49);
    acct[49] = '\0';
    while (argc > 1) {
      --argc;
      ++argv;
      strncat(acct,*argv, 49-strlen(acct));
    }
    ap = acct;
  }
  else {
    ap = GETPASS("Account:");
  }
  command("ACCT %s\r\n", ap);
}

jmp_buf abortprox;

void proxabort() {
  extern int proxy;

  if (!proxy) {
    pswitch(1);
  }
  if (connected) {
    proxflag = 1;
  }
  else {
    proxflag = 0;
  }
  pswitch(0);
  longjmp(abortprox,1);
}

void doproxy(int argc, char *argv[]) {
  extern jmp_buf abortprox;
  register struct cmd *c;
  struct cmd *getcmd();
  sig_t oldintr;
  void proxabort();
    
  if (argc < 2 && !another(&argc, &argv, "command")) {
    printf("usage: %s command\n", argv[0]);
    code = -1;
    return;
  }
  c = getcmd(argv[1]);
  if (c == (struct cmd *) -1) {
    printf("?Ambiguous command\n");
    fflush(stdout);
    code = -1;
    return;
  }
  if (c == 0) {
    printf("?Invalid command\n");
    fflush(stdout);
    code = -1;
    return;
  }
  if (!c->c_proxy) {
    printf("?Invalid proxy command\n");
    fflush(stdout);
    code = -1;
    return;
  }
  if (setjmp(abortprox)) {
    code = -1;
    return;
  }
  oldintr = (sig_t)Signal(SIGINT, proxabort);
  pswitch(1);
  if (c->c_conn && !connected) {
    printf("Not connected\n");
    fflush(stdout);
    pswitch(0);
    Signal(SIGINT, oldintr);
    code = -1;
    return;
  }
  (*c->c_handler)(argc-1, argv+1);
  if (connected) {
    proxflag = 1;
  }
  else {
    proxflag = 0;
  }
  pswitch(0);
  Signal(SIGINT, oldintr);
}

void setcase() {
  mcase = !mcase;
  printf("Case mapping %s.\n", onoff(mcase));
  code = mcase;
}

void setcr() {
  crflag = !crflag;
  printf("Carriage Return stripping %s.\n", onoff(crflag));
  code = crflag;
}

void setntrans(int argc, char *argv[]) {
  if (argc == 1) {
    ntflag = 0;
    printf("Ntrans off.\n");
    code = ntflag;
    return;
  }
  ntflag++;
  code = ntflag;
  strncpy(ntin, argv[1], 16);
  ntin[16] = '\0';
  if (argc == 2) {
    ntout[0] = '\0';
    return;
  }
  strncpy(ntout, argv[2], 16);
  ntout[16] = '\0';
}

char *
dotrans(char *name)
{
  static char new[MAXPATHLEN];
  char *cp1, *cp2 = new;
  register int i, ostop, found;

  for (ostop = 0; *(ntout + ostop) && ostop < 16; ostop++);
  for (cp1 = name; *cp1; cp1++) {
    found = 0;
    for (i = 0; *(ntin + i) && i < 16; i++) {
      if (*cp1 == *(ntin + i)) {
	found++;
	if (i < ostop) {
	  *cp2++ = *(ntout + i);
	}
	break;
      }
    }
    if (!found) {
      *cp2++ = *cp1;
    }
  }
  *cp2 = '\0';
  return(new);
}

void setnmap(int argc, char *argv[]) {
  char *cp;
    
  if (argc == 1) {
    mapflag = 0;
    printf("Nmap off.\n");
    code = mapflag;
    return;
  }

  if (argc < 3 && !another(&argc, &argv, "mapout")) {
    printf("Usage: %s [mapin mapout]\n",argv[0]);
    code = -1;
    return;
  }

  mapflag = 1;
  code = 1;
  cp = index(altarg, ' ');

  if (proxy) {
    while(*++cp == ' ');
    altarg = cp;
    cp = index(altarg, ' ');
  }

  *cp = '\0';
  strncpy(mapin, altarg, MAXPATHLEN - 1);
  while (*++cp == ' ');
  strncpy(mapout, cp, MAXPATHLEN - 1);
}

char *domap(char *name) {
  static char new[MAXPATHLEN];
  register char *cp1 = name, *cp2 = mapin;
  char *tp[9], *te[9];
  int i, toks[9], toknum = 0, match = 1;

  for (i=0; i < 9; ++i) {
    toks[i] = 0;
  }

  while (match && *cp1 && *cp2) {
    switch (*cp2) {
      case '\\':
	if (*++cp2 != *cp1) {
	  match = 0;
	}
	break;
      case '$':
	if (*(cp2+1) >= '1' && (*cp2+1) <= '9') {
	  if (*cp1 != *(++cp2+1)) {
	    toks[toknum = *cp2 - '1']++;
	    tp[toknum] = cp1;
	    while (*++cp1 && *(cp2+1) != *cp1);
	    te[toknum] = cp1;
	  }
	  cp2++;
	  break;
	}
	/* FALLTHROUGH */
      default:
	if (*cp2 != *cp1) {
	  match = 0;
	}
	break;
    }

    if (match && *cp1) {
      cp1++;
    }
    if (match && *cp2) {
      cp2++;
    }
  }

  if (!match && *cp1) {
    /* last token mismatch */
    toks[toknum] = 0;
  }

  cp1  = new;
  *cp1 = '\0';
  cp2  = mapout;

  while (*cp2) {
    match = 0;
    switch (*cp2) {
      case '\\':
	if (*(cp2 + 1)) {
	  *cp1++ = *++cp2;
	}
	break;
      case '[':
    LOOP:
      if (*++cp2 == '$' && isdigit((unsigned char)*(cp2+1))) { 
	if (*++cp2 == '0') {
	  char *cp3 = name;
			
	  while (*cp3) {
	    *cp1++ = *cp3++;
	  }
	  match = 1;
	} else if (toks[toknum = *cp2 - '1']) {
	  char *cp3 = tp[toknum];
			
	  while (cp3 != te[toknum]) {
	    *cp1++ = *cp3++;
	  }
	  match = 1;
	}
      } else {
	while (*cp2 && *cp2 != ',' && *cp2 != ']') {
	  if (*cp2 == '\\') {
	    cp2++;
	  } else if (*cp2 == '$' && isdigit((unsigned char)*(cp2+1))) {
	    if (*++cp2 == '0') {
	      char *cp3 = name;
				
	      while (*cp3) {
		*cp1++ = *cp3++;
	      }
	    } else if (toks[toknum = *cp2 - '1']) {
	      char *cp3=tp[toknum];
				
	      while (cp3 != te[toknum]) {
		*cp1++ = *cp3++;
	      }
	    }
	  } else if (*cp2) {
	    *cp1++ = *cp2++;
	  }
	}

	if (!*cp2) {
	  printf("nmap: unbalanced brackets\n");
	  return(name);
	}

	match = 1;
	cp2--;
      }

      if (match) {
	while (*++cp2 && *cp2 != ']') {
	  if (*cp2 == '\\' && *(cp2 + 1)) {
	    cp2++;
	  }
	}

	if (!*cp2) {
	  printf("nmap: unbalanced brackets\n");
	  return(name);
	}

	break;
      }
		
      switch (*++cp2) {
	case ',':
	  goto LOOP;
	case ']':
	  break;
	default:
	  cp2--;
	  goto LOOP;
      }

			
      break;
      case '$':
	if (isdigit((unsigned char)*(cp2 + 1))) {
	  if (*++cp2 == '0') {
	    char *cp3 = name;
			
	    while (*cp3) {
	      *cp1++ = *cp3++;
	    }
	  } else if (toks[toknum = *cp2 - '1']) {
	    char *cp3 = tp[toknum];
			
	    while (cp3 != te[toknum]) {
	      *cp1++ = *cp3++;
	    }
	  }
	  break;
	}

	/* intentional drop through */
      default:
	*cp1++ = *cp2;
	break;
    }

    cp2++;
  }

  *cp1 = '\0';

  if (!*new) {
    return(name);
  }

  return(new);
}

void setsunique() {
  sunique = !sunique;
  printf("Store unique %s.\n", onoff(sunique));
  code = sunique;
}

void setrunique() {
  runique = !runique;
  printf("Receive unique %s.\n", onoff(runique));
  code = runique;
}

/* change directory to perent directory                                      */
void cdup() {
  if (command("CDUP\r\n") == ERROR && code == 500) {
    if (verbose) printf("CDUP command not recognized, trying XCUP\n");
    command("XCUP\r\n");
  }
}

/* restart transfer at specific point                                        */
void restart(int argc, char *argv[]) {
  extern long atol();

  if (argc != 2) {
    printf("restart: offset not specified\n");
  } else {
    restart_point = atol(argv[1]);
    printf("restarting at %ld. execute get, put or append to initiate transfer\n", (long)restart_point);
  }
}

/* show remote system type                                                   */
void syst() {
  command("SYST\r\n");
}

void macdef(int argc, char *argv[]) {
  char *tmp;
  int c;

  if (macnum == 16) {
    printf("Limit of 16 macros have already been defined\n");
    code = -1;
    return;
  }

  if (argc < 2 && !another(&argc, &argv, "macro name")) {
    printf("Usage: %s macro_name\n",argv[0]);
    code = -1;
    return;
  }

  if (interactive) {
    printf("Enter macro line by line, terminating it with a null line\n");
  }

  strncpy(macros[macnum].mac_name, argv[1], 8);

  if (macnum == 0) {
    macros[macnum].mac_start = macbuf;
  } else {
    macros[macnum].mac_start = macros[macnum - 1].mac_end + 1;
  }

  tmp = macros[macnum].mac_start;

  while (tmp != macbuf+4096) {
    if ((c = getchar()) == EOF) {
      printf("macdef:end of file encountered\n");
      code = -1;
      return;
    }

    if ((*tmp = c) == '\n') {
      if (tmp == macros[macnum].mac_start) {
	macros[macnum++].mac_end = tmp;
	code = 0;
	return;
      }

      if (*(tmp-1) == '\0') {
	macros[macnum++].mac_end = tmp - 1;
	code = 0;
	return;
      }
	    
      *tmp = '\0';
    }

    tmp++;
  }

  while (1) {
    while ((c = getchar()) != '\n' && c != EOF) /* LOOP */;
	
    if (c == EOF || getchar() == '\n') {
      printf("Macro not defined - 4k buffer exceeded\n");
      code = -1;
      return;
    }
  }
}

/* get size of file on remote machine                                        */
void sizecmd(int argc, char *argv[]) {
  if (argc < 2 && !another(&argc, &argv, "filename")) {
    printf("usage: %s filename\n", argv[0]);
    code = -1;
    return;
  }

  command("SIZE %s\r\n", argv[1]);
}

/* get last modification time of file on remote machine                      */
void modtime(int argc, char *argv[]) {
  int overbose;

  if (argc < 2 && !another(&argc, &argv, "filename")) {
    printf("usage: %s filename\n", argv[0]);
    code = -1;
    return;
  }

  overbose = verbose;
    
  if (debug == 0) {
    verbose = -1;
  }
    
  if (command("MDTM %s\r\n", argv[1]) == COMPLETE) {
    int yy, mo, day, hour, min, sec;
	
    sscanf(reply_string, "%*s %04d%02d%02d%02d%02d%02d", &yy, &mo, &day, &hour, &min, &sec);
    /* might want to print this in local time */
    printf("%s\t%02d/%02d/%04d %02d:%02d:%02d GMT\n", argv[1], mo, day, yy, hour, min, sec);
  } else {
    printf("%s\n", reply_string);
  }

  verbose = overbose;
}

/* show status on reomte machine                                             */
void rmtstatus(int argc, char *argv[]) {
  command(argc > 1 ? "STAT %s\r\n" : "STAT\r\n" , argv[1]);
}

/* get file if modtime is more recent than current file                      */
void newer(int argc, char *argv[]) {
  if (!getit(argc, argv, -1, "w")) return;
  printf("Local file \"%s\" is newer than remote file \"%s\"\n", argv[1], argv[2]);
}
