/*
 * Copyright (c) 1986, 1988 Regents of the University of California.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms are permitted
 * provided that the above copyright notice and this paragraph are
 * duplicated in all such forms and that any documentation,
 * advertising materials, and other materials related to such
 * distribution and use acknowledge that the software was developed
 * by the University of California, Berkeley.  The name of the
 * University 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 WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#ifndef lint
static char sccsid[] = "@(#)db_dump.c	4.24 (Berkeley) 1/14/89";
#endif /* not lint */

#include <sys/param.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <syslog.h>
#include <arpa/nameser.h>
#include "ns.h"
#include "db.h"

extern	char *p_type(), *p_class();

#ifdef DUMPFILE
char	*dumpfile = DUMPFILE;
#else
char	*dumpfile = "/usr/tmp/named_dump.db";
#endif

extern char *cache_file;

/*
 * Dump current cache in a format similar to RFC 883.
 *
 * We try to be careful and determine whether the operation succeeded
 * so that the new cache file can be installed.
 */

#define DB_ROOT_TIMBUF 3600

doachkpt()
{
    extern int errno;
    FILE *fp;
    char tmpcheckfile[256];

    /* nowhere to checkpoint cache... */
    if (cache_file == NULL) {
#ifdef DEBUG
        if (debug >= 3)
            fprintf(ddt,"doachkpt(to where?)\n");
#endif
        return;
    }

#ifdef DEBUG
    if (debug >= 3)
        fprintf(ddt,"doachkpt()\n");
#endif

    (void) sprintf(tmpcheckfile, "%s.chk", cache_file);
    if ((fp = fopen(tmpcheckfile, "w")) == NULL) {
#ifdef DEBUG
        if (debug >= 3)
            fprintf(ddt,"doachkpt(can't open %s for write)\n", tmpcheckfile);
#endif
        return;
    }

    (void) gettime(&tt);
    fprintf(fp, "; Dumped at %s", ctime(&tt.tv_sec));
    fflush(fp);
    if (ferror(fp)) {
#ifdef DEBUG
        if (debug >= 3)
            fprintf(ddt,"doachkpt(write to checkpoint file failed)\n");
#endif
        return;
    }

    if (fcachetab != NULL) {
	int n;
	if ((n = scan_root(hashtab)) < MINROOTS) {
	    syslog(LOG_ERR, "%d root hints... (too low)", n);
	    fprintf(fp, "; ---- Root hint cache dump ----\n");
	    (void) db_dump(fcachetab, fp, DB_Z_CACHE, "");
	}
    }

    if (hashtab != NULL) {
        fprintf(fp, "; ---- Cache dump ----\n");
        if (db_dump(hashtab, fp, DB_Z_CACHE, "") == NODBFILE) {
#ifdef DEBUG
            if (debug >= 3)
                fprintf(ddt,"doachkpt(checkpoint failed)\n");
#endif
            (void) fclose(fp);
            return;
        }
    }

    (void) fsync(fileno(fp));
    if (fclose(fp) == EOF) {
#ifdef DEBUG
        if (debug >= 3)
            fprintf(ddt,"doachkpt(close failed)\n");
#endif
        return;
    }

    if (rename(tmpcheckfile, cache_file)) {
#ifdef DEBUG
        if (debug >= 3)
            fprintf(ddt,"doachkpt(install %s to %s failed, %d)\n",
                    tmpcheckfile,cache_file, errno);
#endif
    }
}

/*
 * What we do is scan the root hint cache to make sure there are at least
 * MINROOTS root pointers with non-0 TTL's so that the checkpoint will not
 * lose the root.  Failing this, all pointers are written out w/ TTL ~0
 * (root pointers timed out and prime_cache() not done or failed).
 */
#define TIMBUF 300

int
scan_root(htp)
	struct hashbuf *htp;
{
	register struct databuf *dp;
	register struct namebuf *np;
	struct timeval soon;
	int roots = 0;

#ifdef DEBUG
	if (debug)
		fprintf(ddt,"scan_root(0x%x)\n", htp);
#endif

