/*

proute.c

Author: Liraz Siri <liraz@bigfoot.com>

Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
                   All rights reserved

Created: Thu Oct  1 03:17:58 GMT 1998

esp_route client. This is really just a quick ssh client hack that
sets up esp routing configuration on any specified esp router.

*/

#include "includes.h"
#include "xmalloc.h"
#include "randoms.h"
#include "ssh.h"
#include "packet.h"
#include "buffer.h"
#include "authfd.h"
#include "readconf.h"
#include "userfile.h"
#include "emulate.h"

#include "esp_proute.h"
#include "logpatch.h"

#ifdef LIBWRAP
#include <tcpd.h>
#include <syslog.h>
#ifdef NEED_SYS_SYSLOG_H
#include <sys/syslog.h>
#endif /* NEED_SYS_SYSLOG_H */
int allow_severity = LOG_INFO;
int deny_severity = LOG_WARNING;
#endif /* LIBWRAP */

/* Random number generator state.  This is initialized in ssh_login, and
   left initialized.  This is used both by the packet module and by various
   other functions. */
RandomState random_state;

/* Flag indicating whether debug mode is on.  This can be set on the
   command line. */
int debug_flag = 0;

/* Flag indicating whether quiet mode is on. */
int quiet_flag = 0;

/* Flag indicating that nothing should be read from stdin.  This can be set
   on the command line. */
int stdin_null_flag = 0;

/* Flag indicating that ssh should fork after authentication.  This is useful
   so that the pasphrase can be entered manually, and then ssh goes to the
   background. */
int fork_after_authentication_flag = 0;

/* General data structure for command line options and options configurable
   in configuration files.  See readconf.h. */
Options options;

/* Name of the host we are connecting to.  This is the name given on the
   command line, or the HostName specified for the user-supplied name
   in a configuration file. */
char *host, *proxy_host, *source_host;
int proxy_proto, host_port;
int source_proto = -1;

int negotiation_only = 0, 
    destroy_rule = 0, 
    destroy_rules = 0;

#ifdef SIGWINCH
/* Flag to indicate that we have received a window change signal which has
   not yet been processed.  This will cause a message indicating the new
   window size to be sent to the server a little later.  This is volatile
   because this is updated in a signal handler. */
volatile int received_window_change_signal = 0;
#endif /* SIGWINCH */

/* Value of argv[0] (set in the main program). */
char *av0;

/* Flag indicating whether we have a valid host private key loaded. */
int host_private_key_loaded = 0;

/* Host private key. */
RSAPrivateKey host_private_key;

/* Original real uid. */
uid_t original_real_uid;

int proto_number(const char *name)
{
 if(!strcmp(name, "icmp")) return IPPROTO_ICMP;
 if(!strcmp(name, "tcp")) return IPPROTO_TCP;
 if(!strcmp(name, "udp")) return IPPROTO_UDP;
 return -1;
}

