/* negotiat.c       Copyright (c) 2000-2003 Nagy Daniel
 *
 * $Date: 2003/11/11 13:55:44 $
 * $Revision: 1.2 $
 *
 * This module is the SSH negotiation part:
 *  - open TCP connection
 *  - version check
 *  - key generation
 *
 * 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 <io.h>
#include <stdio.h>
#include <string.h>
#if defined (__DJGPP__)
 #include <errno.h>
#endif

#include "tcp.h"
#include "auth.h"
#include "ssh.h"
#include "transprt.h"
#include "macros.h"
#include "sshmd5.h"
#include "sshrsa.h"
#include "cipher.h"
#include "version.h"
#include "xmalloc.h"
#include "config.h"
#include "common.h"

SessKeyInitPtr SessKeyInit;

/* external functions, data */
extern Config GlobalConfig;		/* configuration variables */
extern unsigned short Configuration;    /* configuration variables */

extern Packet pktin;

/* global variables */
char ssh1_session_id[16];		/* Session identifier */

/* local variables */
static unsigned char auth_mask[4];


/*
 * SSH version string exchange: get server's SSH protocol
 * version, examine it, and send ours if it seems that we
 * can communicate
 */
static void SSH1_Exchange_Identification(void)
{
char localstr[256], remotestr[256];
unsigned short remote_major, remote_minor;
int i;

   if(Configuration & VERBOSE_MODE)
        puts("Identification Exchange");

   /* Read other side's version identification. */
   do{
      if(!(i = sock_gets(&GlobalConfig.s, remotestr, sizeof(remotestr))))
      fatal("Cannot read remote identification string");
   } while(strncmp(remotestr, "SSH-", 4)); /* ignore other lines */

   if(sscanf(remotestr, "SSH-%hu.%hu-", &remote_major, &remote_minor) != 2)
      fatal("Bad remote protocol version string");

   remotestr[i] = 0;
   remotestr[strcspn(remotestr, "\r\n")] = 0; /* cut \r\n if exists */

   if(Configuration & VERBOSE_MODE)
        printf("Remote version: %s\r\n",remotestr);

   sprintf(localstr, "SSH-%d.%d-SSHDOS_%s\r\n", PROTOCOL_MAJOR, PROTOCOL_MINOR, SSH_VERSION);
   if(Configuration & VERBOSE_MODE)
      printf("Local version: %s", localstr);

   if(remote_major != 1 && remote_minor == 0)
      fatal("Unsupported remote protocol version");

   if(sock_write(&GlobalConfig.s, localstr, strlen(localstr)) != strlen(localstr))
      fatal("Sock write: %s", strerror(errno));
}


/*
 * Set maximum SSH packet size
 */
static void PacketSize(void)
{
unsigned short size = MAX_PACKET_SIZE;

   if(Configuration & VERBOSE_MODE)
      puts("Setting maximum packet size");

   SSH_pkt_init(SSH_CMSG_MAX_PACKET_SIZE);
   SSH_putuint32(size);
   SSH_pkt_send();

   SSH_pkt_Read(SSH_SMSG_SUCCESS);
}


/*
 * Extract host keys, get authentication masks,
 * create session key and ID, send them back and begin
 * encryption
 */
static void SSH1_create_keys(void)
{
unsigned short i, j;
char *RSAblock, *keystr1, *keystr2;
unsigned char cookie[8];
unsigned char session_key[32];
RSAKey servkey, hostkey;

   memset(&servkey, 0, sizeof(RSAKey));
   memset(&hostkey, 0, sizeof(RSAKey));

   /* Wait for a public key packet from the server. */
   if(Configuration & VERBOSE_MODE)
      puts("Wait for public key");
   SSH_pkt_Read(SSH_SMSG_PUBLIC_KEY);

   /* get the server key */
   memcpy(cookie, pktin.body, 8);
   if(Configuration & VERBOSE_MODE)
      puts("Extracting server keys");
   i = makekey(pktin.body + 8, &servkey, &keystr1, 0);
   j = makekey(pktin.body + 8 + i, &hostkey, &keystr2, 0);
   memcpy(auth_mask, pktin.body + 16 + i + j, sizeof(auth_mask));

   /* generate session ID */
   if(Configuration & VERBOSE_MODE)
      puts("Generating session ID");
   MD5Init();
   MD5Update(keystr2, hostkey.bytes);
   MD5Update(keystr1, servkey.bytes);
   MD5Update(cookie, 8);
   MD5Final(ssh1_session_id);

   for(i = 0; i < 32; i++)
      session_key[i] = rand() % 256;

   j = (hostkey.bytes > servkey.bytes ? hostkey.bytes : servkey.bytes);
   RSAblock = (char *)xmalloc(j);
   memset(RSAblock, 0, j);

   for (i = 0; i < 32; i++) {
      RSAblock[i] = session_key[i];
      if (i < 16)
         RSAblock[i] ^= ssh1_session_id[i];
   }

   if(Configuration & VERBOSE_MODE)
      puts("Encrypting session key");
   if (hostkey.bytes > servkey.bytes) {
      rsaencrypt(RSAblock, 32, &servkey);
      rsaencrypt(RSAblock, servkey.bytes, &hostkey);
   }
   else{
      rsaencrypt(RSAblock, 32, &hostkey);
      rsaencrypt(RSAblock, hostkey.bytes, &servkey);
   }
   freersakey(&hostkey);
   freersakey(&servkey);

   SSH_pkt_init(SSH_CMSG_SESSION_KEY);
   SSH_putbool(GlobalConfig.CipherType);
   SSH_putdata(cookie, 8);
   SSH_putbool((j * 8) >> 8);
   SSH_putbool((j * 8) & 0xFF);
   SSH_putdata(RSAblock, j);
   SSH_putuint32(0);
   if(Configuration & VERBOSE_MODE)
      puts("Sending encrypted session key");
   SSH_pkt_send();
   xfree(RSAblock);

   SessKeyInit(session_key);
   Configuration |= CIPHER_ENABLED; /* enable cipher */
}


/*
 * Run SSH negotiation process
 */
void SSH_Connect(char *username, char *password, char *keyfile)
{

   /* Wait for host version packet */
   while(!sock_dataready(&GlobalConfig.s))
      if(!tcp_tick(&GlobalConfig.s))
         fatal("Remote host closed connection");

   /* Version string exchange and verification */
   SSH1_Exchange_Identification();

   /* Create SSH keys */
   SSH1_create_keys();

   /* Check that our authentication method is allowed */
   if((auth_mask[3] & (1 << SSH_AUTH_RSA)) == 0)
      puts("Server does not allow RSA key authentication");
   if((auth_mask[3] & (1 << SSH_AUTH_PASSWORD)) == 0)
      puts("Server does not allow password authentication");

   /* Wait for encrypted ACK */
   if(Configuration & VERBOSE_MODE)
      puts("Waiting for first encrypted ACK");
   SSH_pkt_Read(SSH_SMSG_SUCCESS);

   /*
    * Send name, then try public key authentication first.
    * If it fails, password authentication comes next
    */
   if(SSH1_Send_User_Name(username))
      if(SSH1_Auth_Pubkey(keyfile))
         SSH1_Auth_Password(password);

   /* Set maximum packet size */
   PacketSize();

   /* Start compression if configured */
   if(Configuration & COMPRESSION_REQUESTED)
      Request_Compression(6);
}
