/* sshdos.c       Copyright (c) 2000-2003 Nagy Daniel
 *
 * $Date: 2003/11/12 10:57:10 $
 * $Revision: 1.4 $
 *
 * This module is the main part:
 *  - command line parsing
 *  - client loop
 *  - running remote command or interactive session
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
 */

#include <stdio.h>
#include <conio.h>
#include <string.h>
#if defined (__WATCOMC__)
 #include <time.h>
 #include <dos.h> 
#elif defined (__DJGPP__)
 #include <go32.h>
 #include <dpmi.h>
 #include <unistd.h>
#endif

#include "tcp.h"
#include "ssh.h"
#include "cipher.h"
#include "transprt.h"
#include "version.h"
#include "xmalloc.h"
#include "config.h"
#include "common.h"
#include "negotiat.h"
#include "vidio.h"
#include "vttio.h"
#include "keyio.h"
#include "keymap.h"


/* external functions */
SendFuncPtr SendPacket;
extern SessKeyInitPtr SessKeyInit;
extern CryptPtr EncryptPacket;
extern CryptPtr DecryptPacket;


/* external structures, variables */
extern Packet pktin;    /* incoming SSH1 packet */
extern unsigned short statusline;
extern unsigned columns;  /* Columns on logical terminal screen */
extern unsigned lines;    /* Lines on logical terminal screen */
extern unsigned short vidmode;

/* global variables */
Config GlobalConfig;            /* global configuration structure */
unsigned short Configuration = 0;       /* Configuration bits */

/* local variables */
struct vidmod{
 char *mode;
 unsigned short vidmode;
};
static struct vidmod modes[]={ {"80x25", 3},
                               {"80x60", 0x108},
                               {"132x25", 0x109},
                               {"132x50", 0x10b} };
static char *command = NULL;
static short tty = 0;
static char *RemoteHost = NULL;
static char *UserName = NULL;
static char *PassWord = NULL;
static char *KeyFile = NULL;
static char *term;
static unsigned short RemotePort = SSH_PORT;
static unsigned short Keepalives = 0;
static FILE *LogFile = NULL;

#if defined (__WATCOMC__)
 volatile int timer = 0;       /* increased by timer interrupt */
 void (__interrupt __far *oldhandler)(); /* for old timer interrupt */

 void __interrupt __far keepalive(void){
   timer++;
   _chain_intr(oldhandler);
 }
#elif defined (__DJGPP__)
/*
 * we don't want DJGPP to extend wildcards, so include
 * this dummy function
 */
 char **__crt0_glob_function (char *arg)
 {
   return 0;
 }

/*
 * Some keepalive support tricks. In DJGPP, we must ensure,
 * that interrupt handlers remain always in memory, not
 * swapped out to disk. That's why we need end_keepalive
 * dummy function. Also note, that we don't need to call
 * the old handler under DJGPP.
 */
 unsigned long timer = 0;		/* increased by timer interrupt */
 _go32_dpmi_seginfo oldhandler, my_handler;	/* for old and new timer interrupt */

 /* Timer handler */
 void keepalive(void)
 {
   timer++;
 }
 void end_keepalive(void) {}	/* dummy, not to swap out keepalive func. */
#endif

/*
 * Initialize global variables
 */
static void Config_Init(void){
   term = "xterm"; /* default is "xterm" */
   GlobalConfig.debugfile = NULL;
   GlobalConfig.brailab = NULL;
   GlobalConfig.CipherType = SSH_CIPHER_BLOWFISH;
   SendPacket = SendSSHPacket;
   SessKeyInit = blowfish_sesskey;
   EncryptPacket = blowfish_encrypt_blk;
   DecryptPacket = blowfish_decrypt_blk;
}

/*
 * Allocate a pseudo terminal
 */
static void SSH1_Request_Pty(char *termtype)
{
   if(Configuration & VERBOSE_MODE)
        puts("Requesting PTY");
   SSH_pkt_init(SSH_CMSG_REQUEST_PTY);
   SSH_putstring(termtype);
   SSH_putuint32(lines - statusline);
   SSH_putuint32(columns);
   SSH_putuint32(0);
   SSH_putuint32(0);
   SSH_putbool(0);
   SSH_pkt_send();

   SSH_pkt_Read(SSH_SMSG_SUCCESS);
}

