/*
 *  File: sadbed.c
 *  Author: Claes M. Nyberg <md0claes@mdstud.chalmers.se>
 *  Description: SADB edit routines
 *  Date: Tue Dec 16 22:38:51 CET 2003
 *
 *  Copyright (c) 2003 Claes M. Nyberg <md0claes@mdstud.chalmers.se>
 *  All rights reserved, all wrongs reversed.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *  3. The name of author may not be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 *  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 *  AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 *  THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 *  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 *  OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 *  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 *  OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 *  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <libgen.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "sadbed.h"
#include "random.h"

/* For mmap()  */
#ifndef MAP_FILE
#define MAP_FILE    0
#endif

/* Local options */
static struct sadbopt opt;

/* Local procedures */
static int interactive(struct ent_node *);
static struct ent_node *add_ent(struct ent_node *, struct sadbent *, u_char *, u_int *);
static struct ent_node *get_ent(struct ent_node *, u_long, struct sadbent **);
static void *memordie(size_t);
static void write_tree(struct ent_node *, int);
static void save(struct ent_node **, u_char *, u_char *, char **);


#define PRINT_HOSTNAME(ip)                  \
        if (opt.resolve) {                  \
			u_char *pt = net_hostname2(ip); \
			if (pt)                         \
				printf(" (%s)", pt);        \
		}

/* flags used by print_tree below */
#define PRINT_KEY_FLAG		0x01
#define PRINT_PKTS_FLAG		0x02


/*
 * Append entry with IP address from file to tree, 
 * or all if ipaddr is zero. 
 * Returns the number of entries added.
 */
static ssize_t
add_filent(struct ent_node **root, u_char *file, 
		u_char decrypt, u_char savekey, u_int ipaddr)
{
	size_t tot = 0;
	struct mfile *mf;
	struct mfile mftmp;

    if ( (mf = open_mfile(file)) == NULL)
        return(0);

    if (mf->mf_size <=  sizeof(struct safhdr)) {
        fprintf(stderr, "** Error: '%s' is not "
            "a SADB file\n", file);
        close_mfile(mf);
		return(0);
    }

	/* Copy file structure in case of decryption */
	memcpy(&mftmp, mf, sizeof(struct mfile));

	/* Decrypt file */
	if (decrypt) {
		struct safhdr *fhp;
		struct bfish_key *bk;
		u_char *key;

		if ( (key = getkey(FILE_KEY, file)) == NULL)
			return(0);
	
		if ( (bk = bfish_keyinit(key, strlen(key))) == NULL) {
			fprintf(stderr, "** Error: Could not initiate blowfish key\n");
			close_mfile(mf);
			return(0);
		}

		/* Zero out or save key key */
		if (savekey)
			opt.key = strdup(key);
		memset(key, 0x00, strlen(key));

		/* Decrypt */
		fhp = (struct safhdr *)mf->mf_file;
		bfish_cbc_decrypt(mf->mf_file+sizeof(fhp->iv),
			mf->mf_size-sizeof(fhp->iv), fhp->iv, bk);

		/* Zero out key */
		memset(bk, 0x00, sizeof(struct bfish_key));
		free(bk);

		/* Check key */
		if (strncmp(fhp->text, SADB_KEYCHECK, sizeof(SADB_KEYCHECK)-1)) {
			fprintf(stderr, "** Error: Bad passphrase\n");
			close_mfile(mf);
			return(0);
		}

		mftmp.mf_size -= sizeof(struct safhdr);
		mftmp.mf_file += sizeof(struct safhdr);
	}

	/* Traverse the file */
	while (mftmp.mf_size > 0) {
		struct sadbent sae;
		u_int added;
		
		if (mftmp.mf_size <= sizeof(struct sadbhdr)) {
			fprintf(stderr, "** Error: add_filent(): Data length is to "
				"short (%u bytes)\n", mftmp.mf_size);
			goto leave;	
		}

		if (sadb_genent(mftmp.mf_file, GET_ENT_HOST_LENGTH(mftmp.mf_file), &sae) == NULL)
				goto leave;
		
		/* Add entry to tree */
		if ((ipaddr == 0) || (ENTIP(sae) == ipaddr)) {
			u_char *data;
			struct sadbent sae_copy;

			if ( (data = sadb_copyent(&sae, &sae_copy, opt.verbose)) == NULL) {
				fprintf(stderr, "Failed to copy data\n");
				goto leave;
			}
			
			*root = add_ent(*root, &sae_copy, data, &added);
			if (added == 1) {
				printf("Added %s", net_ntoa(ENTIP(sae), NULL));
				PRINT_HOSTNAME(ENTIP(sae));
				printf("\n");
				tot++;
			}
			
			/* We are done here */
			if (ipaddr != 0)
				break;
		}
			
		mftmp.mf_size -= sae.se_hdr->sah_totlen;
		mftmp.mf_file += sae.se_hdr->sah_totlen;
	}
leave:
	if ((tot == 0) && (ipaddr != 0))
		fprintf(stderr, "Entry not found!\n");
	close_mfile(mf);
	return(tot);	
}