	/* metric by which we determine whether a root NS pointer is still */
	/* valid (will be written out if we do a dump).  we also add some */
	/* time buffer for safety... */
	(void) gettime(&soon);
	soon.tv_sec += TIMBUF;

	for (np = htp->h_tab[0]; np != NULL; np = np->n_next) {
		if (np->n_dname[0] == '\0') {
			dp = np->n_data;
			while (dp != NULL) {
				if (dp->d_type == T_NS &&
				    dp->d_ttl > soon.tv_sec) {
					roots++;
					if (roots >= MINROOTS)
						return (roots);
				}
				dp = dp->d_next;
			}
		}
	}
	return (roots);
}

#ifdef notdef
mark_cache(htp, ttl)
    struct hashbuf *htp;
    int ttl;
{
    register struct databuf *dp;
    register struct namebuf *np;
    struct namebuf **npp, **nppend;
    struct timeval soon;

#ifdef DEBUG
    if (debug)
        fprintf(ddt,"mark_cache()\n");
#endif

    (void) gettime(&soon);
    soon.tv_sec += TIMBUF;

    npp = htp->h_tab;
    nppend = npp + htp->h_size;
    while (npp < nppend) {
        for (np = *npp++; np != NULL; np = np->n_next) {
            if (np->n_data == NULL)
                continue;
            for (dp = np->n_data; dp != NULL; dp = dp->d_next) {
                if (dp->d_ttl < soon.tv_sec)
                    dp->d_ttl = ttl;
            }
        }
    }

    npp = htp->h_tab;
    nppend = npp + htp->h_size;
    while (npp < nppend) {
        for (np = *npp++; np != NULL; np = np->n_next) {
            if (np->n_hash == NULL)
                continue;
            mark_cache(np->n_hash, ttl);
        }
    }
}
#endif notdef

/*
 * Dump current data base in a format similar to RFC 883.
 */

doadump()
{
	FILE	*fp;

#ifdef DEBUG
	if (debug >= 3)
		fprintf(ddt,"doadump()\n");
#endif

	if ((fp = fopen(dumpfile, "w")) == NULL)
		return;
	gettime(&tt);
	fprintf(fp, "; Dumped at %s", ctime(&tt.tv_sec));
	fprintf(fp, "; --- Cache & Data ---\n");
	if (hashtab != NULL)
		(void) db_dump(hashtab, fp, DB_Z_ALL, "");
	fprintf(fp, "; --- Hints ---\n");
	if (fcachetab != NULL)
		(void) db_dump(fcachetab, fp, DB_Z_ALL, "");
	(void) fclose(fp);
}

#ifdef ALLOW_UPDATES
/* Create a disk database to back up zones 
 */
zonedump(zp)
    register struct zoneinfo *zp;
{
	FILE	*fp;
	char *fname;
	struct hashbuf *htp;
	char *op;
	struct stat st;

	/* Only dump zone if there is a cache specified */
	if (zp->z_source && *(zp->z_source)) {
#ifdef DEBUG
	    if (debug)
		    fprintf(ddt, "zonedump(%s)\n", zp->z_source);
#endif

	    if ((fp = fopen(zp->z_source, "w")) == NULL)
		    return;
	    if (op = index(zp->z_origin, '.'))
		op++;
	    gettime(&tt);
	    htp = hashtab;
	    if (nlookup(zp->z_origin, &htp, &fname, 0) != NULL) {
		    db_dump(htp, fp, zp-zones, (op == NULL ? "" : op));
#ifdef ALLOW_UPDATES
		    zp->hasChanged = 0;		/* Checkpointed */
#endif ALLOW_UPDATES
	    }
	    (void) fclose(fp);
	    if (stat(zp->z_source, &st) == 0)
		    zp->z_ftime = st.st_mtime;
	}
#ifdef DEBUG
	    else if (debug)
		fprintf(ddt, "zonedump: no zone to dump\n");
#endif
}
#endif