/*
 * Start interactive shell or run command
 */
static void SSH1_Start_Shell_Or_Command(void)
{
   if(command != NULL && command[0] != '\0') {
      if(Configuration & VERBOSE_MODE)
           puts("Running command");
      SSH_pkt_init(SSH_CMSG_EXEC_CMD);
      SSH_putstring(command);
      xfree(command);
   } else {
      if(Configuration & VERBOSE_MODE)
           puts("Running shell");
      SSH_pkt_init(SSH_CMSG_EXEC_SHELL);
   }
   SSH_pkt_send();

}

/*
 * Client loop. This runs when the user successfully logged in,
 * until SSH connection is terminated
 */
static short dosession(void)
{
unsigned short i;

   do{
        /* send keepalive SSH_MSG_IGNORE packet if configured */
        if( timer > Keepalives ){
           SSH_pkt_init(SSH_MSG_IGNORE);
           SSH_putstring("keepalive");
           SSH_pkt_send();
           timer = 0;
        } /* if */

        if(!tcp_tick(&GlobalConfig.s)){ /* TCP wait */
           puts("Remote host closed connection");
           return(EXIT_SSH);
        }
        while(ConChk()) /* examine STDIN */
           DoKey();
   } while(!sock_dataready(&GlobalConfig.s));

   SbkSetPage(-1);

   SSH_pkt_Read(0); /* uncrypt and get valuable data */

   switch(pktin.type){
        case SSH_SMSG_STDOUT_DATA:
                for(i = 0; i < pktin.length - 4; i++){
                   if(tty)
                        ConOut(pktin.body[i+4]);
                   else
                        putchar(pktin.body[i+4]);
                }
                if(LogFile)
                   fwrite(pktin.body + 4, 1, pktin.length - 4, LogFile);
                break;

        case SSH_SMSG_STDERR_DATA:
                pktin.body[pktin.length] = '\0';
                cputs(pktin.body + 4);
                break;

        case SSH_SMSG_EXITSTATUS:
                SSH_pkt_init(SSH_CMSG_EXIT_CONFIRMATION);
                SSH_pkt_send();
                return(EXIT_SSH);

        default:
                cprintf("Invalid packet received. Type: %d\n\r",pktin.type);
                break;
   } /* switch */
   return(0);

}

/*
 * Get command line arguments
 */