/*
 * Free the entry tree.
 */
static void
free_ents(struct ent_node *root)
{
	if (root != NULL) {
		free_ents(root->left);
		free(root->data);
		free(root);
		free_ents(root->right);
	}
}

/*
 * Wrapped calloc, exits process on error.
 */
static void *
memordie(size_t n)
{
	void *pt;
	
	if ( (pt = calloc(n, 1)) == NULL) {
		fprintf(stderr, "Error: Failed to allocate memory: %s\n",
			strerror(errno));
		exit(EXIT_FAILURE);;
	}
	return(pt);
}

/*
 * Add entry to tree.
 * data points to the memory allocated for the pointers in the  
 * saent structure;
 */
static struct ent_node *
add_ent(struct ent_node *root, struct sadbent *saent, 
	u_char *data, u_int *added)
{
	/* New tree */
	if (root == NULL) {
		root = (struct ent_node *)memordie(sizeof(struct ent_node));
		root->saent.se_hdr = saent->se_hdr;
		root->saent.se_kpkts = saent->se_kpkts;
		root->saent.se_cpkt = saent->se_cpkt;
		root->data = data;
		root->left = NULL;
		root->right = NULL;
		*added = 1;
	}
	/* Entry allready exist */
	else if (ENTPTIP(saent) == ENTIP_TREE(root)) {
		printf("Entry %s", net_ntoa(ENTIP_TREE(root), NULL));
		PRINT_HOSTNAME(ENTIP_TREE(root));
		printf(" already exist, refusing to add.\n");
		*added = 0;
	}
	/* Smaller, go left */
	else if (ENTPTIP(saent) < ENTIP_TREE(root)) 
		root->left = add_ent(root->left, saent, data, added);	
	/* Bigger, go right */
	else if (ENTPTIP(saent) > ENTIP_TREE(root)) 
		root->right = add_ent(root->right, saent, data, added);
	return(root);
}

/*
 * Add subtree to tree.
 */
static struct ent_node *
add_tree(struct ent_node *root, struct ent_node *subtree)
{
	/* Nothing to do */
	if (subtree == NULL)
		return(root);

	/* New tree, replace with subtree */
	if (root == NULL) 
		root = subtree;

	/* Entry already exist
	 * This should never happen since this routine
	 * is only called after an element has been removed */
	else if (ENTIP_TREE(subtree) == ENTIP_TREE(root)) 
		fprintf(stderr, "Fatal, subtree entry already exist in tree!\n");
	
	/* Smaller, go left */
	else if (ENTIP_TREE(subtree) < ENTIP_TREE(root))
		root->left = add_tree(root->left, subtree);
	
	/* Bigger, go right */
	else if (ENTIP_TREE(subtree) > ENTIP_TREE(root))
		root->right = add_tree(root->right, subtree);

	return(root);
}


/*
 * Delete entry from tree.
 */