int
db_dump(htp, fp, zone, origin)
	int zone;
	struct hashbuf *htp;
	FILE *fp;
	char *origin;
{
	register struct databuf *dp;
	register struct namebuf *np;
	struct namebuf **npp, **nppend;
	char dname[MAXDNAME];
	u_long n;
	u_long addr;
	u_short i;
	int j;
	register u_char *cp;
	char *proto;
	extern char *inet_ntoa(), *protocolname(), *servicename();
	int found_data, tab, printed_origin = 0;

	npp = htp->h_tab;
	nppend = npp + htp->h_size;
	while (npp < nppend) {
	    for (np = *npp++; np != NULL; np = np->n_next) {
		if (np->n_data == NULL)
			continue;
		/* Blecch - can't tell if there is data here for the
		 * right zone, so can't print name yet
		 */
		found_data = 0;
		/* we want a snapshot in time... */
		for (dp = np->n_data; dp != NULL; dp = dp->d_next) {
			/* Is the data for this zone? */
			if (zone != DB_Z_ALL && dp->d_zone != zone)
			    continue;
			if (dp->d_zone == DB_Z_CACHE &&
			    dp->d_ttl <= tt.tv_sec &&
			    (dp->d_flags & DB_F_HINT) == 0)
				continue;
			if (!printed_origin) {
			    fprintf(fp, "$ORIGIN %s.\n", origin);
			    printed_origin++;
			}
			tab = 0;
			if (!found_data) {
			    if (np->n_dname[0] == 0) {
				if (origin[0] == 0)
				    fprintf(fp, ".\t");
				else
				    fprintf(fp, ".%s.\t", origin); /* ??? */
			    } else
				fprintf(fp, "%s\t", np->n_dname);
			    if (strlen(np->n_dname) < 8)
				tab = 1;
			    found_data++;
			} else {
			    (void) putc('\t', fp);
			    tab = 1;
			}
			if (dp->d_zone == DB_Z_CACHE) {
			    if (dp->d_flags & DB_F_HINT &&
			        (long)(dp->d_ttl - tt.tv_sec) < DB_ROOT_TIMBUF)
				    fprintf(fp, "%d\t", DB_ROOT_TIMBUF);
			    else
				    fprintf(fp, "%d\t",
				        (int)(dp->d_ttl - tt.tv_sec));
			} else if (dp->d_ttl != 0 &&
			    dp->d_ttl != zones[dp->d_zone].z_minimum)
				fprintf(fp, "%d\t", (int)dp->d_ttl);
			else if (tab)
			    (void) putc('\t', fp);
			fprintf(fp, "%s\t%s\t", p_class(dp->d_class),
				p_type(dp->d_type));
			cp = (u_char *)dp->d_data;
			/*
			 * Print type specific data
			 */
			switch (dp->d_type) {
			case T_A:
				switch (dp->d_class) {
				case C_IN:
					GETLONG(n, cp);
					n = htonl(n);
					fprintf(fp, "%s",
					   inet_ntoa(*(struct in_addr *)&n));
					break;
				}
				if (dp->d_nstime)
					fprintf(fp, "\t; %d", dp->d_nstime);
				fprintf(fp, "\n");
				break;
			case T_CNAME:
			case T_MB:
			case T_MG:
			case T_MR:
			case T_PTR:
				if (cp[0] == '\0')
					fprintf(fp, ".\n");
				else
					fprintf(fp, "%s.\n", cp);
				break;

			case T_NS:
				cp = (u_char *)dp->d_data;
				if (cp[0] == '\0')
					fprintf(fp, ".\t");
				else
					fprintf(fp, "%s.", cp);
				if (dp->d_nstime)
					fprintf(fp, "\t; %d???", dp->d_nstime);
				fprintf(fp, "\n");
				break;

			case T_HINFO:
				if (n = *cp++) {
					fprintf(fp, "\"%.*s\"", (int)n, cp);
					cp += n;
				} else
					fprintf(fp, "\"\"");
				if (n = *cp++)
					fprintf(fp, " \"%.*s\"", (int)n, cp);
				else
					fprintf(fp, "\"\"");
				(void) putc('\n', fp);
				break;

			case T_SOA:
				fprintf(fp, "%s.", cp);
				cp += strlen(cp) + 1;
				fprintf(fp, " %s. (\n", cp);
				cp += strlen(cp) + 1;
				GETLONG(n, cp);
				fprintf(fp, "\t\t%lu", n);
				GETLONG(n, cp);
				fprintf(fp, " %lu", n);
				GETLONG(n, cp);
				fprintf(fp, " %lu", n);
				GETLONG(n, cp);
				fprintf(fp, " %lu", n);
				GETLONG(n, cp);
				fprintf(fp, " %lu )\n", n);
				break;

			case T_MX:
				GETSHORT(n, cp);
				fprintf(fp,"%lu", n);
				fprintf(fp," %s.\n", cp);
				break;


			case T_UINFO:
				fprintf(fp, "\"%s\"\n", cp);
				break;

			case T_UID:
			case T_GID:
				if (dp->d_size == sizeof(u_long)) {
					GETLONG(n, cp);
					fprintf(fp, "%lu\n", n);
				}
				break;

			case T_WKS:
				GETLONG(addr, cp);	
				addr = htonl(addr);	
				fprintf(fp,"%s ",
				    inet_ntoa(*(struct in_addr *)&addr));
				proto = protocolname(*cp);
				cp += sizeof(char); 
				fprintf(fp, "%s ", proto);
				i = 0;
				while(cp < (u_char *)dp->d_data + dp->d_size) {
					j = *cp++;
					do {
					    if (j & 0200)
						fprintf(fp," %s",
						   servicename(i, proto));
					    j <<= 1;
					} while(++i & 07);
				} 
				fprintf(fp,"\n");
				break;

			case T_MINFO:
				fprintf(fp, "%s.", cp);
				cp += strlen(cp) + 1;
				fprintf(fp, " %s.\n", cp);
				break;
#ifdef ALLOW_T_UNSPEC
			case T_UNSPEC:
				/* Dump binary data out in an ASCII-encoded
				   format */
				{
					/* Allocate more than enough space:
					 *  actually need 5/4 size + 20 or so
					 */
					int TmpSize = 2 * dp->d_size + 30;
					char *TmpBuf = (char *) malloc(TmpSize);
					if (TmpBuf == NULL) {
#ifdef DEBUG
					    if (debug)
						fprintf(ddt, "Dump T_UNSPEC: malloc returned NULL\n");
#endif DEBUG
						syslog(LOG_ERR, "Dump T_UNSPEC: malloc: %m");
					}
					if (btoa(cp, dp->d_size, TmpBuf,
						 TmpSize) == CONV_OVERFLOW) {
#ifdef DEBUG
						if (debug)
						    fprintf(ddt, "Dump T_UNSPEC: Output buffer overflow\n");
#endif DEBUG
						    syslog(LOG_ERR, "Dump T_UNSPEC: Output buffer overflow\n");
					} else
						fprintf(fp, "%s\n", TmpBuf);
				}
				break;
#endif ALLOW_T_UNSPEC
			default:
				fprintf(fp, "???\n");
			}
		}
	    }
	}
        if (ferror(fp))
            return(NODBFILE);

	npp = htp->h_tab;
	nppend = npp + htp->h_size;
	while (npp < nppend) {
	    for (np = *npp++; np != NULL; np = np->n_next) {
		if (np->n_hash == NULL)
			continue;
		getname(np, dname, sizeof(dname));
		if (db_dump(np->n_hash, fp, zone, dname) == NODBFILE)
		    return(NODBFILE);
	    }
	}
	return(OK);
}

