/*	$NetBSD: nsupdate.c,v 1.1.2.1 1999/12/04 16:58:02 he Exp $	*/

#if !defined(lint) && !defined(SABER)
static const char rcsid[] = "Id: nsupdate.c,v 8.21 1999/10/19 22:22:59 cyarnell Exp";
#endif /* not lint */

/*
 * Copyright (c) 1996,1999 by Internet Software Consortium.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
 * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
 * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 */

#include "port_before.h"

#include <sys/param.h>
#include <sys/socket.h>

#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/nameser.h>

#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <netdb.h>
#include <resolv.h>
#include <res_update.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <isc/dst.h>
#include "port_after.h"
#include "../named/db_defs.h"

/* XXX all of this stuff should come from libbind.a */

/*
 * Map class and type names to number
 */
struct map {
	char    token[10];
	int     val;
};

struct map class_strs[] = {
	{ "in",         C_IN },
	{ "chaos",      C_CHAOS },
	{ "hs",         C_HS },
};
#define M_CLASS_CNT (sizeof(class_strs) / sizeof(struct map))

struct map type_strs[] = {
	{ "a",          T_A },
	{ "ns",         T_NS },
	{ "cname",      T_CNAME },
	{ "soa",        T_SOA },
	{ "mb",         T_MB },
	{ "mg",         T_MG },
	{ "mr",         T_MR },
	{ "null",       T_NULL },
	{ "wks",        T_WKS },
	{ "ptr",        T_PTR },
	{ "hinfo",      T_HINFO },
	{ "minfo",      T_MINFO },
	{ "mx",         T_MX },
	{ "txt",        T_TXT },
	{ "rp",         T_RP },
	{ "afsdb",      T_AFSDB },
	{ "x25",        T_X25 },
	{ "isdn",       T_ISDN },
	{ "rt",         T_RT },
	{ "nsap",       T_NSAP },
	{ "nsap_ptr",   T_NSAP_PTR },
	{ "sig",        T_SIG },
	{ "key",        T_KEY },
	{ "px",         T_PX },
	{ "loc",        T_LOC },
	{ "nxt",        T_NXT },
	{ "eid",        T_EID },
	{ "nimloc",     T_NIMLOC },
	{ "srv",        T_SRV },
	{ "atma",       T_ATMA },
	{ "naptr",      T_NAPTR },
	{ "kx",         ns_t_kx },
	{ "cert",       ns_t_cert },
	{ "aaaa",       ns_t_aaaa },
};
#define M_TYPE_CNT (sizeof(type_strs) / sizeof(struct map))

struct map section_strs[] = {
	{ "zone",	S_ZONE },
	{ "prereq",	S_PREREQ },
	{ "update", 	S_UPDATE },
	{ "reserved",	S_ADDT },
};
#define M_SECTION_CNT (sizeof(section_strs) / sizeof(struct map))

struct map opcode_strs[] = {
	{ "nxdomain",	NXDOMAIN },
	{ "yxdomain",	YXDOMAIN },
	{ "nxrrset", 	NXRRSET },
	{ "yxrrset",	YXRRSET },
	{ "delete",	DELETE },
	{ "add",	ADD },
};
#define M_OPCODE_CNT (sizeof(opcode_strs) / sizeof(struct map))

static int getcharstring(char *, char *, int, int, int);
static char *progname;

static void usage(void);
static int getword_str(char *, int, char **, char *);

static struct __res_state res;

int dns_findprimary (res_state, char *, struct ns_tsig_key *, char *,
                     int, struct in_addr *);

/*
 * format of file read by nsupdate is kept the same as the log
 * file generated by updates, so that the log file can be fed
 * to nsupdate to reconstruct lost updates.
 * 
 * file is read on line at a time using fgets() rather than
 * one word at a time using getword() so that it is easy to
 * adapt nsupdate to read piped input from other scripts
 *
 * overloading of class/type has to be deferred to res_update()
 * because class is needed by res_update() to determined the
 * zone to which a resource record belongs
 */