static struct ent_node *
del_ent(struct ent_node *root, u_long ip)
{
	/* Entry not found */
	if (root == NULL) {
		if (opt.verbose)
			fprintf(stderr, "Error: entry not found\n");
		return(NULL);
	}

	/* Found entry, merge subtree with root */
	else if (ip == ENTIP_TREE(root)) {
		
		/* Free memory */
		free(root->data);
		free(root);

		/* Merge subtree */
		if ((root->right == NULL) && (root->left != NULL))
			root = root->left;
		else if ((root->right != NULL) && (root->left == NULL))
			root = root->right;
		else if ((root->right != NULL) && (root->left != NULL))
			root = add_tree(root->right, root->left);
		else
			root = NULL;
	}
	
	/* Smaller, go left */
	else if (ip < ENTIP_TREE(root))
		root->left = del_ent(root->left, ip);
	
	/* Bigger, go right */
	else if (ip > ENTIP_TREE(root))
		root->right = del_ent(root->right, ip);

	return(root);
}

/*
 * Get entry from tree
 * *ent is set to the address of entry if found, 
 * NULL otherwise
 */
static struct ent_node *
get_ent(struct ent_node *root, u_long ip, struct sadbent **ent)
{
	/* Entry not found */
	if (root == NULL) {
		*ent = NULL;
		return(NULL);
	}

	/* Entry already exist
	 * This should never happen since this routine
	 * is only called after an element has been removed */
	else if (ip == ENTIP_TREE(root)) 
		*ent = &(root->saent);
		
	/* Smaller, go left */
	else if (ip < ENTIP_TREE(root))
		root->left = get_ent(root->left, ip, ent);
	
	/* Bigger, go right */
	else if (ip > ENTIP_TREE(root))
		root->right = get_ent(root->right, ip, ent);

	return(root);
}

/*
 * Traverse tree in sorted order and print entries.
 * If IP is not zero, only that entry is printed.
 * entname - Only print entry name if set to 1.
 */
static void
print_tree(struct ent_node *root, u_int nameonly, u_int flags, u_long ip)
{
	if (root != NULL) {
		print_tree(root->left, nameonly, flags, ip);

		if ((ip == 0) || (ip == ENTIP_TREE(root))) {
			if (nameonly) {
				printf("%s", net_ntoa(ENTIP_TREE(root), NULL));
				PRINT_HOSTNAME(ENTIP_TREE(root));
				printf("\n");
			}
			else 
				sadb_printent(&root->saent, opt.resolve,
					(flags & PRINT_PKTS_FLAG) != 0, 
					(flags & PRINT_KEY_FLAG) != 0);
		}
		print_tree(root->right, nameonly, flags, ip);
	}
}

/*
 * Print tree contents.
 * Increases *synerr on syntax error.
 * Argv usage: print [ip] [-pk] 
 */
static void
print(struct ent_node **root, u_int *synerr,
		u_int argc, char **argv)
{
	long ip = 0;
	int flag;
	u_int pflags = 0;
	struct sadbent *srcent = NULL;

	if (argc == 1) {
		print_tree(*root, 0, 0, 0);
		return;
	}

	if (argv[1][0] != '-') {
		
		if ( (ip = net_inetaddr(argv[1])) == -1) {
			fprintf(stderr, "Failed to resolve target host/IP '%s'\n", 
				argv[1]);
			return;
		}
    	*root = get_ent(*root, (u_long)ip, &srcent);
    	if (srcent == NULL) {
        	fprintf(stderr, "Entry %s",
            	net_ntoa(ip, NULL));
        	PRINT_HOSTNAME(ip);
        	printf(" not found\n");
        	return;
    	}
		argv++;
		argc--;
	}

	optind = 1;
	while ( (flag = getopt(argc, argv, "pk")) !=  -1) {
		switch (flag) {
			case 'p': pflags |= PRINT_PKTS_FLAG; break;
			case 'k': pflags |= PRINT_KEY_FLAG; break;
			default: (*synerr)++; return;								   
		}
	}

	print_tree(*root, 0, pflags, ip);
}

/*
 * Import from file.
 * Increases *synerr on syntax error.
 * Returns the number of entries added.
 * Argv usage: import file [-de ip]
 */