#ifdef ALLOW_T_UNSPEC
/*
 * Subroutines to convert between 8 bit binary bytes and printable ASCII.
 * Computes the number of bytes, and three kinds of simple checksums.
 * Incoming bytes are collected into 32-bit words, then printed in base 85:
 *	exp(85,5) > exp(2,32)
 * The ASCII characters used are between '!' and 'u';
 * 'z' encodes 32-bit zero; 'x' is used to mark the end of encoded data.
 *
 * Originally by Paul Rutter (philabs!per) and Joe Orost (petsd!joe) for
 * the atob/btoa programs, released with the compress program, in mod.sources.
 * Modified by Mike Schwartz 8/19/86 for use in BIND.
 */

/* Make sure global variable names are unique */
#define Ceor T_UNSPEC_Ceor
#define Csum T_UNSPEC_Csum
#define Crot T_UNSPEC_Crot
#define word T_UNSPEC_word
#define bcount T_UNSPEC_bcount

static long int Ceor, Csum, Crot, word, bcount;

#define EN(c)	((int) ((c) + '!'))
#define DE(c) ((c) - '!')
#define AddToBuf(bufp, c) **bufp = c; (*bufp)++;
#define streq(s0, s1)	strcmp(s0, s1) == 0
#define times85(x)	((((((x<<2)+x)<<2)+x)<<2)+x)