/* Prints a help message to the user.  This function never returns. */
void usage(void)
{
  fprintf(stderr, "Usage: %s [options] proxy-host [srcport host:[proto]]\n", av0);
  fprintf(stderr, "Options:\n");
#ifdef ICMP_DELIVEY
  fprintf(stderr, "  -d          Destroy the specified rule.\n");
  fprintf(stderr, "              Note that by default we 'set' the rule.\n");
  fprintf(stderr, "  -D          Destroys the remote routing table.\n");
  fprintf(stderr, "              Option does not require [srcport] and host:proto.\n");
  fprintf(stderr, "  -S addr[:proto]  Force the rule source address.\n");
  fprintf(stderr, "                   Normally we lookup our local address.\n");
  fprintf(stderr, "  -N          Only set the negotiation rule.\n");
  fprintf(stderr, "  -w packets  set our packet window size\n");
  fprintf(stderr, "  -u ratio    set our window update ratio\n");
  fprintf(stderr, "  -r usecs    set the round trip time manually (by default we ping)\n");
  fprintf(stderr, "  -t usecs    Set the transmition delay.\n");
  fprintf(stderr, "  -T proto    Select tunnel protocol (default tcp): "
#ifdef TCP_SUPPORT
	"tcp, "
#endif
#ifdef UDP_SUPPORT
	"udp, "
#endif
	"icmp.\n");
	
#endif /* ICMP_DELIVERY */
  fprintf(stderr, "  -a          Disable authentication agent forwarding.\n");
#if defined(KERBEROS_TGT_PASSING) && defined(KRB5)
  fprintf(stderr, "  -k          Disable Kerberos ticket passing.\n");
#endif /* defined(KERBEROS_TGT_PASSING) && defined(KRB5) */
  fprintf(stderr, "  -i file     Identity for RSA authentication (default: ~/.ssh/identity).\n");
  fprintf(stderr, "  -v          Verbose; display verbose debugging messages.\n");
  fprintf(stderr, "  -e char     Set escape character; ``none'' = disable (default: ~).\n");
  fprintf(stderr, "  -c cipher   Select encryption algorithm: "
#ifdef WITH_DES
	  "``des'', "
#endif /* WITH_DES */
#ifdef WITH_ARCFOUR
	  "``arcfour'', "
#endif /* WITH_ARCFOUR */
#ifdef WITH_NONE
	  "``none'', "
#endif /* WITH_NONE */
#ifdef WITH_IDEA
	  "``idea'', "
#endif /* WITH_IDEA */
#ifdef WITH_BLOWFISH
	  "``blowfish'', "
#endif /* WITH_BLOWFISH */
	  "``3des''\n");
  fprintf(stderr, "  -p port     Connect using this communications port\n");
  fprintf(stderr, "  -C          Enable compression.\n");
  fprintf(stderr, "  -o 'option' Process the option as if it was read from a configuration file.\n");
  exit(1);
}

/* Main program for the ssh client. */