static void getargs(int argc, char *argv[])
{
unsigned short n, i, j, len;
char *s;
#if defined (__386__) || defined (__DJGPP__)
   char usage[]="Usage: sshd386 [options] username remotehost [command [args]]\n"
#else
   char usage[]="Usage: sshdos [options] username remotehost [command [args]]\n"
#endif
            "Options:\n"
            "-c <3des|blowfish>                 - cipher type (default: blowfish)\n"
            "-i <identity file>                 - key file for public key authentication\n"
            "-t <terminal type>                 - terminal type (default: xterm)\n"
            "-p <port number>                   - remote port\n"
            "-k <keymap file>                   - path to keymap file\n"
            "-m <mode>                          - screen mode: 80x25 80x60 132x25 132x50\n"
            "-s <password>                      - remote password\n"
            "-l <log file>                      - log session to file\n"
            "-a <minutes>                       - time between keepalive packets\n"
            "-b <COM[1234]>                     - Brailab PC adapter on COM[1234] port\n"
            "-P                                 - don't allocate a privileged port\n"
            "-C                                 - enable compression\n"
            "-S                                 - disable status line\n"
            "-B                                 - use BIOS calls for video output\n"
            "-n                                 - add CR if server sends only LF\n"
            "-d                                 - save SSH packets to debug.pkt\n"
            "-v                                 - verbose output";

   for(i = 1; i < argc; ++i){
        s = argv[i];
        if(*s != '-')
           break;
        switch(*++s){
           case '\0':
                fatal(usage);

           case 'c':
                if(*++s){
                   if(!strcmp(s,"blowfish"))
                        break;
                   else if(!strcmp(s,"3des")){
                        GlobalConfig.CipherType = SSH_CIPHER_3DES;
                        SessKeyInit = des3_sesskey;
                        EncryptPacket = des3_encrypt_blk;
                        DecryptPacket = des3_decrypt_blk;
                        break;
                   }
                   else
                        fatal(usage);
                }
                else if(++i < argc){
                   if(!strcmp(argv[i],"blowfish"))
                        break;
                   else if(!strcmp(argv[i],"3des")){
                        GlobalConfig.CipherType = SSH_CIPHER_3DES;
                        SessKeyInit = des3_sesskey;
                        EncryptPacket = des3_encrypt_blk;
                        DecryptPacket = des3_decrypt_blk;
                        break;
                   }
                   else
                        fatal(usage);
                }
                else
                   fatal(usage);

           case 'i':
                if(*++s)
                   KeyFile = s;
                else if(++i < argc)
                   KeyFile = argv[i];
                else
                   fatal(usage);
                continue;

           case 's':
                if(*++s)
                   PassWord = strdup(s);
                else if(++i < argc)
                   PassWord = strdup(argv[i]);
                else
                   fatal(usage);
                PassWord[MAX_PASSWORD_LENGTH - 1] = '\0';
                continue;

           case 'l':
                if(*++s){
                   if((LogFile = fopen(s,"w+b")) == NULL)
                        fatal("Cannot create log file");
                }
                else if(++i < argc){
                   if((LogFile = fopen(argv[i],"w+b")) == NULL)
                        fatal("Cannot create log file");
                }
                else
                   fatal(usage);
                continue;

           case 't':
                if(*++s)
                   term = s;
                else if(++i < argc)
                   term = argv[i];
                else
                   fatal(usage);
                continue;

           case 'm':
                if(*++s){
                   for(n = 0; n < 4; n++)
                      if(!strcmp(s, modes[n].mode)){
                         vidmode = modes[n].vidmode;
                         break;
                      }
                   if(!vidmode)
                        fatal(usage);
                }
                else if(++i < argc){
                   for(n = 0; n < 4; n++)
                      if(!strcmp(argv[i], modes[n].mode)){
                         vidmode = modes[n].vidmode;
                         break;
                      }
                   if(!vidmode)
                        fatal(usage);
                }
                else
                   fatal(usage);
                continue;

           case 'p':
                if(*++s)
                   RemotePort = atoi(s);
                else if(++i < argc)
                   RemotePort = atoi(argv[i]);
                else
                   fatal(usage);
                continue;

           case 'a':
                if(*++s)
                   Keepalives = atoi(s);
                else if(++i < argc)
                   Keepalives = atoi(argv[i]);
                else
                   fatal(usage);
                continue;

           case 'b':
                if(*++s){
                   strupr(s);
                   if(!strcmp(s, "COM1") ||
                      !strcmp(s, "COM2") ||
                      !strcmp(s, "COM3") ||
                      !strcmp(s, "COM4")){
                        if((GlobalConfig.brailab = fopen(s,"w+b")) == NULL){
                           fatal("Cannot open COM port");
                        }
                   }
                   else
                        fatal(usage);
                }
                else if(++i < argc){
                   strupr(argv[i]);
                   if(!strcmp(argv[i], "COM1") ||
                      !strcmp(argv[i], "COM2") ||
                      !strcmp(argv[i], "COM3") ||
                      !strcmp(argv[i], "COM4")){
                        if((GlobalConfig.brailab = fopen(argv[i],"w+b")) == NULL){
                           fatal("Cannot open COM port");
                        }
                   }
                   else
                        fatal(usage);
                }
                else
                   fatal(usage);
                continue;

           case 'k':
                if(*++s)
                   keymap_init(s);
                else if(++i < argc)
                   keymap_init(argv[i]);
                else
                   fatal(usage);
                continue;

           case 'P':
                Configuration |= NONPRIVILEGED_PORT;
                continue;

           case 'S':
                statusline = 0;
                continue;

           case 'C':
                Configuration |= COMPRESSION_REQUESTED;
                continue;

           case 'B':
                Configuration |= BIOS;
                continue;

           case 'n':
                Configuration |= NEWLINE;
                continue;

           case 'd':
                if((GlobalConfig.debugfile = fopen("debug.pkt","w+")) == NULL)
                   fatal("Cannot create debug file");
                else
                   fputs("\n-------------------\n",GlobalConfig.debugfile);
                continue;

           case 'v':
                Configuration |= VERBOSE_MODE;
                continue;

           default:
                fatal(usage);
        } /* end switch */

    } /* end for */

   /* no_more_options */
   if(i + 2 > argc)
        fatal(usage);
   UserName = argv[i++];
   RemoteHost = argv[i++];
   if(i >= argc)                        /* command args? */
        return;
   /* collect remaining arguments and make a command line of them */
   for(len = 0, j = i; j < argc; j++)
        len += strlen(argv[j]) + 1;     /* 1 for the separating space */
   command = (char *)xmalloc(len);
   for(*command = '\0', j = i; j < argc; j++){
        strcat(command, argv[j]);
        if(j < argc - 1)                /* add ' 'if not last argument */
            strcat(command, " ");
   }
}