/* Decode ASCII-encoded byte c into binary representation and 
 * place into *bufp, advancing bufp 
 */
static int
byte_atob(c, bufp)
	register c;
	char **bufp;
{
	if (c == 'z') {
		if (bcount != 0)
			return(CONV_BADFMT);
		else {
			putbyte(0, bufp);
			putbyte(0, bufp);
			putbyte(0, bufp);
			putbyte(0, bufp);
		}
	} else if ((c >= '!') && (c < ('!' + 85))) {
		if (bcount == 0) {
			word = DE(c);
			++bcount;
		} else if (bcount < 4) {
			word = times85(word);
			word += DE(c);
			++bcount;
		} else {
			word = times85(word) + DE(c);
			putbyte((int)((word >> 24) & 255), bufp);
			putbyte((int)((word >> 16) & 255), bufp);
			putbyte((int)((word >> 8) & 255), bufp);
			putbyte((int)(word & 255), bufp);
			word = 0;
			bcount = 0;
		}
	} else
		return(CONV_BADFMT);
	return(CONV_SUCCESS);
}

/* Compute checksum info and place c into *bufp, advancing bufp */
static
putbyte(c, bufp)
	register c;
	char **bufp;
{
	Ceor ^= c;
	Csum += c;
	Csum += 1;
	if ((Crot & 0x80000000)) {
		Crot <<= 1;
		Crot += 1;
	} else {
		Crot <<= 1;
	}
	Crot += c;
	AddToBuf(bufp, c);
}

/* Read the ASCII-encoded data from inbuf, of length inbuflen, and convert
   it into T_UNSPEC (binary data) in outbuf, not to exceed outbuflen bytes;
   outbuflen must be divisible by 4.  (Note: this is because outbuf is filled
   in 4 bytes at a time.  If the actual data doesn't end on an even 4-byte
   boundary, there will be no problem...it will be padded with 0 bytes, and
   numbytes will indicate the correct number of bytes.  The main point is
   that since the buffer is filled in 4 bytes at a time, even if there is
   not a full 4 bytes of data at the end, there has to be room to 0-pad the
   data, so the buffer must be of size divisible by 4).  Place the number of
   output bytes in numbytes, and return a failure/success status  */