int
main(argc, argv)
	int argc;
	char **argv;
{
	FILE *fp = NULL;
	char buf[BUFSIZ], buf2[BUFSIZ], hostbuf[100], filebuf[100];
	char dnbuf[MAXDNAME], data[MAXDATA];
	u_char packet[PACKETSZ], answer[PACKETSZ];
	char *host = hostbuf, *batchfile = filebuf;
	char *r_dname, *cp, *startp, *endp, *svstartp;
	char section[15], opcode[10];
	int i, c, n, n1, inside, lineno = 0, vc = 0,
		debug = 0, r_size, r_section, r_opcode,
		prompt = 0, ret = 0, stringtobin = 0;
	int16_t r_class, r_type;
	u_int32_t r_ttl;
	struct map *mp;
	ns_updrec *rrecp;
	ns_updque listuprec;
	struct in_addr hostaddr;
	extern int getopt();
	extern char *optarg;
	extern int optind, opterr, optopt;
	ns_tsig_key key;
	char *keyfile=NULL, *keyname=NULL, *p, *pp;
	int file_major, file_minor, alg;



	progname = argv[0];

	while ((c = getopt(argc, argv, "dsvk:n:")) != -1) {
		switch (c) {
		case 'v':
			vc = 1;
			break;
		case 'd':
			debug = 1;
			break;
		case 's':
			stringtobin = 1;
			break;
		case 'k': {
			/* -k keydir:keyname */
			char *colon;
   
			if ((colon=strchr(optarg, ':'))==NULL) {
				fprintf(stderr, "key option argument should be keydir:keyname\n");
				exit(1);
			}
			keyname=colon+1;
			keyfile=optarg;
			*colon='\0';
			break;
		}
		 case 'n':
			keyname=optarg;
			break;
		default:
			usage();
		}
	}

	if (keyfile) {
#ifdef PARSE_KEYFILE
		if ((fp=fopen(keyfile, "r"))==NULL) {
			perror("open keyfile");
			exit(1);
		}
		/* now read the header info from the file */
		if ((i=fread(buf, 1, BUFSIZ, fp)) < 5) {
			fclose(fp);
                	exit(1);
        	}
		fclose(fp);
		fp=NULL;

		p=buf;

		n=strlen(p);		/* get length of strings */
		n1=strlen("Private-key-format: v");
		if (n1 > n || strncmp(buf, "Private-key-format: v", n1)) {
			fprintf(stderr, "Invalid key file format\n");
			exit(1);	/* not a match */
		}
		p+=n1;		/* advance pointer */
		sscanf((char *)p, "%d.%d", &file_major, &file_minor);
		/* should do some error checking with these someday */
		while (*p++!='\n');	/* skip to end of line */

        	n=strlen(p);		/* get length of strings */
        	n1=strlen("Algorithm: ");
        	if (n1 > n || strncmp(p, "Algorithm: ", n1)) {
			fprintf(stderr, "Invalid key file format\n");
                	exit(1);	/* not a match */
		}
		p+=n1;		/* advance pointer */
		if (sscanf((char *)p, "%d", &alg)!=1) {
			fprintf(stderr, "Invalid key file format\n");
			exit(1);
		}
		while (*p++!='\n');	/* skip to end of line */

        	n=strlen(p);		/* get length of strings */
        	n1=strlen("Key: ");
        	if (n1 > n || strncmp(p, "Key: ", n1)) {
			fprintf(stderr, "Invalid key file format\n");
			exit(1);	/* not a match */
		}
		p+=n1;		/* advance pointer */
		pp=p;
		while (*pp++!='\n');	/* skip to end of line, terminate it */
		*--pp='\0';

		key.data=malloc(1024*sizeof(char));
		key.len=b64_pton(p, key.data, 1024);

		strcpy(key.name, keyname);
		strcpy(key.alg, "HMAC-MD5.SIG-ALG.REG.INT");
#else
		/* use the dst* routines to parse the key files
		 * 
		 * This requires that both the .key and the .private files
		 * exist in your cwd, so the keyfile parmeter here is
		 * assumed to be a path in which the K*.{key,private} files
		 * exist.
		 */
		DST_KEY *dst_key;
		char cwd[PATH_MAX+1];

		if (getcwd(cwd, PATH_MAX)==NULL) {
			perror("unable to get current directory");
			exit(1);
		}
		if (chdir(keyfile)<0) {
			fprintf(stderr, "unable to chdir to %s: %s\n", keyfile,
				strerror(errno));
			exit(1);
		}

		dst_init();
		dst_key = dst_read_key(keyname,
				       0 /* not used for private keys */,
				       KEY_HMAC_MD5, DST_PRIVATE);
		if (!dst_key) {
			fprintf(stderr, "dst_read_key: error reading key\n");
			exit(1);
		}
		key.data=malloc(1024*sizeof(char));
		dst_key_to_buffer(dst_key, key.data, 1024);
		key.len=dst_key->dk_key_size;

		strcpy(key.name, keyname);
		strcpy(key.alg, "HMAC-MD5.SIG-ALG.REG.INT");

		if (chdir(cwd)<0) {
			fprintf(stderr, "unable to chdir to %s: %s\n", cwd,
				strerror(errno));
			exit(1);
		}
#endif
	}

	if ((argc - optind) == 0) {
	    /* no file specified, read from stdin */
	    ret = system("tty -s");
	    if (ret == 0) /* terminal */
		prompt = 1;
	    else /* stdin redirect from a file or a pipe */
		prompt = 0;
	} else {
	    /* file specified, open it */
	    /* XXX - currently accepts only one filename */
	    if ((fp = fopen(argv[optind], "r")) == NULL) {
		fprintf(stderr, "error opening file: %s\n", argv[optind]);
		exit (1);
	    }
	}
	for (;;) {

	    inside = 1;
	    if (prompt)
		fprintf(stdout, "> ");
	    if (!fp)
		cp = fgets(buf, sizeof buf, stdin);
	    else
	        cp = fgets(buf, sizeof buf, fp);
	    if (cp == NULL) /* EOF */
		break;
	    lineno++;

	    /* get rid of the trailing newline */
	    n = strlen(buf);
	    buf[--n] = '\0';
 
	    startp = cp;
	    endp = strchr(cp, ';');
	    if (endp != NULL)
		endp--;
	    else
		endp = cp + n - 1;

	    /* verify section name */
	    if (!getword_str(section, sizeof section, &startp, endp)) {
		/* empty line */
		inside = 0;
	    }
	    if (inside) {
		/* inside the same update packet,
		 * continue accumulating records */
		r_section = -1;
		n1 = strlen(section);
		if (section[n1-1] == ':')
		    section[--n1] = '\0';
		for (mp = section_strs; mp < section_strs+M_SECTION_CNT; mp++)
		    if (!strcasecmp(section, mp->token)) {
			r_section = mp->val;
			break;
		    }
		if (r_section == -1) {
		    fprintf(stderr, "incorrect section name: %s\n", section);
		    exit (1);
		}
		if (r_section == S_ZONE) {
		    fprintf(stderr, "section ZONE not permitted\n");
		    exit (1);
		}
		/* read operation code */
		if (!getword_str(opcode, sizeof opcode, &startp, endp)) {
			fprintf(stderr, "failed to read operation code\n");
			exit (1);
		}
		r_opcode = -1;
		if (opcode[0] == '{') {
		    n1 = strlen(opcode);
		    for (i = 0; i < n1; i++)
			opcode[i] = opcode[i+1];
		    if (opcode[n1-2] == '}')
			opcode[n1-2] = '\0';
		}
		for (mp = opcode_strs; mp < opcode_strs+M_OPCODE_CNT; mp++) {
		    if (!strcasecmp(opcode, mp->token)) {
			r_opcode = mp->val;
			break;
		    }
		}
		if (r_opcode == -1) {
		    fprintf(stderr, "incorrect operation code: %s\n", opcode);
		    exit (1);
		}
		/* read owner's domain name */
		if (!getword_str(dnbuf, sizeof dnbuf, &startp, endp)) {
		    fprintf(stderr, "failed to read owner name\n");
		    exit (1);
		}
		r_dname = dnbuf;
		r_ttl = 0;
		r_type = -1;
		r_class = C_IN; /* default to IN */
		r_size = 0;

		(void) getword_str(buf2, sizeof buf2, &startp, endp);

		if (isdigit(buf2[0])) { /* ttl */
		    r_ttl = strtoul(buf2, 0, 10);
		    if (errno == ERANGE && r_ttl == ULONG_MAX) {
			fprintf(stderr, "oversized ttl: %s\n", buf2);
			exit (1);
		    }
		    (void) getword_str(buf2, sizeof buf2, &startp, endp);
		}

		if (buf2[0]) { /* possibly class */
		    for (mp = class_strs; mp < class_strs+M_CLASS_CNT; mp++) {
			if (!strcasecmp(buf2, mp->token)) {
			    r_class = mp->val;
			    (void) getword_str(buf2, sizeof buf2, &startp, endp);
			    break;
			}
		    }
		}
		/*
		 * type and rdata field may or may not be required depending
		 * on the section and operation
		 */
		switch (r_section) {
		case S_PREREQ:
		    if (r_ttl) {
			fprintf(stderr, "nonzero ttl in prereq section: %ul\n",
				r_ttl);
			r_ttl = 0;
		    }
		    switch (r_opcode) {
		    case NXDOMAIN:
		    case YXDOMAIN:
			if (buf2[0]) {
			    fprintf (stderr, "invalid field: %s, ignored\n",
				     buf2);
			    exit (1);
			}
			break;
		    case NXRRSET:
		    case YXRRSET:
			if (buf2[0])
			    for (mp = type_strs; mp < type_strs+M_TYPE_CNT; mp++)
				if (!strcasecmp(buf2, mp->token)) {
				    r_type = mp->val;
				    break;
				}
			if (r_type == -1) {
			    fprintf (stderr, "invalid type for RRset: %s\n",
				     buf2);
			    exit (1);
			}
			if (r_opcode == NXRRSET)
			    break;
			/*
			 * for RRset exists (value dependent) case,
			 * nonempty rdata field will be present.
			 * simply copy the whole string now and let
			 * res_update() interpret the various fields
			 * depending on type
			 */
			cp = startp;
			while (cp <= endp && isspace(*cp))
			    cp++;
			r_size = endp - cp + 1;
			break;
		    default:
			fprintf (stderr,
				 "unknown operation in prereq section\"%s\"\n",
				 opcode);
			exit (1);
		    }
		    break;
		case S_UPDATE:
		    switch (r_opcode) {
		    case DELETE:
			r_ttl = 0;
			r_type = T_ANY;
			/* read type, if specified */
			if (buf2[0])
			    for (mp = type_strs; mp < type_strs+M_TYPE_CNT; mp++)
				if (!strcasecmp(buf2, mp->token)) {
				    r_type = mp->val;
				    svstartp = startp;
				    (void) getword_str(buf2, sizeof buf2,
						       &startp, endp);
				    if (buf2[0]) /* unget preference */
					startp = svstartp;
				    break;
				}
			/* read rdata portion, if specified */
			cp = startp;
			while (cp <= endp && isspace(*cp))
			    cp++;
			r_size = endp - cp + 1;
			break;
		    case ADD:
			if (r_ttl == 0) {
			    fprintf (stderr,
		"ttl must be specified for record to be added: %s\n", buf);
			    exit (1);
			}
			/* read type */
			if (buf2[0])
			    for (mp = type_strs; mp < type_strs+M_TYPE_CNT; mp++)
				if (!strcasecmp(buf2, mp->token)) {
				    r_type = mp->val;
				    break;
				}
			if (r_type == -1) {
			    fprintf(stderr,
		"invalid type for record to be added: %s\n", buf2);
			    exit (1);
			}
			/* read rdata portion */
			cp = startp;
			while (cp < endp && isspace(*cp))
			    cp++;
			r_size = endp - cp + 1;
			if (r_size <= 0) {
			    fprintf(stderr,
		"nonempty rdata field needed to add the record at line %d\n",
				    lineno);
			    exit (1);
			}
			break;
		    default:
			fprintf(stderr,
		"unknown operation in update section \"%s\"\n", opcode);
			exit (1);
		    }
		    break;
		default:
		    fprintf(stderr,
			    "unknown section identifier \"%s\"\n", section);
		    exit (1);
		}

		if ( !(rrecp = res_mkupdrec(r_section, r_dname, r_class,
					    r_type, r_ttl)) ||
		     (r_size > 0 && !(rrecp->r_data = (u_char *)malloc(r_size))) ) {
			if (rrecp)
				res_freeupdrec(rrecp);
			fprintf(stderr, "saverrec error\n");
			exit (1);
		}
        if (stringtobin) {
             switch(r_opcode)  {
             case T_HINFO:
                  if (!getcharstring(buf,(char *)data,2,2,lineno))
                       exit(1);
                  cp = data;
                  break;
             case T_ISDN:
                  if (!getcharstring(buf,(char *)data,1,2,lineno))
                       exit(1);
                  cp = data;
                  break;
             case T_TXT:
                  if (!getcharstring(buf,(char *)data,1,0,lineno))
                       exit(1);
                  cp = data;
                  break;
             case T_X25:
                  if (!getcharstring(buf,(char *)data,1,1,lineno))
                       exit(1);
                  cp = data;
                  break;
             default:
		  break;
             }
        }
		rrecp->r_opcode = r_opcode;
		rrecp->r_size = r_size;
		(void) strncpy((char *)rrecp->r_data, cp, r_size);
		APPEND(listuprec, rrecp, r_link);
	    } else { /* end of an update packet */
		(void) res_ninit(&res);
		if (vc)
		    res.options |= RES_USEVC | RES_STAYOPEN;
		if (debug)
		    res.options |= RES_DEBUG;
		if (!EMPTY(listuprec)) {
			n = res_nupdate(&res, HEAD(listuprec),
					keyfile != NULL ? &key : NULL);
			if (n < 0)
				fprintf(stderr, "failed update packet\n");
			while (!EMPTY(listuprec)) {
				ns_updrec *tmprrecp = HEAD(listuprec);

				UNLINK(listuprec, tmprrecp, r_link);
				if (tmprrecp->r_size != 0)
					free((char *)tmprrecp->r_data);
				res_freeupdrec(tmprrecp);
			}
		}
	    }
	} /* for */
	return (0);
}

