/* changes:
 * 1.15:
 *   - splint hsftp.c and piper.c
 *   - check if (v)snprintf works correctly, and use if yes
 *   - make drop-root the default
 *   - use initgroups() if available
 *
 * 1.14:
 *   - fix format string security bugs in hsftp.c and piper.c 
 *     (detected by Ulf Härnhammar)
 *     might be exploitable by a malicious server
 *
 * 1.13:
 *   - null terminate lin_str (strncpy will not do that)
 *   - null terminate pwd     (strncpy will not do that)
 *   - limit length of username, hostname arguments 
 *     (buffer overflow detected by Xavier Brouckaert)
 *
 * 1.12:
 *   - allow y/n for Y/N
 *   - fix command line parsing
 *
 * 1.11:
 *   - 'echo $PWD' -> 'pwd' (suggested by Tomi Ollila)
 *   - suggest use of ssh-agent in readme (comment by Thomas Kronseder)
 *   - option to drop root immediately after mlock() on startup
 *	
 * 1.10:
 *   - allow to set the port
 *   - fixed an annoying, irritating, and irrelevant error message 
 *
 * 1.9:
 *   - another OpenSSH fix: use option '-q' with scp
 *
 * 1.8:
 *   - fix annoying 'signal 1' message after successful file transfer
 *   - OpenSSH fix: read password prompt from controlling terminal
 *
 * 1.7:
 *   comment by kromJx:
 *   - newline after invalid command
 *
 *   comments by Alec Habig:
 *   - fix the build
 *   - handle insignificant errors more gracefully
 *
 * 1.6:
 *   comment by Serge Winitzki:
 *   - replace (v)snprintf() by (v)sprintf(), make sure it fits
 *
 *   - fix error with 'lcd':  pw_name -> pw_dir 
 *   - fix missing implementation of 'rm'
 *
 *   suggested by Igor Schein:
 *   - GNU readline support (optional)
 *
 * 1.5:
 *   some comments from Paul Southworth:
 *   - #include <wait.h> -> #include <sys/wait.h> 
 *   - switch off X forwarding (option -x for ssh)
 *   - exit on CONTROL-D
 *
 *   - fflush(stdout) before _exit() in piper.c
 *   - catch "Creating random seed file ..."
 *   - #ifdef TIOCSCTTY
 *   - broken mlock() on hpux
 *   - close pty 
 *
 */

/* 
 * Compiles on HP-UX, but does not work. The problem is in piper.c:
 * the parent process gets no output from the child through the pseudoterminal 
 * (but the child apparently receives input from the parent).
 */


#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <pwd.h>
#include <grp.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <sys/resource.h>

#include "config.h"

#ifdef HAVE_READLINE
#ifdef HAVE_READLINE_READLINE_H
#include <readline/readline.h>
#endif
#ifdef HAVE_READLINE_HISTORY_H
#include <readline/history.h>
#endif

#if defined (HAVE_CURSES_H)
#include <curses.h>
#endif
#if defined (HAVE_TERM_H)
#include <term.h>
#endif

#endif

#ifndef FALSE
#define FALSE (0)
#endif
#ifndef TRUE
#define TRUE (!FALSE)
#endif

#ifndef HAVE_SNPRINTF
#define HAVE_SNPRINTF_BUG
#endif
#ifndef HAVE_VSNPRINTF
#define HAVE_VSNPRINTF_BUG
#endif

/* There are at most 4 args of size MEDBUF plus the format for sprintf,
 * thus we allocate MAXBUF = (5*MEDBUF) for the command string.
 */
#define MEDBUF        512
#define MAXBUF (5*MEDBUF)
#define MINBUF         32

extern int piper (char * command, int mode);

char password[128];
char passphrase[128];

int errnum    =     0;
int opt_debug = FALSE;
int opt_color = TRUE;

int opt_tarflag = TRUE;

int is_suid_root = FALSE;
int no_mlock     = TRUE;

pid_t child_pid = 0;
unsigned int port = 22;  /* SSH port */

char * lin_str = NULL;
char * hom_str = NULL;
char * cwd_str = NULL;
char * cmd_str = NULL;
char * err_str = NULL;


char * tmp_str = NULL;

char * hst_str = NULL;
char * usr_str = NULL;

struct len_str  {
    int    length;               /* size */
    char * thiscol;              /* string */
};


enum mode_color  {
    COL_START  = 0,

    COL_OK     = 1,
    COL_BAD    = 2,
    COL_USR1   = 3,
    COL_USR2   = 4,
    COL_USR3   = 5,
    COL_USR4   = 6,

    COL_DEF, 
    COL_DIR, 
    COL_LINK, 
    COL_FIFO, 
    COL_SOCKET,
    COL_DEV, 
    COL_EXEC,
    COL_END
};

static struct len_str the_colors[] =
{
  { (int) sizeof ("\033[") -1, ("\033[") },       /* start sequence */

  { (int) sizeof ("32") -1,    ("32")    },       /* green   */
  { (int) sizeof ("31") -1,    ("31")    },       /* red     */
  { (int) sizeof ("36") -1,    ("36")    },       /* blue    */
  { (int) sizeof ("34") -1,    ("34")    },       /* cyan    */
  { (int) sizeof ("35") -1,    ("35")    },       /* magenta */
  { (int) sizeof ("01;31") -1, ("01;31") },       /* red     */