static int
import(struct ent_node **root, u_int *synerr, 
		u_int argc, char **argv)
{
	long ip = 0;
	u_int decrypt = 1;
	u_char *file;
	int flag;

	if (argc == 1) { 
		(*synerr)++;
		return(0);
	}
	file = argv[1];

	argc--;
	argv++;

	optind = 1;
	while ( (flag = getopt(argc, argv, "de:")) !=  -1) {
		switch (flag) {
			case 'd':
				decrypt = 0;
				break;

			case 'e':
				if ( (ip = net_inetaddr(optarg)) == -1) {
					fprintf(stderr, "Failed to resolve target host/IP '%s'\n", 
						optarg);
					return(0);
				}
				break;

			default:
				(*synerr)++;
				return(0);
		}
	}

	if (argv[optind] != NULL) {
		(*synerr)++;
		return(0);
	}

	return(add_filent(root, file, decrypt, 0, ip));
}

/*
 * Delete entry from tree
 * Increases synerr on bad syntax.
 * Argv usage: del ip [ip..]
 */
static void
delete(struct ent_node **root, u_int *synerr,
        u_int argc, char **argv)
{
	long ip = 0;
	u_int i = 1;

	if (argc <= 1) {
		(*synerr)++;
		return;
	}


	while (argv[i] != NULL) {
		if ( (ip = net_inetaddr(argv[i])) == -1) {
			fprintf(stderr, "Failed to resolve target host/IP '%s'\n",
				argv[i]);
			continue;
		}

		*root = del_ent(*root, ip);
		if (opt.verbose) {
			fprintf(stderr, "Deleted %s",
				net_ntoa(ip, NULL));
			PRINT_HOSTNAME(ip);
			printf("\n");
		}
		i++;
	}
}

/*
 * Write tree to a new file using new key.
 * Argv usage: dump file [ip...]
 */
static void
dump(struct ent_node **root, u_int *synerr,
        u_int argc, char **argv)
{
	struct stat sb;

	if (argc <= 1) {
		(*synerr)++;
		return;
	}

	if (stat(argv[1], &sb) != -1) {
		int c;
		
		fprintf(stderr, "File %s already exist, overwrite? [y/n]: ",
			argv[1]);
		
		c = getc(stdin);

		/* Remove newline */
		getc(stdin);
		
		if (c != 'y')
			return;

		/* Make sure that the list (if any) is a list of valid
		 * entries to avoid destroyning target file in case of
		 * a typing error */
		 if (argc > 2) {
			u_int errors = 0;
			char **iplist = &argv[2];

	        while (*iplist != NULL) {
    	        struct sadbent *ent = NULL;
        	    long ip;

	            if ( (ip = net_inetaddr(*iplist)) == -1) {
    	            fprintf(stderr, "Failed to resolve target host/IP '%s'\n",
        	            *iplist);
					errors++;
            	    continue;
            	}

	            *root = get_ent(*root, (u_long)ip, &ent);

    	        if (ent == NULL) {
        	        fprintf(stderr, "Could not find %s",
            	        net_ntoa(ip, NULL));
               		PRINT_HOSTNAME(ip);
                	printf("\n");
                	continue;
					errors++;
            	}
			}
			fprintf(stderr, "Found (%u) errors in list of entries to dump,"
					"refusing to write file\n", errors);
		 	return;
		 }
	}

	/* Write the file */
	if (argc == 2)
		save(root, argv[1], NULL, NULL);
	else
		save(root, argv[1], NULL, &argv[2]);
}

/*
 * Save tree or selected entries to file
 * If key is NULL, a new key is prompted for
 * If the NULL terminated list of entries iplist is NULL 
 * all entries are written.
 */
