/* $Id: fish-keygen.c,v 1.23 1999/06/25 05:33:39 levitte Exp $ */

#include "gnu_extras.h"

#if defined(__DECC) || defined(__GNUC__)
#include <unistd.h>
#endif
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <descrip.h>
#include <errno.h>
#include <sys/stat.h>
#include <lib$routines.h>
#include <stsdef.h>
#include <ssdef.h>
#include <math.h>
#include <time.h>
#ifdef __GNUC__
#define __DECC
#endif
#include <openssl/rsa.h>
#include <openssl/bn.h>
#include <openssl/md5.h>
#ifdef __GNUC__
#undef __DECC
#endif
#include "fish.h"
#include "ssh.h"
#include "util.h"
#include "vms.h"
#include "fishmsg.h"
#include "fish-keygen.h"
#include "buffer.h"
#include "userkey.h"

/* Because it isn't really supported with DEC C 4.0 source */
#if defined(__DECC) && !defined(__MODE_T)
#   define __MODE_T 1
    typedef __mode_t mode_t;  
#endif


/* The identity file has the following format:

   Unencrypted section:

	33 bytes:	AUTHFILE_ID_STRING (including a NUL!)
	1 byte:		cipher type number
	1 32bit:	a dummy?
	1 32bit:	number of RSA key bits
	1 bignum:	RSA n
	1 bignum:	RSA e
	1 string:	comment string

   Encrypted section (encrypted with the cipher type given in the
   unencrypted section, with a user-provided password that has been
   run through MD5):

	4 bytes:	byte 1 == byte 3 and byte 2 == byte 4
	1 bignum:	RSA d
	1 bignum:	RSA q mod p
	1 bignum:	RSA p
	1 bignum:	RSA q

*/

static void rsa_cb(int p, int n, char *arg)
{
    char c = '.';

    if (p == 1) c = '+';
    if (p == 2) c = '*';
    if (p == 3) c = '\n';
    putc(c, stderr);
    fflush(stderr);
}

int create_keyfile(ssh_prefs *sshprefs, userkey *uk,
		   general_buffer *passphrase,
		   char *passphrase_prompt1, char *passphrase_prompt2)
{
    FILE *f;
    int status;
    char buf[1024], ret[1024], ret2[1024], *p;
    char auth_id[] = AUTHFILE_ID_STRING;
    mode_t save_mask;
    int bytes_written;

    general_buffer *keybuf = buf_init(0);

    if (access(sshprefs->identity_file, F_OK) >= 0
	&& sshprefs->whattodo == KEYGEN) {
	buf_destroy(keybuf);
	return FISH_M_NOOVERWR;
    }

    if (uk == 0) {
	uk = userkey_init();
	uk->cipher_type = sshprefs->cipher_type;
	uk->bits = sshprefs->bits;
	uk->comment = sshprefs->comment;
	uk->encrypted_data = 0;
	uk->encrypted_len = 0;
	uk->key = 0;
    }

    if (uk->comment == 0) uk->comment = sshprefs->comment;
    if (uk->key == 0)
	uk->key = RSA_generate_key(uk->bits, 65537, rsa_cb, NULL);

    if (passphrase_prompt1 == 0)
	passphrase_prompt1 = "Passphrase for '%s': ";
    if (passphrase_prompt2 == 0)
	passphrase_prompt2 = "Verify passphrase for '%s': ";

    if (passphrase == 0) {
    passphrase_again:
	sprintf(buf, passphrase_prompt1, uk->comment);
	if (!read_prompted("TT:", buf, ret, 1023, 0, 0, &status)) {
	    lib_put_output("");
	    lib$signal(status);
	}
	lib_put_output("");
	sprintf(buf, passphrase_prompt2, uk->comment);
	if (!read_prompted("TT:", buf, ret2, 1023, 0, 0, &status)) {
	    lib_put_output("");
	    lib$signal(status);
	}
	lib_put_output("");
	if (strcmp(ret, ret2) != 0) {
	    /* Passphrases do not match.  Zero passphrases and exit! */
	    memset(ret, 0, sizeof(ret));
	    memset(ret2, 0, sizeof(ret2));
	    lib$signal(FISH_M_PWDNOTVER);
	    goto passphrase_again;
	}
	passphrase = buf_init(strlen(ret));
	buf_append_chars_nocount(passphrase, ret);
	memset(ret2, 0, sizeof(ret2));
	memset(ret, 0, sizeof(ret));
    }

    status = userkey_encrypt_private_part(uk, passphrase);
    buf_destroy(passphrase);
    if (!$VMS_STATUS_SUCCESS(status)) {
	buf_destroy(keybuf);
	lib$signal(status);
	return status | 0x10000000;
    }
    
    status = userkey_encode_buffer(uk, &keybuf);
    if (!$VMS_STATUS_SUCCESS(status)) {
	buf_destroy(keybuf);
	lib$signal(status);
	return status | 0x10000000;
    }

    if (access(sshprefs->identity_file, F_OK) >= 0
	&& sshprefs->whattodo == KEYGEN) {
	buf_destroy(keybuf);
	return FISH_M_NOOVERWR;
    }

    save_mask = umask(0600);

    if ((f = fopen(sshprefs->identity_file, "wb")) == NULL) {
        char *p,*end,*end2;

        /* Rough but fine */
        p = (char *) xmalloc( sizeof(char) * strlen(sshprefs->identity_file));
        strcpy(p,sshprefs->identity_file);

        end = strrchr(p,']')+1;
	end2 = strrchr(p,'>')+1;
	if (end2 > end)
	    end = end2;
        *end = '\0';

        /* I used mask 0 i.e. the protection set by
	   SET FILE/PROT[=...]/DEFAULT */
	umask(save_mask);
        if( mkdir(p,0) != 0){
	    lib$signal(FISH_M_CREATEFAIL, 1, p, vaxc$errno);
        }       
        lib$signal(FISH_M_CREATED, 1, p);

        _xfree(p);

	save_mask = umask(0600);

        /* Now we try again                                             */
        if ((f = fopen(sshprefs->identity_file, "wb")) == NULL) {
	    umask(save_mask);
	    lib$signal(FISH_M_INVFILE, 2, sshprefs->identity_file,
		       strerror(errno, vaxc$errno));
	    return FISH_M_INVFILE | 0x10000000;
	}
    }


    fwrite(buf_bytes(keybuf), buf_amount(keybuf), 1, f);
    buf_destroy(keybuf);

    fclose(f);

    umask(save_mask);

    /* Uhmmm, keybuf is now not really used for keys any more... */
    keybuf = buf_init(0);
    buf_append_chars_nocount(keybuf, "Your identification has been saved in ");
    buf_append_chars_nocount(keybuf, sshprefs->identity_file);
    lib_put_output(buf_chars(keybuf));
    buf_destroy(keybuf);

    lib_put_output("Your public key is:");

    keybuf = buf_init(30);
    sprintf(buf_bytes(keybuf), "%d ", sshprefs->bits);
    buf_adjust_amount_by(keybuf, strlen(buf_bytes(keybuf)));
    buf_append_chars_nocount(keybuf, p = BN_bn2dec(uk->key->e));  xfree(p);
    buf_append_chars_nocount(keybuf, " ");
    buf_append_chars_nocount(keybuf, p = BN_bn2dec(uk->key->n));  xfree(p);
    buf_append_chars_nocount(keybuf, sshprefs->comment);
    lib_put_output(buf_chars(keybuf));

    strcpy(ret, sshprefs->identity_file);
    strcat(ret, "-PUB");

    if ((f = fopen(ret, "w")) == NULL) {
	lib$signal(FISH_M_INVFILE, 2, ret, strerror(errno, vaxc$errno));
	return FISH_M_INVFILE | 0x10000000;
    }

    fprintf(f, "%s\n", buf_chars(keybuf));
    buf_destroy(keybuf);

    fclose(f);

    keybuf = buf_init(80);
    buf_append_chars_nocount(keybuf, "Your public key has been saved in ");
    buf_append_chars_nocount(keybuf, ret);
    lib_put_output(buf_chars(keybuf));
    buf_destroy(keybuf);

    userkey_destroy(uk);
    return SS$_NORMAL;
}