static void
usage() {
	fprintf(stderr, "Usage: %s [ -k keydir:keyname ] [-d] [-v] [file]\n",
		progname);
	exit(1);
}

/*
 * Get a whitespace delimited word from a string (not file)
 * into buf. modify the start pointer to point after the
 * word in the string.
 */
static int
getword_str(char *buf, int size, char **startpp, char *endp) {
        char *cp;
        int c;
 
        for (cp = buf; *startpp <= endp; ) {
                c = **startpp;
                if (isspace(c) || c == '\0') {
                        if (cp != buf) /* trailing whitespace */
                                break;
                        else { /* leading whitespace */
                                (*startpp)++;
                                continue;
                        }
                }
                (*startpp)++;
                if (cp >= buf+size-1)
                        break;
                *cp++ = (u_char)c;
        }
        *cp = '\0';
        return (cp != buf);
}

#define MAXCHARSTRING 255

static int
getcharstring(char *buf, char *data,
         int minfields, int maxfields, int lineno)
{
   int nfield = 0, n = 0, i;

   do {
        nfield++;
        i = 0;
        if (*buf == '"') {
             buf++;
             while(buf[i] && buf[i] != '"')
                  i++;
        } else {
             while(isspace(*buf))
                  i++;
        }
        if (i > MAXCHARSTRING) {
             fprintf(stderr,
               "%d: RDATA field %d too long",
               lineno, nfield);
             return(0);
        }
        if (n + i + 1 > MAXDATA) {
             fprintf(stderr,
               "%d: total RDATA too long", lineno);
             return(0);
        }
        data[n]=i;
        memmove(data + 1 + n, buf, i);
        buf += i + 1;
        n += i + 1;
        while(*buf && isspace(*buf))
             buf++;
   } while (nfield < maxfields && *buf);

   if (nfield < minfields) {
        fprintf(stderr,
             "%d: expected %d RDATA fields, only saw %d",
             lineno, minfields, nfield);
        return (0);
   }

   return (n);
}