static void
save(struct ent_node **root, u_char *file, u_char *key, char **iplist)
{
	int fd;
	struct stat sb;
	struct safhdr fhdr;
	struct bfish_key *bk = NULL;
	caddr_t mmad;

	if (*root == NULL) {
		fprintf(stderr, "Nothing to save!\n");
		return;
	}

	/* Get new key */
	if (key == NULL) {
		if ( (key = getkey(NEW_KEY, file)) == NULL)
		return;
	}

	/* Init key */
	if ( (bk = bfish_keyinit(key, strlen(key))) == NULL) {
		fprintf(stderr, "** Error: Could not initiate blowfish key\n");
		return;
	}

	if ( (fd = open(file, O_RDWR | O_CREAT | O_TRUNC, 077)) < 0) {
		perror("** Error: open()");
		return;
	}

	/* Create IV */
	if (random_bytes((u_char *)&fhdr.iv[0], sizeof(fhdr.iv)) == NULL) {
		fprintf(stderr, "** Error: Could not generate random bytes for IV\n");
		return;
	}

	/* Write header */
	*((uint32_t *)&fhdr.iv[0]) = htonl(*((uint32_t *)&fhdr.iv[0]));
	*((uint32_t *)&fhdr.iv[4]) = htonl(*((uint32_t *)&fhdr.iv[4]));
	snprintf(fhdr.text, sizeof(fhdr.text), "%s", SADB_KEYCHECK);

	if (write(fd, &fhdr, sizeof(fhdr)) != sizeof(fhdr)) {
		perror("** Error: write()");
		goto finished;
	}
	
	/* Write selected entries only */
	if (iplist != NULL) {

		while (*iplist != NULL) {
    	    struct sadbent *ent = NULL;
			long ip;

        	if ( (ip = net_inetaddr(*iplist)) == -1) {
            	fprintf(stderr, "Failed to resolve target host/IP '%s'\n",
                	*iplist);
            	continue;
        	}

        	*root = get_ent(*root, (u_long)ip, &ent);

        	if (ent == NULL) {
            	fprintf(stderr, "Could not find %s",
                	net_ntoa(ip, NULL));
            	PRINT_HOSTNAME(ip);
            	printf("\n");
            	continue;
        	}

        	if (sadb_writeraw(fd, ent, opt.verbose, 1) < 0) {
            	fprintf(stderr, "Aborting write\n");
            	return;
        	}

        	if (opt.verbose) {
            	fprintf(stderr, "Wrote %s",
                	net_ntoa(ip, NULL));
            	PRINT_HOSTNAME(ip);
            	printf("\n");
        	}
        	iplist++;
		}
	}
	else 
		write_tree(*root, fd);

	/* Get filesize */
	if (fstat(fd, &sb) == -1) {
		perror("** Error: fstat()");
		goto finished;
	}

	/* Map outfile */
	if ( (mmad = mmap(0, sb.st_size, PROT_READ|PROT_WRITE, MAP_FILE|MAP_SHARED,
			fd, 0)) == MAP_FAILED) {
		perror("** Error: mmap()");
		goto finished;
	}

	/* Encrypt */
	bfish_cbc_encrypt((u_char *)mmad + sizeof(fhdr.iv),
			sb.st_size-sizeof(fhdr.iv), fhdr.iv, bk);

	printf("Wrote %u bytes to %s\n", (u_int)sb.st_size, file);

	munmap(mmad, sb.st_size);
finished:
	memset(bk, 0x00, sizeof(struct bfish_key));
	free(bk);
	close(fd);
}

/*
 * Write tree in raw entries to file descriptor
 */
static void
write_tree(struct ent_node *root, int fd)
{
    if (root != NULL) {
        write_tree(root->left, fd);
		if (sadb_writeraw(fd, &root->saent, opt.verbose, 1) < 0) {
			fprintf(stderr, "Aborting write\n");
			return;
		}
        write_tree(root->right, fd);
    }
}


/*
 * Copy entry and insert it into tree
 * Sets *synerr to 1 on syntax error.
 */