  { (int) sizeof ("0") -1,     ("0")     },       /* Default      */
  { (int) sizeof ("01;32") -1, ("01;32") },       /* directory    */
  { (int) sizeof ("01;36") -1, ("01;36") },       /* symlink      */
  { (int) sizeof ("01;31") -1, ("01;31") },       /* fifo         */
  { (int) sizeof ("01;33") -1, ("01;33") },       /* socket       */
  { (int) sizeof ("44;37") -1, ("44;37") },       /* device       */
  { (int) sizeof ("01;35") -1, ("01;35") },       /* exec         */

  { (int) sizeof ("m\0") -1,   ("m\0")   },       /* end   sequence */
};


/* The color indicator 
 */
static int putcolor (struct len_str * col, FILE * stream )
{
  int    i;
  char * ptr;

  ptr = col->thiscol;

  for (i = 0; i < col->length; ++i) {
    fputc(*ptr, stream);
    ++ptr;
  }
  return i; 
}

static void printcol_start (char * c_mode, FILE * stream)
{
  int mode = COL_DEF;

  switch (*c_mode) 
    {
    case 'R':
      mode = COL_BAD;
      break;
    case 'G':
      mode = COL_OK;
      break;
    case 'B':
      mode = COL_USR1;
      break;
    default:
      break;
    }

  putcolor (&the_colors[COL_START], stream);
  putcolor (&the_colors[mode],      stream);
  putcolor (&the_colors[COL_END],   stream);
  return;
}
    

static void printcol_end (FILE * stream)
{
  putcolor (&the_colors[COL_START], stream);
  putcolor (&the_colors[COL_DEF],   stream);
  putcolor (&the_colors[COL_END],   stream);
  return;
}
  

static void printcol (FILE * stream, char * str, char * c_mode, 
		      int start, int end)
{
  register int i;
  int len = (int) strlen(str);

  if (!opt_color)
    {
      fputs(str, stream);
      return;
    }
  if (end <= start || len < end)
    {
      end = len;
    }

  for (i = 0; i < start; ++i)
    fputc (str[i], stream);
  printcol_start (c_mode, stream);
  for (i = start; i < end; ++i)
    fputc (str[i], stream);
  printcol_end (stream);
  if (end != len) 
    fputs(&str[end], stream);
  return;
}

static void sigHandler (int mysignal)
{
  if (mysignal == SIGINT && child_pid != 0)
    {
      fputs ("Signal: Interrupt - kill ssh subprocess\n", stderr);
      kill (child_pid, SIGTERM);
      sleep (1);
      kill (child_pid, SIGKILL);
      (void) waitpid (-1, NULL, WNOHANG|WUNTRACED);
      child_pid = 0;
      return;
    }

  if (mysignal == SIGINT) 
    fputs ("Signal: Interrupt - exit\n", stderr);
  
  memset (password, 0, 128);
  memset (passphrase, 0, 128);
  exit (EXIT_SUCCESS);
}

static
void * my_malloc (size_t size)
{
  void * ptr;

  if ( NULL == (ptr = malloc(size)))
    {
      fprintf (stderr, "Out of memory.");
      memset (password, 0, 128);
      memset (passphrase, 0, 128);
      exit (EXIT_FAILURE);
    }
  return ptr;
}

/* returns fresh memory, should be free'd
 */
static char * my_strdup (const char * str)
{
  char * newstr;
  size_t  len = strlen(str);

  newstr = my_malloc (len + 1);
  strcpy (newstr, str);        /* known to fit */
  return newstr;
}

static char * my_strconcat (const char * arg1, ...)
{
  size_t    length;
  char    * s;
  char    * strnew;
  va_list vl;

  if (arg1 == NULL)
    return NULL;

  length = strlen (arg1) + 1;

  va_start (vl, arg1);
  s = va_arg (vl, char * );
  while (s)
    {
      length = length + strlen (s);
      s = va_arg (vl, char * );
    }
  va_end (vl);

  strnew = my_malloc ( length + 2 );
  strnew[0] = '\0';

  strncpy (strnew, arg1, length + 1);
  strnew[length+1] = '\0';

  va_start (vl, arg1);
  s = va_arg (vl, char * );
  while (s)
    {
      strncat (strnew, s, length + 1);
      strnew[length+1] = '\0';
      s = va_arg (vl, char * );
    }
  va_end (vl);

  return strnew;
}


void print_dbg (const char *fmt, ...)
{
   va_list vl;

   if (!opt_debug)
     return;
   if (!err_str)
     return;

   va_start(vl,fmt);
#ifdef HAVE_VSNPRINTF_BUG
   vsprintf  (err_str, fmt, vl);
#else
   vsnprintf (err_str, MEDBUF+MAXBUF, fmt, vl);
   err_str[MEDBUF+MAXBUF-1] = '\0';
#endif
   va_end(vl);
   fprintf (stderr, "%s", err_str);
   fputs("\n", stderr);
   return;
}

