/* $Id: hostfile.c,v 1.4 1999/06/27 21:54:26 levitte Exp $ */

#include "gnu_extras.h"

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>
#include <descrip.h>
#include <ssdef.h>
#include <stsdef.h>
#include <lib$routines.h>
#include <strdef.h>
#include <str$routines.h>
#ifdef __GNUC__
#define __DECC
#endif
#include <openssl/bn.h>
#ifdef __GNUC__
#undef __DECC
#endif
#include "fish.h"
#include "buffer.h"
#include "fishmsg.h"
#include "util.h"
#include "hostfile.h"

typedef struct host_list {
    general_buffer *name;
    char negated;
    struct host_list *next;
} host_list;

typedef struct host_entry {
    struct host_list *hosts;
    unsigned int bits;
    RSA *key;
} host_entry;

/* XXX: ugly cast! */
#define INIT_SDESC(dsc, len, ptr) {(dsc).dsc$b_dtype = DSC$K_DTYPE_T;\
    (dsc).dsc$b_class = DSC$K_CLASS_S; (dsc).dsc$w_length = (len);\
    (dsc).dsc$a_pointer = (char *)(ptr);}


host_list *host_list_init(char *name, int negated)
{
    host_list *hl = xmalloc(sizeof(host_list));

    hl->name = buf_init(0);
    buf_append_chars_nocount(hl->name, name);
    hl->negated = negated;
    hl->next = 0;

    return hl;
}

void host_list_destroy(host_list *hl)
{
    if (hl) {
	buf_destroy(hl->name);
	host_list_destroy(hl->next);
	xfree(hl);
    }
}

void host_list_prepend(host_list **hl, host_list *hle)
{
    if (hle && hl) {
	if(*hl)
	    hle->next = (*hl)->next;
	*hl = hle;
    }
}

void host_list_reverse(host_list **hl)
{
    host_list *hle1;
    host_list *hle2 = 0;

    for (hle1 = *hl; hle1; hle1 = *hl) {
	*hl = hle1->next;
	hle1->next = hle2;
	hle2 = hle1;
    }
    *hl = hle2;
}

host_entry *host_entry_init()
{
    host_entry *he = xmalloc(sizeof(host_entry));

    he->hosts = 0;
    he->bits = 0;
    he->key = RSA_new();

    return he;
}

void host_entry_destroy(host_entry *he)
{
    if (he) {
	if (he->hosts) host_list_destroy(he->hosts);
	if (he->key) RSA_free(he->key);
	xfree(he);
    }
}

int read_host_entry(host_entry **he, FILE *f)
{
    char buf[32768];
    char *cp, *cp2;

    *he = 0;

 again:
    if (*he)
	host_entry_destroy(*he);

    *he = host_entry_init();

    if (fgets(buf, 32768, f)) {
	for (cp = buf; isspace(*cp); cp++)
	    ;

	/* Skip comments and emoty lines */
	if (*cp == '\0' || *cp == '#' || *cp == '\n')
	    goto again;

	while (*cp != '\0' && !isspace(*cp)) {
	    int negated = 0;
	    char c;

	    if (*cp == '!') {
		negated = 1;
		cp++;
	    }
	    for (cp2 = cp; !isspace(*cp2) && *cp2 != ','; cp2++)
		;
	    c = *cp2;
	    *cp2 = '\0';
	    on_state(fprintf(stderr,"Host: %s%s\n",
			     negated ? "(negated) " : "",
			     cp));
	    host_list_prepend(&((*he)->hosts), host_list_init(cp, negated));
	    cp = cp2;
	    if (c == ',')
		cp++;
	    else
		*cp = c;
	}
	host_list_reverse(&((*he)->hosts));

	/* Skip badly formatted lines */
	if (*cp == '\0')
	    goto again;

	for (; isspace(*cp); cp++)
	    ;
	(*he)->bits = strtoul(cp, &cp2, 10);
	on_state(fprintf(stderr, "bits: %u\n", (*he)->bits));

	if (*cp2 == '\0' || !isspace(*cp2))
	    goto again;

	for (cp = cp2; isspace(*cp); cp++)
	    ;
	for (cp2 = cp; isdigit(*cp2); cp2++)
	    ;
	if (*cp2 == '\0' || !isspace(*cp2))
	    goto again;
	*cp2++ = '\0';
	BN_dec2bn(&((*he)->key->e), cp);
	on_state(fprintf(stderr, "e:    %s\n", cp = BN_bn2hex((*he)->key->e));
		 xfree(cp));

	for (cp = cp2; isspace(*cp); cp++)
	    ;
	for (cp2 = cp; isdigit(*cp2); cp2++)
	    ;
	if (*cp2 == '\0' || !isspace(*cp2))
	    goto again;
	*cp2++ = '\0';
	BN_dec2bn(&((*he)->key->n), cp);
	on_state(fprintf(stderr, "n:    %s\n", cp = BN_bn2hex((*he)->key->n));
		 xfree(cp));

	for (cp = cp2; isspace(*cp); cp++)
	    ;
	if (*cp != '\0')
	    goto again;
	return 1;
    }
    return 0;
}