static void
copy(struct ent_node **root, u_int *synerr,
	u_int argc, char **argv)
{
	long srcip;
	long destip;
	u_int added = 0;
	size_t set_packets = 1;
	u_char *data;
	struct sadbent *srcent = NULL;
	struct sadbent newent;
	int flag;

    if (argc < 3) {
        (*synerr)++;
        return;
    }

	if ( (srcip = net_inetaddr(argv[1])) == -1) {
		fprintf(stderr, "Failed to resolve target host/IP '%s'\n",
			argv[1]);
		return;
	}
   	
	/* Get the entry to copy */
	*root = get_ent(*root, (u_long)srcip, &srcent);
	if (srcent == NULL) {
		fprintf(stderr, "Error: Could not find source entry %s",
			net_ntoa(srcip, NULL));
		PRINT_HOSTNAME(srcip);
		printf("\n");
		return;
	}
	
	if ( (destip = net_inetaddr(argv[2])) == -1) {
		fprintf(stderr, "Failed to resolve target host/IP '%s'\n", argv[2]);
		return;
	}

	if (destip == srcip) {
		fprintf(stderr, "Source and destination are the same!\n");
		return;
	}

    argc--;argc--;
    argv++;argv++;
    optind = 1;
    while ( (flag = getopt(argc, argv, "p")) !=  -1) {
        switch (flag) {
            case 'p':
                set_packets = 0;
                break;

            default:
                (*synerr)++;
                return;
        }
    }

	/* Copy entry */
	if ( (data = sadb_copyent(srcent, &newent, opt.verbose)) == NULL) {
		fprintf(stderr, "Failed to copy entry\n");
		return;
	}

	/* Set the new target IP address */
	newent.se_hdr->sah_ipv4addr = destip;

	/* Adjust packets, i.e. replace the destination address that matches
	 * the old one (srcip) with the new one (destip) */
	if (set_packets)
		flag = sadb_setdest(&newent, srcip, destip);
	if (opt.verbose && set_packets)
		printf("Updated %u destination addresses in new entry\n", flag);

	/* Insert into tree */
	*root = add_ent(*root, &newent, data, &added);
	if (added == 1) {
		printf("Added %s", net_ntoa(ENTIP(newent), NULL));
		PRINT_HOSTNAME(ENTIP(newent));
		printf("\n");
	}
	else
		free(data);
}

/*
 * The interactive loop
 */
static int
interactive(struct ent_node *root)
{
	u_char input[2048];
	char *argv[IACT_MAXARGS];
	u_int argc;
	u_char ret = 0;
	u_int synerr;

	for (;;) {
		memset(input, 0x00, sizeof(input));
		memset(argv, 0x00, sizeof(char *)*IACT_MAXARGS);
		synerr = 0;
		
		do {
			printf("%s", SADB_PROMPT);
			fflush(stdout);
			fgets(input, sizeof(input), stdin);
			input[strlen(input)-1] = '\0'; 
		} while(!input[0]);

		/* Get arguments */
		argslex(input, &argc, argv, IACT_MAXARGS);
	
		if (!strncasecmp(argv[0], IACT_LIST, 
				sizeof(IACT_LIST)-1)) 
			print_tree(root, 1, 0, 0);

		else if (!strncasecmp(argv[0], IACT_PRINT, 
				sizeof(IACT_PRINT)-1)) 
			print(&root, &synerr, argc, argv);
			
		else if (!strncasecmp(argv[0], IACT_HELP,
				sizeof(IACT_HELP)-1))
			interactive_help(argv[1]);

		else if (!strncasecmp(argv[0], IACT_IMPORT,
				sizeof(IACT_IMPORT)-1)) 
			import(&root, &synerr, argc, argv);

		else if (!strncasecmp(argv[0], IACT_DELETE,
				sizeof(IACT_DELETE)-1))
			delete(&root, &synerr, argc, argv);
		
		else if (!strncasecmp(argv[0], IACT_DUMP,
				sizeof(IACT_DUMP)-1))
			dump(&root, &synerr, argc, argv);
		
		else if (!strncasecmp(argv[0], IACT_SAVE,
				sizeof(IACT_SAVE)-1))
			save(&root, opt.file, opt.key, NULL);
		
		
		else if (!strncasecmp(argv[0], IACT_COPY,
				sizeof(IACT_COPY)-1))
			copy(&root, &synerr, argc, argv);

		else if (!strncasecmp(argv[0], IACT_RESOLVE,
				sizeof(IACT_RESOLVE)-1)) {
			if (argc == 1)
				printf("%s\n", 
					opt.resolve==1?IACT_BOOL_TRUE:IACT_BOOL_FALSE);
			else if (!strncasecmp(argv[1], IACT_BOOL_TRUE,
					sizeof(IACT_BOOL_TRUE)-1))
				opt.resolve=1;
			else if (!strncasecmp(argv[1], IACT_BOOL_FALSE,
					sizeof(IACT_BOOL_FALSE)-1))
				opt.resolve=0;
			else
				synerr++;
		}

        else if (!strncasecmp(argv[0], IACT_AUTOSAVE,
                sizeof(IACT_AUTOSAVE)-1)) {
            if (argc == 1)
                printf("%s\n",
                    opt.autosave==1?IACT_BOOL_TRUE:IACT_BOOL_FALSE);
            else if (!strncasecmp(argv[1], IACT_BOOL_TRUE,
                    sizeof(IACT_BOOL_TRUE)-1))
                opt.autosave=1;
            else if (!strncasecmp(argv[1], IACT_BOOL_FALSE,
                    sizeof(IACT_BOOL_FALSE)-1))
                opt.autosave=0;
            else
                synerr++;
        }

		else if (!strncasecmp(argv[0], IACT_VERBOSE,
				sizeof(IACT_VERBOSE)-1)) {
			if (argc == 1)
				printf("%s\n", 
					opt.verbose>0?IACT_BOOL_TRUE:IACT_BOOL_FALSE);
			else if (!strncasecmp(argv[1], IACT_BOOL_TRUE,
					sizeof(IACT_BOOL_TRUE)-1))
				opt.verbose=1;
			else if (!strncasecmp(argv[1], IACT_BOOL_FALSE,
					sizeof(IACT_BOOL_FALSE)-1))
				opt.verbose=0;
			else
				synerr++;
		}

		else if (!strncasecmp(argv[0], IACT_QUIT,
				sizeof(IACT_QUIT)-1))
			break;

		else 
			fprintf(stderr, "Unrecognized command\n");
	
		if (synerr != 0)
			fprintf(stderr, "Syntax error, 'help' for help\n");
	}

	/* Save on exit */
	if (opt.autosave) {
		printf("Saving entries\n");
		save(&root, opt.file, opt.key, NULL);
	}

	if (opt.key != NULL) {
		memset(opt.key, 0x00, strlen(opt.key));
		free(opt.key);
	}
	free_ents(root);
	printf("Bye!\n");
	return(ret);
}