/*
 * The dirz() subroutine from trustfile.c
 *
 * Author information:
 * Matt Bishop
 * Department of Computer Science
 * University of California at Davis
 * Davis, CA  95616-8562
 * phone (916) 752-8060
 * email bishop@cs.ucdavis.edu
 *
 * This code is placed in the public domain.  I do ask that
 * you keep my name associated with it, that you not represent
 * it as written by you, and that you preserve these comments.
 * This software is provided "as is" and without any guarantees
 * of any sort.
 *
 * entry point:
 *	void dirz(char *path)
 * purpose:
 * 	reduce the path name by applying these rules:
 * 	(1) "/./" -> "/" 	(2) "/xxx/../" -> "/"
 * 	(3) "//" -> "/"
 * arguments:
 *	char *path	name to be cleaned up
 * return values:
 *	path		contains the cleaned-up name; note that
 *			this routine never adds chars, so you
 *			won't have overflow problems.  if it
 *			is NULL going in, it's NULL coming out
 * calls:
 *	strcpy		string copy; note we copy a trailing part
 *			of the array to a preceding position, so
 *			we can't have buffer overflow
 * gotchas:
 *	* heaven help you if the path isn't a full path name, because
 *	  this sucker won't; in patricular, "../" will give you the
 *	  wrong answer!
 *
 * Jan 24, 2000 Rainer Wichmann:
 * the doc on strcpy says: "The strings may not overlap, and ...",
 * therefore all strcpy's replaced by pairs of strcpy's with a temporary
 * swap array
 */
static void dirz (char *path)
{
	register char *p = path;/* points to rest of path to clean up */
	register char *q;	/* temp pointer for skipping over stuff */
        char * swp;

	/*
	 * standard error checking
	 */
	if (path == NULL)
		return;

	swp = my_malloc (strlen (path) + 1);

	/*
	 * loop over the path name until everything is checked
	 */
	while(*p){
		/* not one of 3 rules -- skip */
		if (*p != '/'){
			p++;
			continue;
		}
		/* is it "/./" or "/."? */
		if (p[1] == '.' && (p[2] == '/' || !p[2])){
			/* yes -- delete "/." */
		        (void) strcpy(swp, &p[2]);
			(void) strcpy(p, swp);
			/* special case "/." as full path name */
			/* this is "/", not "" 		       */
			if (p == path && !*p){
				*p++ = '/';
				*p = '\0';
			}
		}
		/* is it "//" (multiple /es)? */
		else if (p[1] == '/'){
			/* yes -- skip them */
			for(q = &p[2]; *q == '/'; q++)
				;
                        (void) strcpy(swp, q);
			(void) strcpy(&p[1], swp);
		}
		/* is it "/../" or "/.."? */
		else if (p[1] == '.' && p[2] == '.' && (p[3] == '/' || !p[3])){
			/* yes -- if it's root, delete .. only */
			if (p == path){
			        (void) strcpy(swp, &p[3]);
				(void) strcpy(p, swp);
			}
			else{
				/* nope -- back up over previous component */
				q = p - 1;
				while(q != path && *q != '/')
					q--;
				/* now wipe it out */
                                (void) strcpy(swp, &p[3]);
				(void) strcpy(q, swp);
				p = q;
			}
		}
		else
			p++;
	}
	free (swp);
	return;
}

/* --------------------------------------------------------------------- */

#ifndef IGNORE
#define IGNORE  1
#endif
#ifndef SAVE
#define SAVE    2
#endif
#ifndef RESTORE
#define RESTORE 3
#endif

/* 
 * taken from rxvt and modified for HP-UX.
 * also, drop gid before uid.
 */

#if defined(DROP_ROOT)

void privileges(int mode)
{
  static int i = 0;
  struct passwd *passit;

  if (i == 0)
    {
      setgid(getgid());
      if (0 == setuid(getuid()))
	i = 1;
      else
	{
	  fprintf(stderr, "**PANIC** Could not drop privileges !!!\n");
	  _exit(EXIT_FAILURE);
	}
#ifdef HAVE_INITGROUPS
      if (NULL != (passit = getpwuid(getuid())))
	{
	  initgroups(passit->pw_name, passit->pw_gid);
	}
#endif
    }

  return;
}

#else
      

void privileges(int mode)
{
#if defined(__hpux__)
  static uid_t    euid;
  static gid_t    egid;

  switch (mode) 
    {
    case IGNORE:
      setresgid(-1, getgid(), -1);
      setresuid(-1, getuid(), -1);
      break;
    case SAVE:
      euid = geteuid();
      egid = getegid();
      break;
    case RESTORE:
      setresuid(-1, euid, -1);
      setresgid(-1, egid, -1);
      break;
    }
#elif defined(HAVE_SETEUID)
  static uid_t    euid;
  static gid_t    egid;
  
  switch (mode) 
    {
    case IGNORE:
      setegid(getgid());
      seteuid(getuid());
      break;
    case SAVE:
      euid = geteuid();
      egid = getegid();
      break;
    case RESTORE:
      seteuid(euid);
      setegid(egid);
      break;
    }
#else
  switch (mode) 
    {
    case IGNORE:
      setgid(getgid());
      setuid(getuid());
      break;
    case SAVE:
      break;
    case RESTORE:
      break;
    }
#endif
  if (mode == IGNORE &&  (getuid() !=geteuid()) )
    {
      fprintf(stderr, "**PANIC** Could not drop privileges !!!\n");
      _exit(EXIT_FAILURE);
    }
  return;
}
#endif

/* --------------------------------------------------------------------- */