int match_hostname(char *host, host_list *hl)
{
    struct dsc$descriptor_s host_dsc;
    INIT_SDESC(host_dsc, strlen(host), host);

    for (; hl; hl = hl->next) {
	struct dsc$descriptor_s hp_dsc;
	INIT_SDESC(hp_dsc, buf_amount(hl->name), buf_chars_noadjust(hl->name));

	if (str$match_wild(&host_dsc, &hp_dsc) == STR$_MATCH) {
	    on_state(fprintf(stderr, "Found a matching host: %.*s\n",
			     buf_amount(hl->name),
			     buf_chars_noadjust(hl->name)));
	    return !hl->negated;
	}
    }
    return 0;
}

int find_host(char *host, RSA *key, char *filename)
{
    FILE *f;
    host_entry *he;
    int end_status = FISH_M_HOSTNEW;

    if ((f = fopen(filename,"r")) != 0) {
	while(read_host_entry(&he, f)) {
	    if (match_hostname(host, he->hosts)) {
		if (BN_num_bits(he->key->n) == he->bits
		    && BN_cmp(he->key->n, key->n) == 0
		    && BN_cmp(he->key->e, key->e) == 0) {
		    on_state(fprintf(stderr, "Keys match!\n"));
		    host_entry_destroy(he);
		    fclose (f);
		    return FISH_M_HOSTOK;
		}
		/* This entry is wrong, but let's keep on reading, there may
		   be a correct entry added... */
		end_status = FISH_M_HOSTCHANGED;
	    }
	    host_entry_destroy(he);
	}
	host_entry_destroy(he);
	fclose (f);
    }
    return end_status;
}

int add_host(char *host, RSA *key, char *filename)
{
    FILE *f;
    general_buffer *buf = buf_init(1);
    char *p, buf2[15];

    if ((f = fopen(filename, "a+")) == 0) {
	lib$signal(FISH_M_OPENOUT, 1, filename, vaxc$errno);
	return FISH_M_OPENOUT | 0x1000000;
    }

    buf_append_chars_nocount(buf, host);
    buf_append_chars_nocount(buf, " ");
    sprintf(buf2, " %u ", BN_num_bits(key->n));
    buf_append_chars_nocount(buf, buf2);
    buf_append_chars_nocount(buf, p = BN_bn2dec(key->e)); xfree(p);
    buf_append_chars_nocount(buf, " ");
    buf_append_chars_nocount(buf, p = BN_bn2dec(key->n)); xfree(p);

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

    buf_destroy(buf);
    fclose(f);

    return SS$_NORMAL;
}

/* Emacs local variables

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

*/