/* 
 * Print usage and exit
 */
void
usage(u_char *pname)
{
	printf("\nVersion %s\n", VERSION); 
	printf("Usage: %s file [-dhnv]\n", basename(pname));
	printf("    -d       - Target file is not encrypted\n");
	printf("    -h       - This help\n");
	printf("    -n       - Don't resolve hostnames\n");
	printf("    -s       - Save on exit\n");
	printf("    -v       - Verbose output\n\n");

}


int
main(int argc, char *argv[])
{
	struct ent_node *root = NULL;
	u_char decrypt = 1;
    int i;

	/* Default settings */
	opt.file = NULL;
	opt.key = NULL;
	opt.resolve = 1;
	opt.verbose = 0;   /* Verbose level */
	opt.autosave = 0;

	if (argc <= 1) {
		usage(argv[0]);
		exit(EXIT_FAILURE);
	}

	if (argv[1] && argv[1][0] != '-') {
		opt.file = strdup(argv[1]);	
		argc--;
		argv++;
	}

    while ( (i = getopt(argc, argv, "dvhns")) != -1) {
        switch(i) {
            case 'd': 
				if (opt.file == NULL) {
					fprintf(stderr, "No file, try -h for help.\n");
					exit(EXIT_FAILURE);
				}
				decrypt = 0; 
				break;
			case 'h': 
				usage(argv[0]);  
				exit(EXIT_SUCCESS);
            case 'v': opt.verbose++; break;
			case 'n': opt.resolve = 0; break;
			case 's': opt.autosave = 1; break;
            default:
                fprintf(stderr, "try -h for help.\n");
                exit(EXIT_FAILURE);
        }
    }
		
    /* Load all entries in file */
	if (opt.file != NULL) {
    	if ( (i = add_filent(&root, opt.file, decrypt, 1, 0)) == 0)
        	exit(EXIT_FAILURE);
		if (opt.verbose)
			printf("Found %d entries\n", i);
	}

	/* Step into interactive mode */
	exit(interactive(root));
}