static void cmd_cwd ()
{
  /* 'echo $PWD' -> pwd suggested by Tomi Ollila
   */
#ifdef HAVE_SNPRINTF_BUG
  sprintf (cmd_str, "ssh -p %u -x -l %s %s 'pwd'", 
	   port, usr_str, hst_str);
#else
  snprintf (cmd_str, MAXBUF, "ssh -p %u -x -l %s %s 'pwd'", 
	   port, usr_str, hst_str);
  cmd_str[MAXBUF-1] = '\0';
#endif
  print_dbg("Command: %s", cmd_str);
  piper (cmd_str, 0);
  strncpy (cwd_str, hom_str, MEDBUF);
  cwd_str[MEDBUF-1] = '\0';
  return;
}

static void cmd_cd (char * str)
{
  char * p;
  size_t len;

  len = strlen(str);
  if (str[len-1] == '\n') str[len-1] = '\0';
  p = &str[2];
  while (*p == ' ') ++p;

  /* no argument
   */
  if (*p == '\n' || *p == '\0')
    {
      strcpy(cwd_str, hom_str);
      return;
    }
  else if (*p == '/')
    {
      strncpy (cwd_str, p, MEDBUF-1);
    }
  else
    {
      tmp_str = my_strconcat (cwd_str, "/", p, NULL);
      strncpy (cwd_str, tmp_str, MEDBUF-1);
      if (tmp_str) 
	free (tmp_str);
    } 
  cwd_str[MEDBUF-1] = '\0';

  dirz (cwd_str);
  return;
}
  
  

static void cmd_dir (char * str)
{
  size_t len;
  char * p;

  len = strlen(str);
  if (str[len-1] == '\n') str[len-1] = '\0';
  p = &str[3];
  while (*p == ' ') ++p;

  /* no argument
   */
#ifdef HAVE_SNPRINTF_BUG
  if (*p == '\n' || *p == '\0')
      sprintf (cmd_str, "ssh -p %u -x -l %s %s 'ls %s'", 
	       port, usr_str, hst_str, cwd_str);
  else
    sprintf (cmd_str, "ssh -p %u -x -l %s %s 'cd %s && ls %s'",
	     port, usr_str, hst_str, cwd_str, p);
#else
  if (*p == '\n' || *p == '\0')
      snprintf (cmd_str, MAXBUF, "ssh -p %u -x -l %s %s 'ls %s'", 
	       port, usr_str, hst_str, cwd_str);
  else
    snprintf (cmd_str, MAXBUF, "ssh -p %u -x -l %s %s 'cd %s && ls %s'",
	     port, usr_str, hst_str, cwd_str, p);
  cmd_str[MAXBUF-1] = '\0';
#endif

  print_dbg("Command: %s", cmd_str);
  piper (cmd_str, 1);

  return;
}

static void helpme()
{
  printf(" put         -- upload file(s)|directory(s) to remote host\n");
  printf(" get         -- download file(s)|directory(s) from remote host\n");
  printf(" mput, mget  -- same as 'put', 'get'\n\n");

  printf(" dir, ls     -- list remote directory (* ls)\n");
  printf(" ldir        -- list local directory  (* ls)\n\n");

  printf(" cd          -- change remote directory\n");
  printf(" lcd         -- change local directory\n");
  printf(" pwd         -- print working directories\n\n");

  printf(" chmod       -- 'chmod' in remote directory (* chmod)\n");
  printf(" mkdir       -- 'mkdir' in remote directory (* mkdir)\n");
  printf(" del, rm     -- 'rm'    in remote directory (* rm)\n");
  printf(" exec        -- execute an arbitrary Unix command on remote host\n\n");

  printf(" debug       -- be more verbose\n");
  printf(" ?, help     -- this help message\n\n");

  printf(" quit, bye   -- quit the program\n\n");

  printf(" (* command) options for 'command' are recognized.\n");
  return;
}


/* print usage notice, exit successfully / failure (mode == TRUE/FALSE)
 */
void usage(int mode)
{
  printcol (stdout, " Usage:", "G", 0, 0);
  printf   ("   %s [options] [-l username] [-p port] hostname\n", PACKAGE);
  printcol (stdout, " Options:", "G", 0, 0);
  printf   (" -v  verbose\n");
  fprintf  (stdout, "          -h  help\n\n");

  printf   (" %s is an ftp emulator that provides the look-and-feel of an "\
	    "ftp session,\n", 
	    PACKAGE);
  printf   (" but uses ");
  printcol (stdout, "ssh", "B", 0, 0);
  printf   (" to transport commands and data.  %s executes\n", PACKAGE);
  printcol (stdout, " UNIX commands", "B", 0, 0);
  printf   (" on the remote host, and thus will fail on non-unix "\
	    "remote hosts.\n");
  printf   (" The password (if needed) is encrypted by ssh before "\
	    "transmission.\n\n");
  printcol (stdout, " Caveats: ", "R", 0, 0);
  printf   ("\n - For speed, %s doesn't check whether a 'cd' on the "\
	    "remote host\n", PACKAGE);
  printf   ("   is valid (i.e. whether the remote directory exists.)\n");
  printf   (" - get/put ia always recursive and always overwrites existing "\
	    "files.\n");

  if (is_suid_root == FALSE)
    {
      printf   (" - This program is not installed SUID root, and therefore "\
		"lacks the power to\n");
      printf   ("   protect its memory space. If you have typed "\
		"in a password and leave\n");
      printf   ("   the program idle "\
		"for some time, the password may get ");
      printcol (stdout, "paged out", "R", 0, 0);
      printf   (" to your\n");
      printf   ("   swap partition (except if you are root herself). "\
		"You have been warned.\n\n");
    }


  printf(" This is version %s of %s, compiled on %s.\n", 
	 VERSION, PACKAGE, __DATE__);
#if defined(HAVE_READLINE) || defined(OPEN_SSH)
  printf(" Compile options: ");
#if defined(HAVE_READLINE) 
  printcol(stdout, "--with-readline ", "B", 0, 0);
#endif
#if defined(OPEN_SSH) 
  printcol(stdout, "--with-openssh ", "B", 0, 0);
#endif
#if defined(DROP_ROOT) 
  printcol(stdout, "--with-drop-root", "B", 0, 0);
#endif
  printf("\n");
#endif
  printf   (" Copyright (c) 2000 Rainer Wichmann "\
	    "<rwichmann@la-samhna.de>.\n");
  printf(" Distributed under the terms of the GNU General Public License.\n");

  memset (password, 0, 128);
  memset (passphrase, 0, 128);
  if (mode) 
    exit (EXIT_SUCCESS);
  exit (EXIT_FAILURE);
}