int main(int ac, char **av)
{
  int i, opt, optind, type, exit_status, ok, fwd_port, fwd_host_port;
  unsigned int  proute_msg;
  unsigned long transmit_delay;
  int authfd;
  char *optarg, *cp, buf[256];
  Buffer command;
  struct stat st;
  struct passwd *pw, pwcopy;
  int dummy;
  int use_privileged_port = 1;
  uid_t original_effective_uid;
#ifdef SIGWINCH
  struct winsize ws;
#endif /* SIGWINCH */

  /* Save the original real uid.  It will be needed later (uid-swapping may
     clobber the real uid).  */
  original_real_uid = getuid();
  original_effective_uid = geteuid();

  /* Set signals and core limits so that we cannot dump core.  */
  signals_prevent_core();

  pw = getpwuid(original_real_uid);
  if (!pw)
    {
      fprintf(stderr, "You don't exist, go away!\n");
      exit(1);
    }
  
  /* Take a copy of the returned structure. */
  memset(&pwcopy, 0, sizeof(pwcopy));
  pwcopy.pw_name = xstrdup(pw->pw_name);
  pwcopy.pw_passwd = xstrdup(pw->pw_passwd);
  pwcopy.pw_uid = pw->pw_uid;
  pwcopy.pw_gid = pw->pw_gid;
  pwcopy.pw_dir = xstrdup(pw->pw_dir);
  pwcopy.pw_shell = xstrdup(pw->pw_shell);
  pw = &pwcopy;
  
  /* Start reading files as the specified user.  However, if we are not running
     suid root, all access can be done locally, and there is no need to
     initialize explicitly. */
  if (original_real_uid != original_effective_uid)
    {
      userfile_init(pw->pw_name, pw->pw_uid, pw->pw_gid, NULL, NULL);
    }

#ifdef HAVE_UMASK
  /* Set our umask to something reasonable, as some files are created with 
     the default umask.  This will make them world-readable but writable 
     only by the owner, which is ok for all files for which we don't set
     the modes explicitly. */
  umask(022);
#endif /* HAVE_UMASK */
  
  /* Save our own name. */
  av0 = av[0];

#ifdef SOCKS
  /* Initialize SOCKS (the firewall traversal library). */
  SOCKSinit(av0);
#endif /* SOCKS */

  /* Set RSA (actually gmp) memory allocation functions. */
  rsa_set_mp_memory_allocation();

  /* Initialize option structure to indicate that no values have been set. */
  initialize_options(&options);

  /* Parse command-line arguments. */
  host = proxy_host = source_host = NULL;
  proxy_proto = PHANTOM_DEFAULT_IPPROTO;

#ifdef ICMP_DELIVERY
  transmit_delay = PHANTOM_TRANSMIT_DELAY;
#endif

  /* If program name is not one of the standard names, use it as host name. */
  if (strchr(av0, '/'))
    cp = strrchr(av0, '/') + 1;
  else
    cp = av0;


  for (optind = 1; optind < ac; optind++)
    {
      if (av[optind][0] != '-')
	{
          proxy_host = av[optind++];

          if(!destroy_rules) 
          {
	   if(!negotiation_only)
	   {
            if(ac - optind != 2) usage();

 	    host_port = atoi(av[optind++]);
	    if(host_port < 1 || host_port > 65535) 
	    {
	     fprintf(stderr, "Bad tunnel port '%s'.\n", av[optind - 1]);
             exit(0);
	    }

	   }
	   else
	   {
            if(ac - optind != 1) usage();
	   }

	   host = av[optind];
           if(cp = strchr(host, ':')) 
           {
            *cp++ = '\0';
	    if((proxy_proto = proto_number(cp)) < 0) 
            {
	     fprintf(stderr, "Bad tunnel protocol '%s'.\n", cp);
	     exit(0);
            }
	   }
	  } 
          else 
          {
           if(ac - optind > 0) usage();
          }
          break;
	}
      opt = av[optind][1];
      if (!opt) 
	usage();

      if (strchr("wurtTpSeico", opt)) /* options with arguments */
	{
	  optarg = av[optind] + 2;
	  if (strcmp(optarg, "") == 0)
	    {
	      if (optind >= ac - 1)
		usage();
	      optarg = av[++optind];
	    }
	}
      else
	{
	  if (av[optind][2])
	    usage();
	  optarg = NULL;
	}
      switch (opt)
	{
	case 'r':
	    if(atol(optarg) <= 0)
	    {
             fprintf(stderr, "Bad round trip time value `%s'.\n", optarg);
	     exit(0);
	    }
            esp_set_rtt(atol(optarg));
            break;
	    
        case 'w':
	  if(atoi(optarg) <= 0)
	    {
	      fprintf(stderr, "bad window size `%s'\n", optarg);
	      exit(0);
	    }
	  esp_set_window_size(atoi(optarg));
	  break;

        case 'u':
	  if(atoi(optarg) <= 0)
	    {
	      fprintf(stderr, "bad update ratio `%s'\n", optarg);
	      exit(0);
	    }
	  esp_set_sync_ratio(atoi(optarg));
	  break;

        case 'N':
	 negotiation_only = 1;
	 break;

	case 'd':
	 destroy_rule = 1;
	 break;

	case 'D': 
	 if(host) 
          usage();

         destroy_rules = 1;
	 break;	  

 	case 'p':
	 options.port = atoi(optarg);
	 if(options.port < 1 || options.port > 65535) {
	  fprintf(stderr, "Bad communications port '%s'.\n", optarg);
	  exit(0);
	 }
	 break;

	case 'S':
	 source_host = optarg;
	 if(cp = strchr(optarg, ':')) 
         {
	  *cp++ = '\0'; 
	  if((source_proto = proto_number(cp)) < 0) {
	   fprintf(stderr, "Bad tunnel protocol. '%s'.\n", cp);
	   exit(0);
          }
	 }

         break;

	case 'a':
	  options.forward_agent = 0;
	  break;

	case 'k':
	  options.kerberos_tgt_passing = 0;
	  break;

	case 'i':
	  if (userfile_stat(original_real_uid, optarg, &st) < 0)
	    {
	      fprintf(stderr, "Warning: Identity file %s does not exist.\n",
		      optarg);
	      break;
	    }
	  if (options.num_identity_files >= SSH_MAX_IDENTITY_FILES)
	    fatal("Too many identity files specified (max %d)",
		  SSH_MAX_IDENTITY_FILES);
	  options.identity_files[options.num_identity_files++] = 
	    xstrdup(optarg);
	  break;

	case 'v':
	  debug_flag = 1;
#ifdef F_SECURE_COMMERCIAL

#endif /* F_SECURE_COMMERCIAL */
	  fprintf(stderr, "SSH Version %s [%s], protocol version %d.%d.\n",
		  SSH_VERSION, HOSTTYPE, PROTOCOL_MAJOR, PROTOCOL_MINOR);
#ifdef RSAREF
	  fprintf(stderr, "Compiled with RSAREF.\n");
#else /* RSAREF */
	  fprintf(stderr, "Standard version.  Does not use RSAREF.\n");
#endif /* RSAREF */
	  break;

	case 'q':
	  quiet_flag = 1;
	  break;

#ifdef ICMP_DELIVERY
	case 't' : transmit_delay = atol(optarg);
		  if(transmit_delay > 60000000) {
		   fprintf(stderr, "unreasonable delay, %d seconds.",
			   transmit_delay/1000000);
		   exit(1);
		  }
		  break;
	case 'T' : 
          options.esp_proto = proto_number(optarg);
	  if(options.esp_proto == -1) {
            fprintf(stderr, "Unknown tunnel protocol '%s'\n", optarg);
            exit(1);
	   }
	  break;
#endif /* ICMP_DELIVERY */

	case 'e':
	  if (optarg[0] == '^' && optarg[2] == 0 &&
	      (unsigned char)optarg[1] >= 64 && (unsigned char)optarg[1] < 128)
	    options.escape_char = (unsigned char)optarg[1] & 31;
	  else
	    if (strlen(optarg) == 1)
	      options.escape_char = (unsigned char)optarg[0];
	    else
	      if (strcmp(optarg, "none") == 0)
		options.escape_char = -2;
	      else
		{
		  fprintf(stderr, "Bad escape character '%s'.\n", optarg);
		  exit(1);
		}
	  break;

	case 'c':
	  options.cipher = cipher_number(optarg);
	  if (options.cipher == -1)
	    {
	      fprintf(stderr, "Unknown cipher type '%s'\n", optarg);
	      exit(1);
	    }
	  break;

	case 'C':
	  options.compression = 1;
	  break;

	case 'o':
	  dummy = 1;
	  process_config_line(&options, host ? host : "", optarg,
			      "command-line", 0, &dummy);
	  break;

	default:
	  usage();
	}
    }

 /* Check that we got a host name. */
  if (!destroy_rules && !host)
    usage();

  /* Initialize "log" output.  Since we are the client all output actually
     goes to the terminal. */
  log_init(av[0], 1, debug_flag, quiet_flag, SYSLOG_FACILITY_USER);

  /* Read per-user configuration file. */
  sprintf(buf, "%.100s/%s", pw->pw_dir, SSH_USER_CONFFILE);
  read_config_file(original_real_uid, buf, proxy_host, &options);

  /* Read systemwide configuration file. */
  read_config_file(original_real_uid, HOST_CONFIG_FILE, proxy_host, &options);

  /* Fill configuration defaults. */
  fill_default_options(&options);
  if (options.user == NULL)
    {
      options.no_user_given = 1;
/*       options.user = xstrdup(pw->pw_name); */
      options.user = xstrdup("root");
    }

  /* Limit the hostname and username length to 255 characters */
  if (strlen(proxy_host) > 255 || strlen(options.user) > 255)
    fatal("Hostname or username is longer than 255 characters.");

  /* Open a connection to the remote host.  This needs root privileges if
     rhosts_authentication is true.  Note that the random_state is not
     yet used by this call, although a pointer to it is stored, and thus it
     need not be initialized. */
  ok = ssh_connect(proxy_host, options.port, options.connection_attempts,
		   !use_privileged_port,
		   original_real_uid, options.proxy_command, &random_state,  
		   options.esp_proto);

  /* Check if the connection failed, and try "rsh" if appropriate. */
  if (!ok)
    {
      userfile_uninit();
      log_msg("Secure connection to %.100s refused, tunnel must be down",
	      proxy_host);
      exit(1);
      /*NOTREACHED*/
    }

  /* Successful connection. */
  /* Load the host private key in case we will need it later for
     combined rsa-rhosts authentication.  This must be done before
     releasing extra privileges, because the file is only readable by
     root.  */
  if (ok)
    {
      if (load_private_key(geteuid(), HOST_KEY_FILE, "", &host_private_key, 
			   NULL))
	host_private_key_loaded = 1;
    }

  /* Create ~/.ssh directory if it doesn\'t already exist. */
  sprintf(buf, "%.100s/%s", pw->pw_dir, SSH_USER_DIR);
  if (userfile_stat(original_real_uid, buf, &st) < 0)
    if (userfile_mkdir(original_real_uid, buf, 0755) < 0)
      error("Could not create directory '%.200s'.", buf);

  /* Expand ~ in options.identity_files. */
  for (i = 0; i < options.num_identity_files; i++)
    options.identity_files[i] = 
      tilde_expand_filename(options.identity_files[i], original_real_uid);

  /* Expand ~ in known host file names. */
  options.system_hostfile = tilde_expand_filename(options.system_hostfile,
						  original_real_uid);
  options.user_hostfile = tilde_expand_filename(options.user_hostfile,
						original_real_uid);

  /* Log into the remote system.  This never returns if the login fails. 
     Note: this initializes the random state, and leaves it initialized. */
  ssh_login(&random_state, host_private_key_loaded, &host_private_key, 
	    proxy_host, &options, original_real_uid);
  debug("ssh_login just returned, this means we managed to log in");

  /* We no longer need the host private key.  Clear it now. */
  if (host_private_key_loaded)
    rsa_clear_private_key(&host_private_key);

#ifdef ICMP_DELIVERY
 packet_set_max_size(1024 * 24);

 esp_set_delay(transmit_delay);
 packet_start(SSH_CMSG_SYNC_DELAY);
 packet_put_int(transmit_delay);
 packet_send();
 packet_write_wait();

 if(packet_read_stimulate(ICMP_READ_ATTEMPTS, 
			  ICMP_RESPONSE_TIMEOUT) != SSH_SMSG_SUCCESS) {
  packet_get_all();
  log_msg("Warning: server refused to synchronize transmit_delay");
 }

#endif /* ICMP_DELIVERY */


  /* Enable compression if requested. */
  if (options.compression)
    {
      debug("Requesting compression at level %d.", options.compression_level);

      if (options.compression_level < 1 || options.compression_level > 9)
	fatal("Compression level must be from 1 (fast) to 9 (slow, best).");

      /* Send the request. */
      packet_start(SSH_CMSG_REQUEST_COMPRESSION);
      packet_put_int(options.compression_level);
      packet_send();
      packet_write_wait();
      type = packet_read_stimulate(ICMP_READ_ATTEMPTS, ICMP_RESPONSE_TIMEOUT);
      if (type == SSH_SMSG_SUCCESS)
	packet_start_compression(options.compression_level);
      else
	{
	  packet_get_all();
	  log_msg("Warning: Remote host refused compression.");
	}
    }

  if(destroy_rule) proute_msg = SSH_CMSG_ROUTE_REMOVE_RULE;
   else proute_msg = SSH_CMSG_ROUTE_SET_RULE;


  if(destroy_rules)
   esp_proute_remove_all();
   else
   { 
    if(!source_host || source_proto == -1)
     source_proto = options.esp_proto;
    esp_proute_sendop(proute_msg, source_host, host, host_port,
                          proxy_proto, source_proto, 
                          negotiation_only);
   }
  packet_start(SSH_MSG_DISCONNECT);
  packet_put_string(HASTA_LA_VISTA, strlen(HASTA_LA_VISTA));
  packet_send();
  packet_write_poll();
 
  /* Close the connection to the remote host. */
  packet_close();
}

/* OK, this is dumb workaround to a code problem. SSH (the client)
   has no use what so ever for pcap, so I'm officaily replacing the
   routine with a dummy, Instead of rearranging the code to work around
   the design problem. */
int esp_pcap_handlepacket(fd_set *readfds, Buffer *incoming)
{
 return -1;
}