int
atob(inbuf, inbuflen, outbuf, outbuflen, numbytes)
	char *inbuf;
	int inbuflen;
	char *outbuf;
	int outbuflen;
	int *numbytes;
{
	int inc, nb;
	long int oeor, osum, orot;
	char *inp, *outp = outbuf, *endoutp = &outbuf[outbuflen];

	if ( (outbuflen % 4) != 0)
		return(CONV_BADBUFLEN);
	Ceor = Csum = Crot = word = bcount = 0;
	for (inp = inbuf, inc = 0; inc < inbuflen; inp++, inc++) {
		if (outp > endoutp)
			return(CONV_OVERFLOW);
		if (*inp == 'x') {
			inp +=2;
			break;
		} else {
			if (byte_atob(*inp, &outp) == CONV_BADFMT)
				return(CONV_BADFMT);
		}
	}

	/* Get byte count and checksum information from end of buffer */
	if(sscanf(inp, "%ld %lx %lx %lx", numbytes, &oeor, &osum, &orot) != 4)
		return(CONV_BADFMT);
	if ((oeor != Ceor) || (osum != Csum) || (orot != Crot))
		return(CONV_BADCKSUM);
	return(CONV_SUCCESS);
}

/* Encode binary byte c into ASCII representation and place into *bufp,
   advancing bufp */
static
byte_btoa(c, bufp)
	register c;
	char **bufp;
{
	Ceor ^= c;
	Csum += c;
	Csum += 1;
	if ((Crot & 0x80000000)) {
		Crot <<= 1;
		Crot += 1;
	} else {
		Crot <<= 1;
	}
	Crot += c;

	word <<= 8;
	word |= c;
	if (bcount == 3) {
		if (word == 0) {
			AddToBuf(bufp, 'z');
		} else {
		    register int tmp = 0;
		    register long int tmpword = word;
			
		    if (tmpword < 0) {	
			   /* Because some don't support unsigned long */
		    	tmp = 32;
		    	tmpword -= (long)(85 * 85 * 85 * 85 * 32);
		    }
		    if (tmpword < 0) {
		    	tmp = 64;
		    	tmpword -= (long)(85 * 85 * 85 * 85 * 32);
		    }
		    AddToBuf(bufp,
		    	 EN((tmpword / (long)(85 * 85 * 85 * 85)) + tmp));
		    tmpword %= (long)(85 * 85 * 85 * 85);
		    AddToBuf(bufp, EN(tmpword / (85 * 85 * 85)));
		    tmpword %= (85 * 85 * 85);
		    AddToBuf(bufp, EN(tmpword / (85 * 85)));
		    tmpword %= (85 * 85);
		    AddToBuf(bufp, EN(tmpword / 85));
		    tmpword %= 85;
		    AddToBuf(bufp, EN(tmpword));
		}
		bcount = 0;
	} else {
		bcount += 1;
	}
}


/*
 * Encode the binary data from inbuf, of length inbuflen, into a
 * null-terminated ASCII representation in outbuf, not to exceed outbuflen
 * bytes. Return success/failure status
 */
int
btoa(inbuf, inbuflen, outbuf, outbuflen)
	char *inbuf;
	int inbuflen;
	char *outbuf;
	int outbuflen;
{
	long int inc, nb;
	long int oeor, osum, orot;
	char *inp, *outp = outbuf, *endoutp = &outbuf[outbuflen -1];

	Ceor = Csum = Crot = word = bcount = 0;
	for (inp = inbuf, inc = 0; inc < inbuflen; inp++, inc++) {
		byte_btoa((unsigned char) (*inp), &outp);
		if (outp >= endoutp)
			return(CONV_OVERFLOW);
	}
	while (bcount != 0) {
		byte_btoa(0, &outp);
		if (outp >= endoutp)
			return(CONV_OVERFLOW);
	}
	/* Put byte count and checksum information at end of buffer, delimited
	   by 'x' */
	(void) sprintf(outp, "x %ld %lx %lx %lx", inbuflen, Ceor, Csum, Crot);
	if (&outp[strlen(outp) - 1] >= endoutp)
		return(CONV_OVERFLOW);
	else
		return(CONV_SUCCESS);
}
#endif ALLOW_T_UNSPEC