/* fetch the password from the user, store it, and zero the
 * static object returned by getpass()
 */
void get_pw()
{
  char * p;

  if (no_mlock == TRUE)
    fprintf (stderr, "Using insecure memory.\n");

  password[0] = '\0';

  do {
    p = getpass ("Enter password: ");
    strncpy (password, p, 128);
    password[127] = '\0';
  } while (strlen(password) < 1);

  memset (p, 0, strlen(password));
  password[127] = '\0';
  return;
}

/* fetch the passphrase from the user, store it, and zero the
 * static object returned by getpass()
 */
void get_pp(char * str)
{
  char * p;

  if (no_mlock == TRUE)
    fprintf (stderr, "Using insecure memory.\n");

  passphrase[0] = '\0';

  do {
    p = getpass (str);
    strncpy (passphrase, p, 128);
    passphrase[127] = '\0';
  } while (strlen(passphrase) < 1);

  memset (p, 0, strlen(passphrase));
  passphrase[127] = '\0';
  return;
}

int is_white_space (char c)
{
  if (c == ' '  || c == '\n' || c == '\t' || 
      c == '\f' || c == '\r' || c == '\v')
    return ( (1 == 1) );
  else
    return ( (1 == 0) );
}

/* Strip whitespace from the start and end of STRING. 
 * From the readline manual.
 */
void stripwhite (char * string)
{
  register size_t i = 0;

  while (is_white_space (string[i]))
    i++;

  if (i)
    strcpy (string, string + i);

  i = strlen (string) - 1;

  while (i > 0 && is_white_space (string[i]))
    i--;

  string[++i] = '\0';
}

#if 0

/* Straight from the readline examples directory, and does not work.
 * Output from gdb:
 *
 * Program received signal SIGSEGV, Segmentation fault.
 * 0x4005b38d in __libc_free (mem=0x805df6a) at malloc.c:2861
 * malloc.c:2861: Datei oder Verzeichnis nicht gefunden. ( <- file not found)
 * (gdb) bt
 * #0  0x4005b38d in __libc_free (mem=0x805df6a) at malloc.c:2861
 * #1  0x8052101 in rl_complete_internal ()
 * #2  0x8050c67 in rl_complete ()
 * #3  0x804c744 in _rl_dispatch ()
 * #4  0x804c552 in readline_internal_char ()
 * #5  0x804c5c1 in readline_internal_charloop ()
 * #6  0x804c5dd in readline_internal ()
 * #7  0x804c2ac in readline ()
 * #8  0x804ac02 in main (argc=2, argv=0xbffff2bc) at hsftp.c:1051
 *
 */
typedef struct {
  char *name;                   /* User printable name of the function. */
} COMMAND;

COMMAND commands[] = {
  { "put"   },
  { "get"   },
  { "mput"  },
  { "mget"  },
  { "dir"   },
  { "ls"    },
  { "ldir"  },
  { "cd"    },
  { "lcd"   },
  { "pwd"   },
  { "chmod" },
  { "mkdir" },
  { "del"   },
  { "rm"    },
  { "exec"  },
  { "debug" },
  { "help"  },
  { "?"     },
  { "quit"  },
  { "bye"   },
  { "exit"  },
  { NULL    },
};

/* **************************************************************** */
/*                                                                  */
/*                  Interface to Readline Completion                */
/*                                                                  */
/* **************************************************************** */

char *command_generator ();
char **fileman_completion ();

/* Tell the GNU Readline library how to complete.  We want to try to complete
   on command names if this is the first word in the line, or on filenames
   if not. */
void initialize_readline ()
{
  char **fileman_completion ();

  /* Allow conditional parsing of the ~/.inputrc file. */
  rl_readline_name = "hsftp";

  /* Tell the completer that we want a crack first. */
  rl_attempted_completion_function = (CPPFunction *)fileman_completion;
}

/* Attempt to complete on the contents of TEXT.  START and END show the
   region of TEXT that contains the word to complete.  We can use the
   entire line in case we want to do some simple parsing.  Return the
   array of matches, or NULL if there aren't any. */
char **
fileman_completion (char * text, int start, int end)
{
  char **matches;
  char *command_generator ();

  matches = (char **)NULL;

  /* If this word is at the start of the line, then it is a command
     to complete.  Otherwise it is the name of a file in the current
     directory. */
  if (start == 0)
    matches = completion_matches (text, command_generator);

  return (matches);
}

/* Generator function for command completion.  STATE lets us know whether
   to start from scratch; without any state (i.e. STATE == 0), then we
   start at the top of the list. */