/*
 * Main program starts here
 */
int main(int argc, char **argv)
{
#if defined (__386__) || defined (__DJGPP__)
   printf("SSHDOS v%s. 386+ version\n", SSH_VERSION);
#else
   printf("SSHDOS v%s\n", SSH_VERSION);
#endif

   Config_Init();       /* Initialize global variables */
   srand(time(NULL));   /* Initialize random number generator */

   getargs(argc, argv); /* Process command line */

   VESACheck();
   SetMode();
   VidParam();          /* Get proper screen size for PTY negotiation */

   TCPConnect(RemoteHost, RemotePort);     /* Connect to server */

   SSH_Connect(UserName, PassWord, KeyFile); /* Start SSH negotiation */

   /* Request a pseudo terminal if stdout is the terminal*/
   if(isatty(fileno(stdout))){
      tty = 1;
      SSH1_Request_Pty(term);
   }

   /* Start an interactive shell or run specified command */
   SSH1_Start_Shell_Or_Command();

#if defined (__DJGPP__)
   tcp_cbreak(1);	/* No Break checking under DJGPP */
#endif

   VidInit(UserName, RemoteHost);
   VTInit();

   if(Keepalives){      /* install keepalive timer */
        Keepalives = Keepalives * 18 * 60; /* correct keepalives value */
#if defined (__WATCOMC__)
        oldhandler = _dos_getvect(0x1C);
        _dos_setvect(0x1C, keepalive);
#elif defined (__DJGPP__)
	_go32_dpmi_lock_data(&timer, sizeof(timer));
	_go32_dpmi_lock_code(keepalive, (long)end_keepalive - (long) keepalive);
	_go32_dpmi_get_protected_mode_interrupt_vector(0x1C, &oldhandler);
	my_handler.pm_offset = (int) keepalive;
	my_handler.pm_selector = _go32_my_cs();
	_go32_dpmi_chain_protected_mode_interrupt_vector(0x1C, &my_handler);
#endif
   } /* if */

   while(EXIT_SSH != dosession());      /* Loop until session end */

   xfree(pktin.body);

   VidUninit();
   keymap_uninit();

#if !defined (__386__)
   if(Configuration & COMPRESSION_ENABLED)
        Disable_Compression();
#endif

   if(Keepalives)
#if defined (__WATCOMC__)
        _dos_setvect(0x1C, oldhandler);
#elif defined (__DJGPP__)
	_go32_dpmi_set_protected_mode_interrupt_vector(0x1C, &oldhandler);
#endif

   sock_close(&GlobalConfig.s); /* Close TCP socket */

   /* Close open files */
   if(GlobalConfig.brailab)
        fclose(GlobalConfig.brailab);
   if(GlobalConfig.debugfile)
        fclose(GlobalConfig.debugfile);
   if(LogFile)
        fclose(LogFile);

   return(0);
}