int change_keyfile(ssh_prefs *sshprefs)
{
    userkey uk;
    int passphrase_times = 3;
    general_buffer *passphrase = buf_init(0);
    int status;

    status = userkey_read_keyfile(&uk, sshprefs->identity_file,
				  sshprefs->uai.uic);
    if (!$VMS_STATUS_SUCCESS(status)) {
	lib$signal(FISH_M_INVFILE, 2, sshprefs->identity_file,
		   strerror(errno, vaxc$errno), status);
	return FISH_M_INVFILE | 0x10000000;
    }

    while ((status = userkey_decrypt_private_part(&uk, passphrase))
	   == FISH_M_UKBADPASS
	   && (!buf_amount(passphrase) || passphrase_times-- > 0)) {
	general_buffer *buf = buf_init(1);
	char ret[1024];
	buf_append_chars_nocount(buf, "Enter passphrase for ");
	buf_append_chars_nocount(buf, uk.comment);
	buf_append_chars_nocount(buf, " :");
	if (!read_prompted("TT:", (char *) buf_chars_noadjust(buf), ret, 1023,
			   0, 0, &status)) {
	    lib_put_output("");
	    lib$signal(status);
	}
	lib_put_output("");

	buf_destroy(passphrase);
	passphrase = buf_init(strlen(ret));
	buf_append_chars_nocount(passphrase, ret);
    }

    if (status != SS$_NORMAL)
	userkey_destroy_static(&uk);
    if (status != SS$_NORMAL) {
	if (status == FISH_M_UKBADPASS)
	    return status;
	lib$signal(FISH_M_INVFILE, 2, sshprefs->identity_file,
		   strerror(errno, vaxc$errno), status);
	return FISH_M_INVFILE | 0x10000000;
    }

    xfree(uk.encrypted_data);
    uk.encrypted_data = 0;
    uk.encrypted_len = 0;

    if (sshprefs->todo_flags & CH_BITS && sshprefs->bits != uk.bits) {
	uk.bits = sshprefs->bits;
	RSA_free(uk.key);
    }
    if (sshprefs->todo_flags & CH_CIPHER_TYPE)
	uk.cipher_type = sshprefs->cipher_type;
    if (sshprefs->todo_flags & CH_COMMENT)
	uk.comment = sshprefs->comment;
    if (sshprefs->todo_flags & CH_PASSPHRASE) {
	buf_destroy(passphrase);
	passphrase = 0;
    }

    return create_keyfile(sshprefs, &uk, passphrase,
			  "New passphrase for '%s': ",
			  "Verify new passphrase for '%s': ");
}

/* Emacs local variables

Local variables:
eval: (set-c-style "BSD")
end:

*/