char *
command_generator (char * text, int state)
{
  static int list_index, len;
  char *name;

  /* If this is a new word to complete, initialize now.  This includes
     saving the length of TEXT for efficiency, and initializing the index
     variable to 0. */
  if (!state)
    {
      list_index = 0;
      len = strlen (text);
    }

  /* Return the next name which partially matches from the command list. */
  while ( (name = commands[list_index].name) )
    {
      list_index++;

      if (strncmp (name, text, len) == 0)
        return (name);
    }

  /* If no names matched, then return NULL. */
  return ((char *)NULL);
}
/* HAVE_READLINE */
#endif

int main (int argc, char * argv[])
{
  struct passwd * pwent;
  char * p;
  char * store;

#ifdef HAVE_READLINE
  char * line_read = NULL;
#endif

  char * prompt_str = PACKAGE"> ";

  char * pwd;
  struct rlimit limits;

  struct sigaction act; 


  /* ----------------------------------------------------------------- */


  /* are we root ? this is just for deciding whether the no-suid
   * warning in the usage message should be printed
   */
  if (0 == geteuid())
    is_suid_root = TRUE;


  /* lock memory for the password, check for success 
   * (no_mlock == TRUE will trigger a warning when user types in password)
   *
   * only root may use mlock() to secure the memory, thus the password
   * may get swapped out to disk when:
   * (i)  the program is not SUID root, and
   * (ii) the user is not root
   */
#if (!defined __hpux__) && (defined HAVE_MLOCK)
  if (0 ==  mlock (password, 128) && 0 ==  mlock (passphrase, 128))  
    no_mlock = FALSE;
#elif (defined __hpux__) && (defined HAVE_MLOCK)
  /* mlock() is broken on HP_UX 
   */
  if (is_suid_root == TRUE)
    {
      if (0 ==  mlock (password, 128) && 0 ==  mlock (passphrase, 128))  
	no_mlock = FALSE;
    }
#endif

  /* We need root privileges for the pseudoterminal, so we drop only
   * temporarily.
   */
  privileges (SAVE);    /* save the effective uid                */
  privileges (IGNORE);  /* set the effective uid to the real uid */

  /* allow no core dumps - the password may be in them
   * (note: the preceding setuid() will disallow core dumps
   * only if the real uid was different from the effective uid)
   */
  limits.rlim_cur = 0;
  limits.rlim_max = 0;
#ifdef RLIMIT_CORE
  setrlimit (RLIMIT_CORE,    &limits); /* this works ok (tested) */
#endif

  /* ----------------------------------------------------------------- */

  /* get the local working directory
   */
  pwd = my_malloc (MEDBUF);
  if (NULL == getcwd(pwd, MEDBUF-1))
    {
      errnum = errno;
      fprintf(stderr, "Cannot get local directory: %s\n", strerror(errnum));
    }

  password[0] = '\0';
  passphrase[0] = '\0';

  /* command line arguments
   */
  if (argc == 1)
    usage(FALSE);


  /* There are at most 4 args of size MEDBUF plus the format for sprintf,
   * thus we allocate MAXBUF = (5*MEDBUF) for the command string.
   */
  cmd_str = my_malloc (MAXBUF);
  err_str = my_malloc (MEDBUF+MAXBUF);

  hom_str = my_malloc (MEDBUF);
  cwd_str = my_malloc (MEDBUF);
  lin_str = my_malloc (MEDBUF);

  /* parse arguments
   */

  /* drop program name 
   */
  --argc; ++argv;

  while (argc > 0 && argv[0][0] == '-')
    {
      switch (argv[0][1])
	{
	case 'l':
	  --argc; ++argv;
	  if (argc < 1)
	    usage(FALSE);
	  if (argv[0][0] == '-')
	    usage(FALSE);
	  if (64 < strlen(argv[0]))
	    {
	      fprintf(stderr, "** Error ** Username too long\n");
	      usage(FALSE);
	    }
	  usr_str = my_strdup (argv[0]);
	  break;
	case 'p':
	  --argc; ++argv;
	  if (argc < 1)
	    usage(FALSE);
	  if (argv[0][0] == '-')
	    usage(FALSE);
	  port = (unsigned int) (atoi (argv[0]));
	  break;
	case 'h':
	  usage(TRUE);
	  break;
	case 'v':
	  opt_debug = TRUE;
	  break;
	default:
	  usage(FALSE);
	  break;
	}
      --argc; ++argv;
    }
  if (argc == 1)
    {
      if (128 < strlen(argv[0]))
	{
	  fprintf(stderr, "** Error ** Hostname too long\n");
	  usage(FALSE);
	}
      hst_str = my_strdup (argv[0]);
    }
  else
    usage(FALSE);

  if (usr_str == NULL)
    {
      pwent = getpwuid (geteuid());
      if (!pwent)
	{
	  print_dbg ("No password file entry for %d\n", geteuid());
	  memset (password, 0, 128);
	  memset (passphrase, 0, 128);
	  exit (EXIT_FAILURE);
	}
      usr_str = my_strdup (pwent->pw_name);
    }
  print_dbg ("Username <%s>, remote host <%s>", usr_str,  hst_str);

  /* install signal handler
   */
  act.sa_handler = &sigHandler;
  act.sa_flags = 0;                     /* init sa_flags           */
  sigaction(SIGINT,  &act, NULL);
  

  /* remote working directory
   */
  hom_str[0] = '\0';
  cwd_str[0] = '\0';
  cmd_cwd();

  if (strlen(hom_str) < 1 || strlen(cwd_str) < 1)
    {
      fprintf(stderr, "** ERROR **: Could not get remote home directory.\n");
      memset (password, 0, 128);
      memset (passphrase, 0, 128);
      exit (EXIT_FAILURE);
    }

  /* debug output
   */
  fprintf (stdout, "Welcome to %s.\n", PACKAGE);
  fprintf (stdout, "Current remote directory is <%s>\n", cwd_str);
  fprintf (stdout, "Current local  directory is <%s>\n", pwd);
  fprintf (stdout, "Use '?' or 'help' to get a list of commands.\n");
  fflush  (stdout);

#if 0
  initialize_readline ();
#endif

  /* main loop
   */
  while (TRUE)
    {

#ifdef HAVE_READLINE
      if (line_read != NULL)
	{
	  free (line_read);
	  line_read = NULL;
	}

      if (NULL == (line_read = readline (prompt_str)))
	break;

      stripwhite (line_read);

      if (line_read && *line_read)
	add_history (line_read);

      strncpy (lin_str, line_read, MEDBUF-1);
      lin_str[MEDBUF-1] = '\0';                 /* fix: null terminate */
      free(line_read);
      line_read = NULL;
#else
      printf (prompt_str);
      /* fgets() reads at most MEDBUF-2 chars and null terminates */
      if (NULL == (fgets  (lin_str, MEDBUF-1, stdin)))
	  break;
      stripwhite (lin_str);
#endif

      if (0 == strncmp (lin_str, "pwd",   3))
	{
	  printf ("Current remote directory is <%s>\n", cwd_str);
	  printf ("Current local  directory is <%s>\n", pwd);
	  continue;
	} 

      if (0 == strncmp (lin_str, "put",   3) ||
	  0 == strncmp (lin_str, "mput",  3))
	{
	  if (lin_str[strlen(lin_str)-1] == '\n')
	    lin_str[strlen(lin_str)-1] = '\0';
	  p = &lin_str[3]; while (*p == ' ') ++p;
	  
	  if (*p == '\0')
	    continue;

	  /* Voodoo programming. It works -- don't ask why
	   * (actually, just a guess after stracing scp).
	   */
#ifdef OPEN_SSH
#ifdef HAVE_SNPRINTF_BUG
	  sprintf(cmd_str, 
		  "scp -P %u -r -q %s %s@%s:%s",
		  port, p, usr_str, hst_str, cwd_str);
#else
	  snprintf(cmd_str, MAXBUF,
		  "scp -P %u -r -q %s %s@%s:%s",
		  port, p, usr_str, hst_str, cwd_str);
	  cmd_str[MAXBUF-1] = '\0';
#endif
#else
#ifdef HAVE_SNPRINTF_BUG
	  sprintf(cmd_str, 
		  "scp -P %u -r %s %s@%s:%s",
		  port, p, usr_str, hst_str, cwd_str);
#else
	  snprintf(cmd_str, MAXBUF,
		  "scp -P %u -r %s %s@%s:%s",
		  port, p, usr_str, hst_str, cwd_str);
	  cmd_str[MAXBUF-1] = '\0';
#endif
#endif
	  print_dbg("Command: %s", cmd_str);
	  piper (cmd_str, 2);
	  
	  /*
	    step_one:
	    if (opt_tarflag)
	    sprintf(cmd_str, 
	    "tar cfb - 20 %s | ssh -x -l %s %s '(cd %s; tar xvBfb - 20)'",
	    p, usr_str, hst_str, cwd_str);
	    else
	    sprintf(cmd_str, 
	    "scp -r %s %s@%s:%s",
	    p, usr_str, hst_str, cwd_str);
	    print_dbg("Command: %s", cmd_str);
	    
	    if (-1 == piper (cmd_str, 2)) 
	    goto step_one;
	  */

	  printf ("Finished\n");
	  continue;
	}


      if (0 == strncmp (lin_str, "get",   3) ||
	  0 == strncmp (lin_str, "mget",  4))
	{
	  if (lin_str[strlen(lin_str)-1] == '\n')
	    lin_str[strlen(lin_str)-1] = '\0';
	  p = &lin_str[3]; while (*p == ' ') ++p;
	  
	  if (*p == '\0')
	    continue;

#ifdef HAVE_SNPRINTF_BUG
	  sprintf(cmd_str, 
		  "ssh -p %u -x -l %s %s 'cd %s && tar cfb - 20 %s ' | tar xvBfb - 20",
		  port, usr_str, hst_str, cwd_str, p);
#else
	  snprintf(cmd_str, MAXBUF,
		  "ssh -p %u -x -l %s %s 'cd %s && tar cfb - 20 %s ' | tar xvBfb - 20",
		  port, usr_str, hst_str, cwd_str, p);
	  cmd_str[MAXBUF-1] = '\0';
#endif
	  print_dbg("Command: %s", cmd_str);
	  piper (cmd_str, 2);
	  printf ("Finished\n");
	  continue;
	}

      if (0 == strncmp (lin_str, "del",   3) ||
	  0 == strncmp (lin_str, "rm",    2))
	{
	  if (lin_str[strlen(lin_str)-1] == '\n')
	    lin_str[strlen(lin_str)-1] = '\0';
	  p = &lin_str[3]; while (*p == ' ') ++p;
	  
	  if (*p == '\0')
	    continue;

#ifdef HAVE_SNPRINTF_BUG
	  sprintf(cmd_str, 
		  "ssh -p %u -x -l %s %s 'cd %s && rm %s '",
		  port, usr_str, hst_str, cwd_str, p);
#else
	  snprintf(cmd_str, MAXBUF, 
		  "ssh -p %u -x -l %s %s 'cd %s && rm %s '",
		  port, usr_str, hst_str, cwd_str, p);
	  cmd_str[MAXBUF-1] = '\0';
#endif

	  print_dbg("Command: %s", cmd_str);
	  piper (cmd_str, 2);
	  continue;
	}

      if (0 == strncmp (lin_str, "mkdir",   5) || 
	  0 == strncmp (lin_str, "chmod",   5))
	{
	  if (lin_str[strlen(lin_str)-1] == '\n')
	    lin_str[strlen(lin_str)-1] = '\0';
	  
#ifdef HAVE_SNPRINTF_BUG
	  sprintf(cmd_str, 
		  "ssh -p %u -x -l %s %s 'cd %s && %s '",
		  port, usr_str, hst_str, cwd_str, lin_str);
#else
	  snprintf(cmd_str, MAXBUF,
		  "ssh -p %u -x -l %s %s 'cd %s && %s '",
		  port, usr_str, hst_str, cwd_str, lin_str);
	  cmd_str[MAXBUF-1] = '\0';
#endif
	  print_dbg("Command: %s", cmd_str);
	  piper (cmd_str, 2);
	  continue;
	}

      if (0 == strncmp (lin_str, "exec", 4))
	{
	  if (lin_str[strlen(lin_str)-1] == '\n')
	    lin_str[strlen(lin_str)-1] = '\0';
	  
#ifdef HAVE_SNPRINTF_BUG
	  sprintf(cmd_str, 
		  "ssh -p %u -x -l %s %s 'cd %s && %s '",
		  port, usr_str, hst_str, cwd_str, &lin_str[4]);
#else
	  snprintf(cmd_str, MAXBUF,
		  "ssh -p %u -x -l %s %s 'cd %s && %s '",
		  port, usr_str, hst_str, cwd_str, &lin_str[4]);
	  cmd_str[MAXBUF-1] = '\0';
#endif
	  print_dbg("Command: %s", cmd_str);
	  piper (cmd_str, 2);
	  continue;
	}

      if (0 == strncmp (lin_str, "dir",  3) ||
	  0 == strncmp (lin_str, "ls",   2))
	{
	  cmd_dir(lin_str);
	  continue;
	}

      if (0 == strncmp (lin_str, "cd",   2))
	{
	  cmd_cd (lin_str);
	  print_dbg ("Current remote directory is <%s>", cwd_str);
	  print_dbg ("Current local  directory is <%s>", pwd);
	  continue;
	}

      if (0 == strncmp (lin_str, "ldir", 4))
	{
#ifdef HAVE_SNPRINTF_BUG
	  sprintf (cmd_str, "ls %s", &lin_str[4]);
#else
	  snprintf (cmd_str, MAXBUF, "ls %s", &lin_str[4]);
	  cmd_str[MAXBUF-1] = '\0';
#endif
	  system (cmd_str); /* LOCAL CALL */
	  continue;
	}

      if (0 == strncmp (lin_str, "lcd",  3))
	{
	  store = my_strdup (pwd);
	  if (lin_str[strlen(lin_str)-1] == '\n')
	    lin_str[strlen(lin_str)-1] = '\0';
	  p = &lin_str[3]; while (*p == ' ') ++p;
	  if (*p == '\0')
	    {
	      pwent = getpwuid (geteuid());
	      strncpy (pwd, pwent->pw_dir, MEDBUF-1); /* fix: null terminate */
	      pwd[MEDBUF-1] = '\0';
	    }
	  else if (*p == '/')
	    {
	      strncpy (pwd, p, MEDBUF-1);
	      pwd[MEDBUF-1] = '\0';
	    }
	  else
	    {
	      tmp_str = my_strconcat (pwd, "/", p, NULL);
	      strncpy (pwd, tmp_str, MEDBUF-1);
	      pwd[MEDBUF-1] = '\0';
	      if (tmp_str) free (tmp_str);
	    }
	  dirz (pwd);
	  if (0 != chdir (pwd))
	    {
	      errnum = errno;
	      fprintf (stderr, "cd %s: %s\n", pwd, strerror(errnum));
	      strcpy (pwd, store);     
	    }
	  print_dbg ("Current remote directory is <%s>", cwd_str);
	  print_dbg ("Current local  directory is <%s>", pwd);
	  free (store);
	  continue;
	}

      if (0 == strncmp (lin_str, "debug",5))
	{
	  opt_debug = TRUE;
	  continue;
	}

      if (0 == strncmp (lin_str, "?",    1) || 
	  0 == strncmp (lin_str, "help", 4))
	{
	  helpme ();
	  continue;
	}

      if (0 == strncmp (lin_str, "bye",  3) ||
	  0 == strncmp (lin_str, "quit", 4) ||
	  0 == strncmp (lin_str, "exit", 4))
	break;

      fprintf (stderr, "Unrecognized command: %s\n", lin_str);
    }

  memset (password, 0, 128);
  memset (passphrase, 0, 128);
  return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1