diff options
Diffstat (limited to 'contrib/bind/bin/named/ns_update.c')
-rw-r--r-- | contrib/bind/bin/named/ns_update.c | 2393 |
1 files changed, 2393 insertions, 0 deletions
diff --git a/contrib/bind/bin/named/ns_update.c b/contrib/bind/bin/named/ns_update.c new file mode 100644 index 0000000..e6fdd8d --- /dev/null +++ b/contrib/bind/bin/named/ns_update.c @@ -0,0 +1,2393 @@ +#if !defined(lint) && !defined(SABER) +static char rcsid[] = "$Id: ns_update.c,v 8.24 1998/03/20 00:49:16 halley Exp $"; +#endif /* not lint */ + +/* + * Copyright (c) 1996, 1997 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. + */ + +/* + * Based on the Dynamic DNS reference implementation by Viraj Bais + * <viraj_bais@ccm.fm.intel.com> + */ + +#include "port_before.h" + +#include <sys/param.h> +#include <sys/uio.h> +#include <sys/file.h> +#include <sys/socket.h> +#include <sys/stat.h> + +#include <netinet/in.h> +#include <arpa/nameser.h> +#include <arpa/inet.h> + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <resolv.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <syslog.h> +#include <time.h> +#include <unistd.h> + +#include <isc/eventlib.h> +#include <isc/logging.h> +#include <isc/memcluster.h> + +#include "port_after.h" + +#include "named.h" + +#define WRITEABLE_MASK (S_IWUSR | S_IWGRP | S_IWOTH) + +/* XXXRTH almost all funcs. in here should be static! + map rdata_dump to db_to_textual + map rdata_expand to wire_to_db + make a textual_to_db and use it in merge_logs? + replace all this "map" stuff with the new routines (from 4.9.5 I think) + */ + +/* from ns_req.c */ + +static struct map m_opcode[] = { + { "nxdomain", NXDOMAIN }, + { "yxdomain", YXDOMAIN }, + { "nxrrset", NXRRSET }, + { "yxrrset", YXRRSET }, + { "delete", DELETE }, + { "add", ADD }, +}; +#define M_OPCODE_CNT (sizeof(m_opcode) / sizeof(struct map)) + +/* XXXRTH workaround map difficulties */ +#define M_CLASS_CNT m_class_cnt +#define M_TYPE_CNT m_type_cnt + +static char *opcodes[] = { + "delete", + "add", + "", + "nxdomain", + "", + "", + "yxdomain", + "yxrrset", + "nxrrset", + "", + "", +}; + + +/* from db_load.c */ + +static struct map m_section[] = { + { "zone", S_ZONE }, + { "prereq", S_PREREQ }, + { "update", S_UPDATE }, + { "reserved", S_ADDT }, +}; +#define M_SECTION_CNT (sizeof(m_section) / sizeof(struct map)) + + +/* from ns_req.c */ + +static ns_updrec *rrecp_start = NULL, *rrecp_last = NULL; + + +/* forward */ +static int findzone(const char *, int, int, int *, int); +static int rdata_expand(const u_char *, const u_char *, const u_char *, + u_int, size_t, u_char *, size_t); + + +static FILE * +open_transaction_log(struct zoneinfo *zp) { + FILE *fp; + + fp = fopen(zp->z_updatelog, "a+"); + if (fp == NULL) { + ns_error(ns_log_update, "can't open %s: %s", zp->z_updatelog, + strerror(errno)); + return (NULL); + } + if (ftell(fp) == 0L) { + fprintf(fp, "%s", LogSignature); + } + return (fp); +} + + +static int +close_transaction_log(struct zoneinfo *zp, FILE *fp) { + if (fflush(fp) == EOF) { + ns_error(ns_log_update, "fflush() of %s failed: %s", + zp->z_updatelog, strerror(errno)); + return (-1); + } + if (fsync(fileno(fp)) < 0) { + ns_error(ns_log_update, "fsync() of %s failed: %s", + zp->z_updatelog, strerror(errno)); + return (-1); + } + if (fclose(fp) == EOF) { + ns_error(ns_log_update, "fclose() of %s failed: %s", + zp->z_updatelog, strerror(errno)); + return (-1); + } + return (0); +} + +/* + * printupdatelog(srcaddr, firstp, hp, zp, old_serial) + * append an ascii form to the zone's transaction log file. + */ +static void +printupdatelog(struct sockaddr_in srcaddr, + ns_updrec *firstp, + HEADER *hp, + struct zoneinfo *zp, + u_int32_t old_serial) +{ + struct databuf *dp; + struct map *mp; + ns_updrec *rrecp; + int opcode; + char time[25]; + FILE *fp; + + if (!firstp) + return; + + fp = open_transaction_log(zp); + if (fp == NULL) + return; + + sprintf(time, "at %lu", (u_long)tt.tv_sec); + fprintf(fp, "[DYNAMIC_UPDATE] id %u from %s %s (named pid %ld):\n", + hp->id, sin_ntoa(srcaddr), time, (long)getpid()); + for (rrecp = firstp; rrecp; rrecp = rrecp->r_next) { + INSIST(zp == &zones[rrecp->r_zone]); + switch (rrecp->r_section) { + case S_ZONE: + fprintf(fp, "zone:\torigin %s class %s serial %u\n", + zp->z_origin, p_class(zp->z_class), + old_serial); + break; + case S_PREREQ: + opcode = rrecp->r_opcode; + fprintf(fp, "prereq:\t{%s} %s. %s ", + opcodes[opcode], rrecp->r_dname, + p_class(zp->z_class)); + if (opcode == NXRRSET || opcode == YXRRSET) { + fprintf(fp, "%s ", p_type(rrecp->r_type)); + if ((dp = rrecp->r_dp) && dp->d_size > 0) { + dp->d_class = zp->z_class; + (void) rdata_dump(dp, fp); + } + } + fprintf(fp, "\n"); + break; + case S_UPDATE: + opcode = rrecp->r_opcode; + fprintf(fp, "update:\t{%s} %s. ", + opcodes[opcode], rrecp->r_dname); + if (opcode == ADD) + fprintf(fp, "%u ", rrecp->r_ttl); + fprintf(fp, "%s ", p_class(zp->z_class)); + if (rrecp->r_type != T_ANY) + fprintf(fp, "%s ", p_type(rrecp->r_type)); + if ((dp = rrecp->r_dp) && dp->d_size > 0) { + dp->d_class = zp->z_class; + (void) rdata_dump(dp, fp); + } + fprintf(fp, "\n"); + break; + case S_ADDT: + break; + default: + ns_panic(ns_log_update, 1, + "printupdatelog - impossible condition"); + /*NOTREACHED*/ + } + } + fprintf(fp, "\n"); + (void) close_transaction_log(zp, fp); +} + +static void +cancel_soa_update(struct zoneinfo *zp) { + ns_debug(ns_log_update, 3, "cancel_soa_update for %s", zp->z_origin); + zp->z_flags &= ~Z_NEED_SOAUPDATE; + zp->z_soaincrtime = 0; + zp->z_updatecnt = 0; +} + +/* + * Figure out when a SOA serial number update should happen. + * Returns non-zero if the caller should call sched_zone_maint(zp). + */ +int +schedule_soa_update(struct zoneinfo *zp, int numupdated) { + (void) gettime(&tt); + + zp->z_flags |= Z_NEED_SOAUPDATE; + + /* + * Only z_deferupdcnt updates are allowed before we force + * a serial update. + */ + zp->z_updatecnt += numupdated; + if (zp->z_updatecnt >= zp->z_deferupdcnt) { + if (incr_serial(zp) < 0) { + ns_error(ns_log_update, + "error updating serial number for %s from %d", + zp->z_origin, zp->z_serial); + } else + return (0); + /* + * Note we continue scheduling if for some reason + * incr_serial fails. + */ + } + + if (zp->z_soaincrintvl > 0) { + /* We want automatic updates in this zone. */ + if (zp->z_soaincrtime > 0) { + /* Already scheduled. */ + ns_debug(ns_log_update, 3, + "schedule_soa_update('%s'): already scheduled", + zp->z_origin); + return (0); + } else { + /* First update since the soa was last incremented. */ + zp->z_updatecnt = numupdated; + zp->z_soaincrtime = tt.tv_sec + zp->z_soaincrintvl; + /* + * Never schedule soaincrtime to occur after + * dumptime. + */ + if (zp->z_soaincrtime > zp->z_dumptime) + zp->z_soaincrtime = zp->z_dumptime; + ns_debug(ns_log_update, 3, + "schedule_soa_update('%s'): scheduled for %lu", + zp->z_origin, (u_long)zp->z_soaincrtime); + return (1); + } + } + return (0); +} + +/* + * Figure out when a zone dump should happen. + * Returns non-zero if the caller should call sched_zone_maint(zp). + */ +int +schedule_dump(struct zoneinfo *zp) { + time_t half; + + (void) gettime(&tt); + + zp->z_flags |= Z_NEED_DUMP; + + if (zp->z_dumpintvl > 0) { + /* We want automatic dumping in this zone. */ + if (zp->z_dumptime > 0) { + /* Already scheduled. */ + ns_debug(ns_log_update, 3, + "schedule_dump('%s'): already scheduled", + zp->z_origin); + return (0); + } else { + /* + * Set new dump time for dynamic zone. Use a random + * number in the last half of the dump limit; we want + * it to be substantially correct while still + * preventing dump synchronization among various + * dynamic zones. + */ + half = (zp->z_dumpintvl + 1) / 2; + zp->z_dumptime = tt.tv_sec + half + (rand() % half); + /* + * Never schedule soaincrtime to occur after + * dumptime. + */ + if (zp->z_soaincrtime > zp->z_dumptime) + zp->z_soaincrtime = zp->z_dumptime; + ns_debug(ns_log_update, 3, + "schedule_dump('%s'): scheduled for %lu", + zp->z_origin, (u_long)zp->z_dumptime); + return (1); + } + } + return (0); +} + +/* + * int + * process_prereq(rec, rcodep) + * Process one prerequisite. + * returns: + * >0 prerequisite was satisfied. + * =0 prerequisite was not satisfied, or an error occurred. + * side effects: + * sets *rcodep if an error occurs or prerequisite isn't satisfied. + */ +static int +process_prereq(ns_updrec *ur, int *rcodep, u_int16_t zclass) { + const char *dname = ur->r_dname; + u_int16_t class = ur->r_class; + u_int16_t type = ur->r_type; + u_int32_t ttl = ur->r_ttl; + struct databuf *rdp = ur->r_dp; + const char *fname; + struct hashbuf *htp; + struct namebuf *np; + struct databuf *dp; + + /* + * An element in the list might have already been + * processed if it is in the same RRset as a previous + * RRset Exists (value dependent) prerequisite. + */ + if (rdp && (rdp->d_mark & D_MARK_FOUND) != 0) { + /* Already processed. */ + return (1); + } + if (ttl != 0) { + ns_debug(ns_log_update, 1, + "process_prereq: ttl!=0 in prereq section"); + *rcodep = FORMERR; + return (0); + } + htp = hashtab; + np = nlookup(dname, &htp, &fname, 0); + if (fname != dname) + np = NULL; /* Matching by wildcard not allowed here. */ + if (class == C_ANY) { + if (rdp->d_size) { + ns_debug(ns_log_update, 1, + "process_prereq: empty rdata required in prereq section with class=ANY"); + *rcodep = FORMERR; + return (0); + } + if (type == T_ANY) { + /* Name is in use. */ + ur->r_opcode = YXDOMAIN; + if (np == NULL || np->n_data == NULL) { + /* + * Name does not exist or is + * an empty nonterminal. + */ + ns_debug(ns_log_update, 1, + "process_prereq: %s not in use", + dname); + *rcodep = NXDOMAIN; + return (0); + } + } else { + /* RRset exists (value independent). */ + int found = 0; + + ur->r_opcode = YXRRSET; + if (np != NULL) + for (dp = np->n_data; + dp && !found; + dp = dp->d_next) + if (match(dp, class, type)) + found = 1; + if (!found) { + ns_debug(ns_log_update, 1, + "process_prereq: RRset (%s,%s,%s) does not exist", + dname, p_type(type), p_class(zclass)); + *rcodep = NXRRSET; + return (0); + } + } + } else if (class == C_NONE) { + if (rdp->d_size) { + ns_debug(ns_log_update, 1, + "process_prereq: empty rdata required in prereq section with class=NONE"); + *rcodep = FORMERR; + return (0); + } + if (type == T_ANY) { + /* Name is not in use. */ + ur->r_opcode = NXDOMAIN; + if (np != NULL && np->n_data != NULL) { + /* + * Name exists and is not an + * empty nonterminal. + */ + ns_debug(ns_log_update, 1, + "process_prereq: %s exists", + dname); + *rcodep = YXDOMAIN; + return (0); + } + } else { + /* RRset does not exist. */ + int found = 0; + + ur->r_opcode = NXRRSET; + class = zclass; + if (np != NULL) + for (dp = np->n_data; + dp && !found; + dp = dp->d_next) + if (match(dp, class, type)) + found = 1; + if (found) { + ns_debug(ns_log_update, 1, + "process_prereq: RRset (%s,%s) exists", + dname, p_type(type)); + *rcodep = YXRRSET; + return (0); + } + } + } else if (class == zclass) { + /* + * RRset exists (value dependent). + * + * Check for RRset equality also. + */ + ns_updrec *tmp; + + ur->r_opcode = YXRRSET; + if (!rdp) { + ns_debug(ns_log_update, 1, + "process_prereq: nonempty rdata required in prereq section with class=%s", + p_class(class)); + *rcodep = FORMERR; + return (0); + } + htp = hashtab; + np = nlookup(dname, &htp, &fname, 0); + if (np == NULL || fname != dname) { + *rcodep = NXRRSET; + return (0); + } + for (dp = np->n_data; dp; dp = dp->d_next) { + if (match(dp, class, type)) { + int found = 0; + + for (tmp = ur; + tmp && !found; + tmp = tmp->r_next) { + if (tmp->r_section != S_PREREQ) + break; + if (!db_cmp(dp, tmp->r_dp)) { + tmp->r_dp->d_mark |= + D_MARK_FOUND; + found = 1; + } + } + if (!found) { + *rcodep = NXRRSET; + return (0); + } + } + } + for (tmp = ur; tmp; tmp = tmp->r_next) + if (tmp->r_section == S_PREREQ && + !strcasecmp(dname, tmp->r_dname) && + tmp->r_class == class && + tmp->r_type == type && + (ur->r_dp->d_mark & D_MARK_FOUND) == 0) { + *rcodep = NXRRSET; + return (0); + } else { + tmp->r_opcode = YXRRSET; + } + } else { + ns_debug(ns_log_update, 1, + "process_prereq: incorrect class %s", + p_class(class)); + *rcodep = FORMERR; + return (0); + } + /* Through the gauntlet, and out. */ + return (1); +} + +/* + * int + * prescan_update(ur, rcodep) + * Process one prerequisite. + * returns: + * >0 update looks OK (format wise; who knows if it will succeed?) + * =0 update has something wrong with it. + * side effects: + * sets *rcodep if an error occurs or prerequisite isn't satisfied. + */ +static int +prescan_update(ns_updrec *ur, int *rcodep, u_int16_t zclass) { + const char *dname = ur->r_dname; + u_int16_t class = ur->r_class; + u_int16_t type = ur->r_type; + u_int32_t ttl = ur->r_ttl; + struct databuf *rdp = ur->r_dp; + const char *fname; + struct hashbuf *htp; + struct namebuf *np; + + if (class == zclass) { + if (type == T_ANY || + type == T_AXFR || type == T_IXFR || + type == T_MAILA || type == T_MAILB) { + ns_debug(ns_log_update, 1, + "prescan_update: invalid type (%s)", + p_type(type)); + *rcodep = FORMERR; + return (0); + } + } else if (class == C_ANY) { + if (ttl != 0 || rdp->d_size || + type == T_AXFR || type == T_IXFR || + type == T_MAILA || type == T_MAILB) { + ns_debug(ns_log_update, 1, + "prescan_update: formerr(#2)"); + *rcodep = FORMERR; + return (0); + } + } else if (class == C_NONE) { + if (ttl != 0 || type == T_ANY || + type == T_AXFR || type == T_IXFR || + type == T_MAILA || type == T_MAILB) { + ns_debug(ns_log_update, 1, + "prescan_update: formerr(#3)"); + *rcodep = FORMERR; + return (0); + } + } else { + ns_debug(ns_log_update, 1, + "prescan_update: invalid class (%s)", + p_class(class)); + *rcodep = FORMERR; + return (0); + } + /* No format errors found. */ + return (1); +} + +/* + * int + * process_updates(firstp, rcodep, from) + * Process prerequisites and apply updates from the list to the database. + * returns: + * number of successful updates, 0 if none were successful. + * side effects: + * *rcodep gets the transaction return code. + * can schedule maintainance for zone dumps and soa.serial# increments. + */ +static int +process_updates(ns_updrec *firstp, int *rcodep, struct sockaddr_in from) { + int i, j, n, dbflags, matches, zonenum; + int numupdated = 0, soaupdated = 0, schedmaint = 0; + u_int16_t zclass; + ns_updrec *ur; + const char *fname; + struct databuf *dp, *savedp; + struct zoneinfo *zp; + int zonelist[MAXDNAME]; + + *rcodep = SERVFAIL; + if (!firstp) + return (0); + if (firstp->r_section == S_ZONE) { + zclass = firstp->r_class; + zonenum = firstp->r_zone; + zp = &zones[zonenum]; + } else { + ns_debug(ns_log_update, 1, + "process_updates: missing zone record"); + return (0); + } + + /* Process prereq records and prescan update records. */ + for (ur = firstp; ur != NULL; ur = ur->r_next) { + const char * dname = ur->r_dname; + u_int16_t class = ur->r_class; + u_int16_t type = ur->r_type; + u_int32_t ttl = ur->r_ttl; + struct databuf *rdp = ur->r_dp; + u_int section = ur->r_section; + + ns_debug(ns_log_update, 3, +"process_update: record section=%s, dname=%s, \ +class=%s, type=%s, ttl=%d, dp=0x%0x", + p_section(section, ns_o_update), dname, + p_class(class), p_type(type), ttl, rdp); + + matches = findzone(dname, zclass, MAXDNAME, + zonelist, MAXDNAME); + ur->r_zone = 0; + for (j = 0; j < matches && !ur->r_zone; j++) + if (zonelist[j] == zonenum) + ur->r_zone = zonelist[j]; + if (!ur->r_zone) { + ns_debug(ns_log_update, 1, + "process_updates: record does not belong to the zone %s", + zones[zonenum].z_origin); + *rcodep = NOTZONE; + return (0); + } + + switch (section) { + case S_ZONE: + break; + case S_PREREQ: + if (!process_prereq(ur, rcodep, zclass)) + return (0); /* *rcodep has been set. */ + ns_debug(ns_log_update, 3, "prerequisite satisfied"); + break; + case S_UPDATE: + if (!prescan_update(ur, rcodep, zclass)) + return (0); /* *rcodep has been set. */ + ns_debug(ns_log_update, 3, "update prescan succeeded"); + break; + case S_ADDT: + break; + default: + ns_panic(ns_log_update, 1, + "process_updates: impossible section"); + /* NOTREACHED */ + } + } + + /* Now process the records in update section. */ + for (ur = firstp; ur != NULL; ur = ur->r_next) { + const char * dname = ur->r_dname; + u_int16_t class = ur->r_class; + + if (ur->r_section != S_UPDATE) + continue; + dbflags = 0; + savedp = NULL; + dp = ur->r_dp; + if (class == zp->z_class) { + /* ADD databuf dp to hash table */ + /* + * Handling of various SOA/WKS/CNAME scenarios + * is done in db_update(). + */ + ur->r_opcode = ADD; + dbflags |= DB_NODATA; + n = db_update(dname, dp, dp, &savedp, + dbflags, hashtab, from); + if (n != OK) { + ns_debug(ns_log_update, 3, + "process_updates: failed to add databuf (%d)", + n); + } else { + ns_debug(ns_log_update, 3, + "process_updates: added databuf 0x%0x", + dp); + dp->d_mark = D_MARK_ADDED; + numupdated++; + if (dp->d_type == T_SOA) + soaupdated = 1; + } + } else if (class == C_ANY || class == C_NONE) { + /* + * DELETE databuf's matching dp from the hash table. + * + * handling of various SOA/NS scenarios done + * in db_update(). + */ + ur->r_opcode = DELETE; + /* + * we know we're deleting now, and db_update won't + * match with class==C_NONE, so we use the zone's + * class. + */ + if (class == C_NONE) + ur->r_dp->d_class = zp->z_class; + dbflags |= DB_DELETE; + n = db_update(dname, dp, NULL, &savedp, + dbflags, hashtab, from); + if (n != OK) + ns_debug(ns_log_update, 3, + "process_updates: delete failed"); + else { + ns_debug(ns_log_update, 3, + "process_updates: delete succeeded"); + numupdated++; + } + } + /* + * Even an addition could have caused some deletions like + * replacing old SOA or CNAME or WKS record or records of + * lower cred/clev. + * + * We need to save the deleted databuf's in case we wish to + * abort this update transaction and roll back all updates + * applied from this packet. + */ + ur->r_deldp = savedp; + } + + /* + * If we got here, things are OK, so set rcodep to indicate so. + */ + *rcodep = NOERROR; + + if (!numupdated) + return (0); + + /* + * schedule maintenance for dumps and SOA.serial# increment + * (this also sets Z_NEED_DUMP and Z_NEED_SOAUPDATE appropriately) + */ + schedmaint = 0; + if (schedule_dump(zp)) + schedmaint = 1; + if (soaupdated) { + /* + * SOA updated by this update transaction, so + * we need to set the zone serial number, stop any + * automatic updates that may be pending, and send out + * a NOTIFY message. + */ + zp->z_serial = get_serial_unchecked(zp); + cancel_soa_update(zp); + schedmaint = 1; +#ifdef BIND_NOTIFY + sysnotify(zp->z_origin, zp->z_class, T_SOA); +#endif + } else { + if (schedule_soa_update(zp, numupdated)) + schedmaint = 1; + } + if (schedmaint) + sched_zone_maint(zp); + return (numupdated); +} + +static enum req_action +req_update_private(HEADER *hp, u_char *cp, u_char *eom, u_char *msg, + struct qstream *qsp, int dfd, struct sockaddr_in from) +{ + char dnbuf[MAXDNAME], *dname; + u_int zocount, prcount, upcount, adcount, class, type, dlen; + u_int32_t ttl; + int i, n, cnt, found, matches, zonenum, numupdated = 0; + int rcode = NOERROR; + u_int c, section; + u_char rdata[MAXDATA]; + struct qinfo *qp; + struct databuf *dp, *nsp[NSMAX]; + struct databuf **nspp = &nsp[0]; + struct zoneinfo *zp; + ns_updrec *rrecp; + int zonelist[MAXDNAME]; + int should_use_tcp; + u_int32_t old_serial; + + nsp[0] = NULL; + + zocount = ntohs(hp->qdcount); + prcount = ntohs(hp->ancount); + upcount = ntohs(hp->nscount); + adcount = ntohs(hp->arcount); + + /* Process zone section. */ + ns_debug(ns_log_update, 3, "req_update: section ZONE, count %d", + zocount); + if ((n = dn_expand(msg, eom, cp, dnbuf, sizeof(dnbuf))) < 0) { + ns_debug(ns_log_update, 1, "req_update: expand name failed"); + hp->rcode = FORMERR; + return (Finish); + } + dname = dnbuf; + cp += n; + if (cp + 2 * INT16SZ > eom) { + ns_debug(ns_log_update, 1, "req_update: too short"); + hp->rcode = FORMERR; + return (Finish); + } + GETSHORT(type, cp); + GETSHORT(class, cp); + if (zocount != 1 || type != T_SOA) { + ns_debug(ns_log_update, 1, + "req_update: incorrect count or type for zone section: %d", + zocount); + hp->rcode = FORMERR; + return (Finish); + } + + matches = findzone(dname, class, 0, zonelist, MAXDNAME); + if (matches == 1) { + zonenum = zonelist[0]; + zp = &zones[zonenum]; + old_serial = get_serial(zp); + if (zp->z_class != (int)class || + (zp->z_type != z_master && zp->z_type != z_slave)) + matches = 0; + } + if (matches != 1) { + ns_debug(ns_log_update, 1, + "req_update: non-authoritative server for %s", + dname); + hp->rcode = NOTAUTH; + return (Finish); + } + + /* + * Begin Access Control Point + */ + + if (!ip_address_allowed(zp->z_update_acl, from.sin_addr)) { + ns_notice(ns_log_security, "unapproved update from %s for %s", + sin_ntoa(from), *dname ? dname : "."); + return (Refuse); + } + + /* + * End Access Control Point + */ + + /* XXXVIX should check update key when we have one. */ + + /* we should be authoritative */ + if (!(zp->z_flags & Z_AUTH)) { + ns_debug(ns_log_update, 1, + "req_update: zone %s: Z_AUTH not set", + dname); + hp->rcode = NOTAUTH; + return (Finish); + } + + if (zp->z_type == Z_SECONDARY) { + /* + * XXX the code below is broken. Until fixed, we just + * refuse. + */ + return (Refuse); + + /* We are a slave for this zone, forward it to the master. */ + for (cnt = 0; cnt < zp->z_addrcnt; cnt++) + *nspp++ = savedata(zp->z_class, T_A, USE_MINIMUM, + (u_char *)&zp->z_addr[cnt].s_addr, + INT32SZ); + *nspp = NULL; + /* + * If the request came in over TCP, forward it over TCP + */ + should_use_tcp = (qsp != NULL); + n = ns_forw(nsp, msg, eom-msg, from, qsp, dfd, &qp, + dname, class, type, NULL, should_use_tcp); + free_nsp(nsp); + switch (n) { + case FW_OK: + case FW_DUP: + return (Return); + case FW_NOSERVER: + /* should not happen */ + case FW_SERVFAIL: + hp->rcode = SERVFAIL; + return (Finish); + } + } + /* + * We are the primary master server for this zone, + * proceed further and process update packet + */ + if (!(zp->z_flags & Z_DYNAMIC)) { + ns_debug(ns_log_update, 1, + "req_update: dynamic flag not set for zone %s", + dname); + return (Refuse); + } + ns_debug(ns_log_update, 3, + "req_update: update request for zone %s, class %s", + zp->z_origin, p_class(class)); + rrecp_start = res_mkupdrec(S_ZONE, dname, class, type, 0); + rrecp_start->r_zone = zonenum; + rrecp_start->r_prev = NULL; + rrecp_start->r_next = NULL; + rrecp_last = rrecp_start; + + /* + * Parse the prerequisite and update sections for format errors. + */ + for (i = 0; (u_int)i < prcount + upcount; i++) { + if ((n = dn_expand(msg, eom, cp, dnbuf, sizeof(dnbuf))) < 0) { + ns_debug(ns_log_update, 1, + "req_update: expand name failed"); + hp->rcode = FORMERR; + return (Finish); + } + dname = dnbuf; + cp += n; + if (cp + RRFIXEDSZ > eom) { + ns_debug(ns_log_update, 1, + "req_update: overrun in answer"); + hp->rcode = FORMERR; + return (Finish); + } + GETSHORT(type, cp); + GETSHORT(class, cp); + GETLONG(ttl, cp); + GETSHORT(dlen, cp); + n = 0; + dp = NULL; + if (dlen > 0) { + if (cp + dlen > eom) { + ns_debug(ns_log_update, 1, + "req_update: bad dlen"); + hp->rcode = FORMERR; + return (Finish); + } + n = rdata_expand(msg, eom, cp, type, dlen, + rdata, sizeof rdata); + if (n == 0 || n > MAXDATA) { + ns_debug(ns_log_update, 1, + "req_update: failed to expand record"); + hp->rcode = FORMERR; + return (Finish); + } + cp += dlen; + } + section = ((u_int)i < prcount) ? S_PREREQ : S_UPDATE; + rrecp = res_mkupdrec(section, dname, class, type, ttl); + dp = savedata(class, type, ttl, rdata, n); + dp->d_zone = zonenum; + dp->d_cred = DB_C_ZONE; + dp->d_clev = nlabels(zp->z_origin); + /* XXX - also record in dp->d_ns, which host this came from */ + rrecp->r_dp = dp; + /* Append the current record to the end of list of records. */ + rrecp_last->r_next = rrecp; + rrecp->r_prev = rrecp_last; + rrecp->r_next = NULL; + rrecp_last = rrecp; + if (cp > eom) { + ns_info(ns_log_update, + "Malformed response from %s (overrun)", + inet_ntoa(from.sin_addr)); + hp->rcode = FORMERR; + return (Finish); + } + } + + /* Now process all parsed records in the prereq and update sections. */ + numupdated = process_updates(rrecp_start, &rcode, from); + hp->rcode = rcode; + if (numupdated <= 0) { + ns_error(ns_log_update, + "error processing update packet id %d from %s", + hp->id, sin_ntoa(from)); + return (Finish); + } + + /* Make a log of the update. */ + (void) printupdatelog(from, rrecp_start, hp, zp, old_serial); + + return (Finish); +} + +static void +free_rrecp(ns_updrec **startpp, ns_updrec **lastpp, int rcode, + struct sockaddr_in from) +{ + ns_updrec *rrecp, *first_rrecp, *next_rrecp; + struct databuf *dp, *tmpdp; + char *dname, *msg; + + REQUIRE(startpp != NULL && lastpp != NULL); + + if (rcode == NOERROR) { + first_rrecp = *startpp; + msg = "free_rrecp: update transaction succeeded, cleaning up"; + } else { + first_rrecp = *lastpp; + msg = "free_rrecp: update transaction aborted, rolling back"; + } + ns_debug(ns_log_update, 1, msg); + for (rrecp = first_rrecp; rrecp != NULL; rrecp = next_rrecp) { + if (rcode == NOERROR) + next_rrecp = rrecp->r_next; + else + next_rrecp = rrecp->r_prev; + if (rrecp->r_section != S_UPDATE) { + if (rrecp->r_dp) + db_freedata(rrecp->r_dp); + res_freeupdrec(rrecp); + continue; + } + dname = rrecp->r_dname; + dp = rrecp->r_dp; + if ((dp->d_mark & D_MARK_ADDED) != 0) { + if (rcode == NOERROR) { + /* + * This databuf is now a part of hashtab, + * or has been deleted by a subsequent update. + * Either way, we must not free it. + */ + dp->d_mark &= ~D_MARK_ADDED; + } else { + /* Delete the databuf. */ + if (db_update(dname, dp, NULL, NULL, + DB_DELETE, hashtab, from) + != OK) { + ns_error(ns_log_update, + "free_rrecp: failed to delete databuf: dname=%s, type=%s", + dname, p_type(dp->d_type)); + } else { + ns_debug(ns_log_update, 3, + "free_rrecp: deleted databuf 0x%0x", + dp); + /* + * XXXRTH + * + * We used to db_freedata() here, + * but I removed it because 'dp' was + * part of a hashtab before we called + * db_update(), and since our delete + * has succeeded, it should have been + * freed. + */ + } + } + } else { + /* + * Databuf's matching this were deleted by this + * update, or were never executed (because we bailed + * out early). + */ + db_freedata(dp); + } + + /* Process deleted databuf's. */ + dp = rrecp->r_deldp; + while (dp != NULL) { + tmpdp = dp; + dp = dp->d_next; + if (rcode == NOERROR) { + if (tmpdp->d_rcnt) + ns_debug(ns_log_update, 1, + "free_rrecp: type = %d, rcnt = %d", + p_type(tmpdp->d_type), + tmpdp->d_rcnt); + else { + tmpdp->d_next = NULL; + db_freedata(tmpdp); + } + } else { + /* Add the databuf back. */ + tmpdp->d_mark &= ~D_MARK_DELETED; + if (db_update(dname, tmpdp, tmpdp, NULL, + 0, hashtab, from) != OK) { + ns_error(ns_log_update, + "free_rrecp: failed to add back databuf: dname=%s, type=%s", + dname, p_type(tmpdp->d_type)); + } else { + ns_debug(ns_log_update, 3, + "free_rrecp: added back databuf 0x%0x", + tmpdp); + } + } + } + res_freeupdrec(rrecp); + } + *startpp = NULL; + *lastpp = NULL; +} + +enum req_action +req_update(HEADER *hp, u_char *cp, u_char *eom, u_char *msg, + struct qstream *qsp, int dfd, struct sockaddr_in from) +{ + enum req_action ret; + + ret = req_update_private(hp, cp, eom, msg, qsp, dfd, from); + free_rrecp(&rrecp_start, &rrecp_last, hp->rcode, from); + if (ret == Finish) { + hp->qdcount = hp->ancount = hp->nscount = hp->arcount = 0; + memset(msg + HFIXEDSZ, 0, (eom - msg) - HFIXEDSZ); + } + return (ret); +} + +/* + * expand rdata portion of a compressed resource record at cp into cp1 + * and return the length of the expanded rdata (length of the compressed + * rdata is "dlen"). + */ +static int +rdata_expand(const u_char *msg, const u_char *eom, const u_char *cp, + u_int type, size_t dlen, u_char *cp1, size_t size) +{ + const u_char *cpinit = cp; + const u_char *cp1init = cp1; + int n, i; + + switch (type) { + case T_A: + if (dlen != INT32SZ) + return (0); + /*FALLTHROUGH*/ + case T_WKS: + case T_HINFO: + case T_TXT: + case T_X25: + case T_ISDN: + case T_NSAP: + case T_LOC: + if (size < dlen) + return (0); + memcpy(cp1, cp, dlen); + return (dlen); + case T_CNAME: + case T_MB: + case T_MG: + case T_MR: + case T_NS: + case T_PTR: + n = dn_expand(msg, eom, cp, (char *)cp1, size); + if (n < 0 || (u_int)n != dlen) + return (0); + return (strlen((char *)cp1) + 1); + case T_MINFO: + case T_SOA: + case T_RP: + /* Get two compressed domain names. */ + for (i = 0; i < 2; i++) { + n = dn_expand(msg, eom, cp, (char *)cp1, size); + if (n < 0) + return (0); + cp += n; + n = strlen((char *)cp1) + 1; + cp1 += n; + size -= n; + } + if (type == T_SOA) { + n = 5 * INT32SZ; + if (size < (size_t)n || cp + n > eom) + return(0); + size -= n; + memcpy(cp1, cp, n); + cp += n; + cp1 += n; + } + if (cp != cpinit + dlen) + return (0); + return (cp1 - cp1init); + case T_MX: + case T_AFSDB: + case T_RT: + case T_SRV: + /* Grab preference. */ + if (size < INT16SZ || cp + INT16SZ > eom) + return (0); + size -= INT16SZ; + memcpy(cp1, cp, INT16SZ); + cp += INT16SZ; + cp1 += INT16SZ; + + if (type == T_SRV) { + if (size < INT16SZ*2 || cp + INT16SZ*2 > eom) + return (0); + size -= INT16SZ*2; + /* Grab weight and port. */ + memcpy(cp1, cp, INT16SZ*2); + cp1 += INT16SZ*2; + cp += INT16SZ*2; + } + + /* Get name. */ + n = dn_expand(msg, eom, cp, (char *)cp1, size); + if (n < 0) + return (0); + cp += n; + n = strlen((char *)cp1) + 1; + cp1 += n; + if (cp != cpinit + dlen) + return (0); + return (cp1 - cp1init); + case T_PX: + /* Grab preference. */ + if (size < INT16SZ || cp + INT16SZ > eom) + return (0); + size -= INT16SZ; + memcpy(cp1, cp, INT16SZ); + cp += INT16SZ; + cp1 += INT16SZ; + /* Get MAP822 name. */ + n = dn_expand(msg, eom, cp, (char *)cp1, size); + if (n < 0) + return (0); + cp += n; + n = strlen((char *)cp1) + 1; + cp1 += n; + size -= n; + n = dn_expand(msg, eom, cp, (char *)cp1, size); + if (n < 0) + return (0); + cp += n; + n = strlen((char *)cp1) + 1; + cp1 += n; + if (cp != cpinit + dlen) + return (0); + return (cp1 - cp1init); + default: + ns_debug(ns_log_update, 3, "unknown type %d", type); + return (0); + } +} + +/* + * Print out rdata portion of a resource record from a databuf into a file. + * + * XXX - similar code in db_dump() should be replaced by a call to this + * function. + */ +void +rdata_dump(struct databuf *dp, FILE *fp) { + u_int32_t n, addr; + u_char *cp, *end; + int i, j; + const char *proto; + + cp = (u_char *)dp->d_data; + switch (dp->d_type) { + case T_A: + switch (dp->d_class) { + case C_IN: + case C_HS: + GETLONG(n, cp); + n = htonl(n); + fputs(inet_ntoa(*(struct in_addr *)&n), fp); + break; + } + if (dp->d_nstime) + fprintf(fp, ";\tNT=%d", dp->d_nstime); + break; + case T_CNAME: + case T_MB: + case T_MG: + case T_MR: + case T_PTR: + fprintf(fp, "%s.", cp); + break; + case T_NS: + cp = (u_char *)dp->d_data; + if (cp[0] == '\0') + fprintf(fp, ".\t"); + else + fprintf(fp, "%s.", cp); + break; + case T_HINFO: + case T_ISDN: + if ((n = *cp++) != '\0') { + fprintf(fp, "\"%.*s\"", (int)n, cp); + cp += n; + } else + fprintf(fp, "\"\""); + if ((n = *cp++) != '\0') + fprintf(fp, " \"%.*s\"", (int)n, cp); + else + fprintf(fp, " \"\""); + break; + case T_SOA: + fprintf(fp, "%s.", cp); + cp += strlen((char *)cp) + 1; + fprintf(fp, " %s. ( ", cp); +#if defined(RETURNSOA) && defined(NCACHE) + if (dp->d_rcode == NXDOMAIN) + fputs(";", fp); +#endif + cp += strlen((char *)cp) + 1; + GETLONG(n, cp); + fprintf(fp, "%u", n); + GETLONG(n, cp); + fprintf(fp, " %u", n); + GETLONG(n, cp); + fprintf(fp, " %u", n); + GETLONG(n, cp); + fprintf(fp, " %u", n); + GETLONG(n, cp); + fprintf(fp, " %u )", n); +#if defined(RETURNSOA) && defined(NCACHE) + if (dp->d_rcode == NXDOMAIN) + fprintf(fp, ";%s.;NXDOMAIN;\t-$", cp); +#endif + break; + case T_MX: + case T_AFSDB: + case T_RT: + GETSHORT(n, cp); + fprintf(fp, "%u", n); + fprintf(fp, " %s.", cp); + break; + case T_PX: + GETSHORT(n, cp); + fprintf(fp, "%u", n); + fprintf(fp, " %s.", cp); + cp += strlen((char *)cp) + 1; + fprintf(fp, " %s.", cp); + break; + case T_TXT: + case T_X25: + end = (u_char *)dp->d_data + dp->d_size; + (void) putc('"', fp); + while (cp < end) { + if ((n = *cp++) != '\0') { + for (j = n; j > 0 && cp < end; j--) + if (*cp == '\n') { + (void) putc('\\', fp); + (void) putc(*cp++, fp); + } else + (void) putc(*cp++, fp); + } + } + /* XXXVIX need to keep the segmentation (see 4.9.5). */ + (void) fputs("\"", fp); + break; + case T_NSAP: + (void) fputs(inet_nsap_ntoa(dp->d_size, dp->d_data, NULL), fp); + break; + case T_LOC: + (void) fputs(loc_ntoa(dp->d_data, NULL), fp); + break; + case T_WKS: + GETLONG(addr, cp); + addr = htonl(addr); + fputs(inet_ntoa(*(struct in_addr *)&addr), fp); + proto = protocolname((u_char)*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); + } + break; + case T_MINFO: + case T_RP: + fprintf(fp, "%s.", cp); + cp += strlen((char *)cp) + 1; + fprintf(fp, " %s.", cp); + break; + default: + fprintf(fp, "\t;?d_type=%d?", dp->d_type); + } +} + +/* + * Return the number of authoritative zones that "dname" could belong to by + * stripping up to "depth" labels from dname. Up to the first "maxzones" + * authoritative zone numbers will be stored in "zonelist", ordered + * deepest match first. + */ +static int +findzone(const char *dname, int class, int depth, int *zonelist, int maxzones){ + char *tmpdname; + char tmpdnamebuf[MAXDNAME]; + char *zonename, *cp; + int tmpdnamelen, zonenamelen, zonenum, i, j, c; + int matches = 0; + int escaped, found, done; + + ns_debug(ns_log_update, 4, "findzone(dname=%s, class=%d, depth=%d, \ +zonelist=0x%x, maxzones=%d)", + dname, class, depth, zonelist, maxzones); +#ifdef DEBUG + if (debug >= 5) { + ns_debug(ns_log_update, 5, "zone dump:"); + for (zonenum = 1; zonenum < nzones; zonenum++) + printzoneinfo(zonenum, ns_log_update, 5); + } +#endif + + strcpy(tmpdnamebuf, dname); + tmpdname = tmpdnamebuf; + /* + * The code to handle trailing dots and escapes is adapted + * from samedomain(). + */ + tmpdnamelen = strlen(tmpdname); + /* + * Ignore a trailing label separator (i.e. an unescaped dot) + * in 'tmpdname'. + */ + if (tmpdnamelen && tmpdname[tmpdnamelen-1] == '.') { + escaped = 0; + /* note this loop doesn't get executed if tmpdnamelen==1 */ + for (j = tmpdnamelen - 2; j >= 0; j--) + if (tmpdname[j] == '\\') { + if (escaped) + escaped = 0; + else + escaped = 1; + } else { + break; + } + if (!escaped) { + tmpdnamelen--; + tmpdname[tmpdnamelen] = '\0'; + } + } + + for (done = i = 0; i <= depth && !done; i++) { + for (zonenum = 1; zonenum < nzones; zonenum++) { + if (zones[zonenum].z_type == z_nil) + continue; + if (zones[zonenum].z_class != class) + continue; + zonename = zones[zonenum].z_origin; + zonenamelen = strlen(zonename); + /* + * Ignore a trailing label separator + * (i.e. an unescaped dot) in 'zonename'. + */ + if (zonenamelen && zonename[zonenamelen-1] == '.') { + escaped = 0; + for (j = zonenamelen - 2; j >= 0; j--) + if (zonename[j] == '\\') { + if (escaped) + escaped = 0; + else + escaped = 1; + } else { + break; + } + if (!escaped) + zonenamelen--; + } + + if (tmpdnamelen != zonenamelen) + continue; + ns_debug(ns_log_update, 5, + "about to strncasecmp('%s', '%s', %d)", + tmpdname, zonename, tmpdnamelen); + /* XXXRTH I'm doing a special test for zonenamelen == 0 + because I worry that some implementations of + strncasecmp might not handle comparisions where + n==0 correctly */ + if (zonenamelen == 0 || + !strncasecmp(tmpdname, zonename, tmpdnamelen)) { + ns_debug(ns_log_update, 5, "match"); + zonelist[matches++] = zonenum; + if (matches == maxzones) { + /* XXX should signal error */ + return (matches); + } + } + } + + /* + * Strip off the first label if we're not already at + * the root label. + */ + if (*tmpdname != '\0') { + for (escaped = found = 0; + (c = *tmpdname) && !found; + tmpdname++) { + if (!escaped && (c == '.')) + /* + * Note the loop increment will + * make tmpdname point past the '.' + * before the '!found' test causes + * us to exit the loop. + */ + found = 1; + + if (escaped) + escaped = 0; + else if (c == '\\') + escaped = 1; + } + } else + done = 1; + + tmpdnamelen = strlen(tmpdname); + } + ns_debug(ns_log_update, 4, + "findzone: returning %d match(es)", matches); + return (matches); +} + +/* + * reapply lost updates from log file for the zone to the zone + * + * returns -1 on error, 0 on success, 1 if dump reload needed + */ +int +merge_logs(struct zoneinfo *zp) { + char origin[MAXDNAME], data[MAXDATA], dnbuf[MAXDNAME], sclass[3]; + char buf[BUFSIZ], buf2[100]; + FILE *fp; + u_int32_t serial, ttl, old_serial, new_serial; + char *dname, *cp, *cp1; + int type, class; + int i, c, section, opcode, matches, zonenum, err, multiline; + int nonempty_lineno = -1, prev_pktdone = 0, cont = 0, inside_next = 0; + int id, rcode = NOERROR; + u_int32_t n; + struct map *mp; + ns_updrec *rrecp; + struct databuf *dp; + struct in_addr ina; + int zonelist[MAXDNAME]; + struct stat st; + u_char *serialp; + struct sockaddr_in empty_from; + int datasize; + + empty_from.sin_family = AF_INET; + empty_from.sin_addr.s_addr = htonl(INADDR_ANY); + empty_from.sin_port = htons(0); + + /* XXX - much of this stuff is similar to that in nsupdate.c + * getword_str() was used in nsupdate.c for reasons described there + * getword() is used here just to be consistent with db_load() + */ + + /* If there is no log file, just return. */ + if (stat(zp->z_updatelog, &st) < 0) { + if (errno != ENOENT) + ns_error(ns_log_update, + "unexpected stat(%s) failure: %s", + zp->z_updatelog, strerror(errno)); + return (-1); + } + fp = fopen(zp->z_updatelog, "r"); + if (fp == NULL) { + ns_error(ns_log_update, "fopen(%s) failed: %s", + zp->z_updatelog, strerror(errno)); + return (-1); + } + + /* + * See if we really have a log file -- it might be a zone dump + * that was in the process of being renamed, or it might + * be garbage! + */ + + if (fgets(buf, sizeof(buf), fp)==NULL) { + ns_error(ns_log_update, "fgets() from %s failed: %s", + zp->z_updatelog, strerror(errno)); + fclose(fp); + return (-1); + } + if (strcmp(buf, DumpSignature) == 0) { + /* It's a dump; finish rename that was interrupted. */ + ns_info(ns_log_update, + "completing interrupted dump rename for %s", + zp->z_source); + if (rename(zp->z_updatelog, zp->z_source) < 0) { + ns_error(ns_log_update, "rename(%s,%s) failed: %s", + zp->z_updatelog, zp->z_source, + strerror(errno)); + return (-1); + } + fclose(fp); + /* Finally, tell caller to reload zone. */ + return (1); + } + if (strcmp(buf, LogSignature) != 0) { + /* Not a dump and not a log; complain and then bail out. */ + ns_error(ns_log_update, "invalid log file %s", + zp->z_updatelog); + fclose(fp); + return (-1); + } + + ns_debug(ns_log_update, 3, "merging logs for %s from %s", + zp->z_origin, zp->z_updatelog); + lineno = 1; + rrecp_start = NULL; + rrecp_last = NULL; + for (;;) { + if (!getword(buf, sizeof buf, fp, 0)) { + if (lineno == (nonempty_lineno + 1)) { + /* + * End of a nonempty line inside an update + * packet or not inside an update packet. + */ + continue; + } + /* + * Empty line or EOF. + * + * Marks completion of current update packet. + */ + inside_next = 0; + prev_pktdone = 1; + cont = 1; + } else { + nonempty_lineno = lineno; + } + + if (!strcasecmp(buf, "[DYNAMIC_UPDATE]")) { + err = 0; + rcode = NOERROR; + cp = fgets(buf, sizeof buf, fp); + if (cp != NULL) + lineno++; + if (cp == NULL || !sscanf((char *)cp, "id %d", &id)) + id = -1; + inside_next = 1; + prev_pktdone = 1; + cont = 1; + } else if (!strcasecmp(buf, "[INCR_SERIAL]")) { + /* XXXRTH not enough error checking here */ + cp = fgets(buf, sizeof buf, fp); + if (cp != NULL) + lineno++; + if (cp == NULL || + !sscanf((char *)cp, "from %u to %u", + &old_serial, &new_serial)) { + ns_error(ns_log_update, + "incr_serial problem with %s", + zp->z_updatelog); + } else { + serial = get_serial(zp); + if (serial != old_serial) { + ns_error(ns_log_update, + "serial number mismatch (log=%u, zone=%u) in %s", old_serial, + serial, zp->z_updatelog); + } else { + set_serial(zp, new_serial); + /* + * The zone has changed; make sure + * a dump is scheduled. + */ + (void)schedule_dump(zp); + sched_zone_maint(zp); + ns_info(ns_log_update, + "set serial to %u (log file %s)", + new_serial, zp->z_updatelog); + } + } + prev_pktdone = 1; + cont = 1; + } + if (prev_pktdone) { + if (rrecp_start) { + n = process_updates(rrecp_start, &rcode, + empty_from); + if (n > 0) + ns_info(ns_log_update, + "successfully merged update id %d from log file %s", + id, zp->z_updatelog); + else + ns_error(ns_log_update, + "error merging update id %d from log file %s", + id, zp->z_updatelog); + free_rrecp(&rrecp_start, &rrecp_last, rcode, + empty_from); + } + prev_pktdone = 0; + if (feof(fp)) + break; + } + if (cont) { + cont = 0; + continue; + } + if (!inside_next) + continue; + /* + * inside the same update packet, + * continue accumulating records. + */ + section = -1; + n = strlen(buf); + if (buf[n-1] == ':') + buf[--n] = '\0'; + for (mp = m_section; mp < m_section+M_SECTION_CNT; mp++) + if (!strcasecmp(buf, mp->token)) { + section = mp->val; + break; + } + ttl = 0; + type = -1; + class = zp->z_class; + n = 0; + data[0] = '\0'; + switch (section) { + case S_ZONE: + cp = fgets(buf, sizeof buf, fp); + if (!cp) + *buf = '\0'; + n = sscanf(cp, "origin %s class %s serial %ul", + origin, sclass, &serial); + if (n != 3 || strcasecmp(origin, zp->z_origin)) + err++; + if (cp) + lineno++; + if (!err && serial != zp->z_serial) { + ns_error(ns_log_update, + "serial number mismatch in update id %d (log=%u, zone=%u) in %s", + id, serial, zp->z_serial, + zp->z_updatelog); + inside_next = 0; + err++; + } + if (!err && inside_next) { + int success; + + dname = origin; + type = T_SOA; + class = sym_ston(__p_class_syms, sclass, + &success); + if (!success) { + err++; + break; + } + matches = findzone(dname, class, 0, + zonelist, MAXDNAME); + if (matches) + zonenum = zonelist[0]; + else + err++; + } + break; + case S_PREREQ: + case S_UPDATE: + /* Operation code. */ + if (!getword(buf, sizeof buf, fp, 0)) { + err++; + break; + } + opcode = -1; + if (buf[0] == '{') { + n = strlen(buf); + for (i = 0; (u_int32_t)i < n; i++) + buf[i] = buf[i+1]; + if (buf[n-2] == '}') + buf[n-2] = '\0'; + } + for (mp = m_opcode; mp < m_opcode+M_OPCODE_CNT; mp++) + if (!strcasecmp(buf, mp->token)) { + opcode = mp->val; + break; + } + if (opcode == -1) { + err++; + break; + } + /* Owner's domain name. */ + if (!getword((char *)dnbuf, sizeof dnbuf, fp, 0)) { + err++; + break; + } + n = strlen((char *)dnbuf) - 1; + if (dnbuf[n] == '.') + dnbuf[n] = '\0'; + dname = dnbuf; + ttl = 0; + type = -1; + class = zp->z_class; + n = 0; + data[0] = '\0'; + (void) getword(buf, sizeof buf, fp, 1); + if (isdigit(buf[0])) { /* ttl */ + ttl = strtoul(buf, 0, 10); + if (errno == ERANGE && ttl == ULONG_MAX) { + err++; + break; + } + (void) getword(buf, sizeof buf, fp, 1); + } + + /* possibly class */ + if (buf[0] != '\0') { + int success; + int maybe_class; + + maybe_class = sym_ston(__p_class_syms, + buf, + &success); + if (success) { + class = maybe_class; + (void) getword(buf, + sizeof buf, + fp, 1); + } + } + /* possibly type */ + if (buf[0] != '\0') { + int success; + int maybe_type; + + maybe_type = sym_ston(__p_type_syms, + buf, + &success); + + if (success) { + type = maybe_type; + (void) getword(buf, + sizeof buf, + fp, 1); + } + } + if (buf[0] != '\0') /* possibly rdata */ + /* + * Convert the ascii data 'buf' to the proper + * format based on the type and pack into + * 'data'. + * + * XXX - same as in db_load(), + * consolidation needed + */ + switch (type) { + case T_A: + if (!inet_aton(buf, &ina)) { + err++; + break; + } + n = ntohl(ina.s_addr); + cp = data; + PUTLONG(n, cp); + n = INT32SZ; + break; + case T_HINFO: + case T_ISDN: + n = strlen(buf); + data[0] = n; + memcpy(data+1, buf, n); + n++; + if (!getword(buf, sizeof buf, + fp, 0)) { + i = 0; + } else { + endline(fp); + i = strlen(buf); + } + data[n] = i; + memcpy(data+n+1, buf, i); + break; + case T_SOA: + case T_MINFO: + case T_RP: + (void) strcpy(data, buf); + cp = data + strlen(data) + 1; + if (!getword((char *)cp, + sizeof data - (cp - data), + fp, 1)) { + err++; + break; + } + cp += strlen((char *)cp) + 1; + if (type != T_SOA) { + n = cp - data; + break; + } + if (class != zp->z_class || + strcasecmp(dname, zp->z_origin)) { + err++; + break; + } + c = getnonblank(fp, zp->z_updatelog); + if (c == '(') { + multiline = 1; + } else { + multiline = 0; + ungetc(c, fp); + } + for (i = 0; i < 5; i++) { + n = getnum(fp, zp->z_updatelog, + GETNUM_SERIAL); + if (getnum_error) { + err++; + break; + } + PUTLONG(n, cp); + } + if (multiline && + getnonblank(fp, zp->z_updatelog) + != ')') { + err++; + break; + } + endline(fp); + break; + case T_WKS: + if (!inet_aton(buf, &ina)) { + err++; + break; + } + n = ntohl(ina.s_addr); + cp = data; + PUTLONG(n, cp); + *cp = (char)getprotocol(fp, + zp->z_updatelog + ); + n = INT32SZ + sizeof(char); + n = getservices((int)n, data, + fp, zp->z_updatelog); + break; + case T_NS: + case T_CNAME: + case T_MB: + case T_MG: + case T_MR: + case T_PTR: + (void) strcpy(data, buf); + if (makename(data, origin, + sizeof(data)) == -1) { + err++; + break; + } + n = strlen(data) + 1; + break; + case T_MX: + case T_AFSDB: + case T_RT: + n = 0; + cp = buf; + while (isdigit(*cp)) + n = n * 10 + (*cp++ - '0'); + /* catch bad values */ + cp = data; + PUTSHORT((u_int16_t)n, cp); + if (!getword(buf, sizeof(buf), + fp, 1)) { + err++; + break; + } + (void) strcpy((char *)cp, buf); + if (makename((char *)cp, origin, + sizeof(data) - (cp-data)) + == -1) { + err++; + break; + } + /* advance pointer to end of data */ + cp += strlen((char *)cp) +1; + /* now save length */ + n = (cp - data); + break; + case T_PX: + n = 0; + data[0] = '\0'; + cp = buf; + while (isdigit(*cp)) + n = n * 10 + (*cp++ - '0'); + cp = data; + PUTSHORT((u_int16_t)n, cp); + for (i = 0; i < 2; i++) { + if (!getword(buf, + sizeof(buf), + fp, 0)) { + err++; + break; + } + (void) strcpy((char *)cp, + buf); + cp += strlen((char *)cp) + 1; + } + n = cp - data; + break; + case T_TXT: + case T_X25: + i = strlen(buf); + cp = data; + datasize = sizeof data; + cp1 = buf; + while (i > 255) { + if (datasize < 256) { + ns_error(ns_log_update, + "record too big"); + return (-1); + } + datasize -= 255; + *cp++ = 255; + memcpy(cp, cp1, 255); + cp += 255; + cp1 += 255; + i -= 255; + } + if (datasize < i + 1) { + ns_error(ns_log_update, + "record too big"); + return (-1); + } + *cp++ = i; + memcpy(cp, cp1, i); + cp += i; + n = cp - data; + endline(fp); + /* XXXVIX: segmented texts 4.9.5 */ + break; + case T_NSAP: + n = inet_nsap_addr(buf, + (u_char *)data, + sizeof data); + endline(fp); + break; + case T_LOC: + cp = buf + (n = strlen(buf)); + *cp = ' '; + cp++; + while ((i = getc(fp), *cp = i, + i != EOF) + && *cp != '\n' + && (n < MAXDATA)) { + cp++; + n++; + } + if (*cp == '\n') + ungetc(*cp, fp); + *cp = '\0'; + n = loc_aton(buf, (u_char *)data); + if (n == 0) { + err++; + break; + } + endline(fp); + break; + default: + err++; + } + if (section == S_PREREQ) { + ttl = 0; + if (opcode == NXDOMAIN) { + class = C_NONE; + type = T_ANY; + n = 0; + } else if (opcode == YXDOMAIN) { + class = C_ANY; + type = T_ANY; + n = 0; + } else if (opcode == NXRRSET) { + class = C_NONE; + n = 0; + } else if (opcode == YXRRSET) { + if (n == 0) + class = C_ANY; + } + } else { /* section == S_UPDATE */ + if (opcode == DELETE) { + if (n == 0) { + class = C_ANY; + if (type == -1) + type = T_ANY; + } else { + class = C_NONE; + } + } + } + break; + case S_ADDT: + default: + ns_debug(ns_log_update, 1, + "cannot interpret section: %d", section); + inside_next = 0; + err++; + } + if (err) { + inside_next = 0; + ns_debug(ns_log_update, 1, + "merge of update id %d failed due to error at line %d", + id, lineno); + free_rrecp(&rrecp_start, &rrecp_last, rcode, + empty_from); + continue; + } + rrecp = res_mkupdrec(section, dname, class, type, ttl); + if (section != S_ZONE) { + dp = savedata(class, type, ttl, (u_char *)data, n); + dp->d_zone = zonenum; + dp->d_cred = DB_C_ZONE; + dp->d_clev = nlabels(zp->z_origin); + rrecp->r_dp = dp; + } else { + rrecp->r_zone = zonenum; + } + if (rrecp_start == NULL) { + rrecp_start = rrecp; + rrecp_last = rrecp; + rrecp->r_prev = NULL; + rrecp->r_next = NULL; + } else { + rrecp_last->r_next = rrecp; + rrecp->r_prev = rrecp_last; + rrecp->r_next = NULL; + rrecp_last = rrecp; + } + } /* for (;;) */ + + fclose(fp); + return (0); +} + + +/* + * Create a disk database to back up zones + */ +int +zonedump(zp) + struct zoneinfo *zp; +{ + FILE *fp; + const char *fname; + struct hashbuf *htp; + char *op; + struct stat st; + char tmp_name[MAXPATHLEN]; + int escaped; + char c; + + /* + * We must check to see if Z_NEED_SOAUPDATE is set, and if so + * we must do it. This won't be the case normally + * (when called from ns_maint()), but it is possible if we're + * exiting named. + */ + + if (zp->z_flags & Z_NEED_SOAUPDATE) { + u_int32_t serial, old_serial; + + old_serial = get_serial(zp); + serial = old_serial + 1; + if (serial == 0) + serial = 1; + set_serial(zp, serial); + } + + /* Only dump zone if there is a cache specified */ + if (zp->z_source && *(zp->z_source)) { + ns_debug(ns_log_update, 1, "zonedump(%s)", zp->z_source); + + if (strlen(zp->z_source)+strlen(DumpSuffix) >= MAXPATHLEN) { + ns_error(ns_log_update, + "filename %s too long in zonedump", + zp->z_source); + /* + * This problem won't ever get better, so we + * clear the "need dump" flag. + */ + zp->z_flags &= ~Z_NEED_DUMP; + return (-1); + } + (void)sprintf(tmp_name, "%s%s", zp->z_source, DumpSuffix); + if ((fp = write_open(tmp_name)) == NULL) { + ns_error(ns_log_update, "fopen() of %s failed: %s", + tmp_name, strerror(errno)); + return (-1); + } + fprintf(fp, "%s", DumpSignature); + op = zp->z_origin; + escaped = 0; + while (*op && (((c = *op++) != '.') || escaped)) + escaped = (c == '\\') && !escaped; + gettime(&tt); + htp = hashtab; + if (nlookup(zp->z_origin, &htp, &fname, 0) != NULL) { + if (db_dump(htp, fp, zp-zones, op) != OK) { + ns_error(ns_log_update, + "error dumping zone file %s", + zp->z_source); + (void)fclose(fp); + return (-1); + } + } + if (fflush(fp) == EOF) { + ns_error(ns_log_update, "fflush() of %s failed: %s", + tmp_name, strerror(errno)); + return (-1); + } + if (fsync(fileno(fp)) < 0) { + ns_error(ns_log_update, "fsync() of %s failed: %s", + tmp_name, strerror(errno)); + return (-1); + } + if (fclose(fp) == EOF) { + ns_error(ns_log_update, "fclose() of %s failed: %s", + tmp_name, strerror(errno)); + return (-1); + } + /* + * Try to make read only, so people will be less likely to + * edit dynamic domains. + */ + if (stat(tmp_name, &st) < 0) { + ns_error(ns_log_update, + "stat(%s) failed, pressing on: %s", + tmp_name, strerror(errno)); + } else { + zp->z_ftime = st.st_mtime; + st.st_mode &= ~WRITEABLE_MASK; + if (chmod(tmp_name, st.st_mode) < 0) + ns_error(ns_log_update, + "chmod(%s,%o) failed, pressing on: %s", + tmp_name, st.st_mode, + strerror(errno)); + } + if (rename(tmp_name, zp->z_updatelog) < 0) { + ns_error(ns_log_update, "rename(%s,%s) failed: %s", + tmp_name, zp->z_updatelog, strerror(errno)); + return (-1); + } + if (rename(zp->z_updatelog, zp->z_source) < 0) { + ns_error(ns_log_update, "rename(%s,%s) failed: %s", + zp->z_updatelog, zp->z_source, + strerror(errno)); + return (-1); + } + } else + ns_debug(ns_log_update, 1, "zonedump: no zone to dump"); + + zp->z_flags &= ~Z_NEED_DUMP; + zp->z_dumptime = 0; + return (0); +} + +struct databuf * +findzonesoa(struct zoneinfo *zp) { + struct hashbuf *htp; + struct namebuf *np; + struct databuf *dp; + const char *fname; + + htp = hashtab; + np = nlookup(zp->z_origin, &htp, &fname, 0); + if (np == NULL || fname != zp->z_origin) + return (NULL); + foreach_rr(dp, np, T_SOA, zp->z_class, zp - zones) + return (dp); + return (NULL); +} + +u_char * +findsoaserial(u_char *data) { + char *cp = (char *)data; + + cp += strlen(cp) + 1; /* Nameserver. */ + cp += strlen(cp) + 1; /* Mailbox. */ + return ((u_char *)cp); +} + +u_int32_t +get_serial_unchecked(struct zoneinfo *zp) { + struct databuf *dp; + u_char *cp; + u_int32_t ret; + + dp = findzonesoa(zp); + if (!dp) + ns_panic(ns_log_update, 1, + "get_serial_unchecked(%s): can't locate zone SOA", + zp->z_origin); + cp = findsoaserial(dp->d_data); + GETLONG(ret, cp); + return (ret); +} + +u_int32_t +get_serial(struct zoneinfo *zp) { + u_int32_t ret; + + ret = get_serial_unchecked(zp); + if (ret != zp->z_serial) + ns_panic(ns_log_update, 1, + "get_serial(%s): db and zone serial numbers differ", + zp->z_origin); + return (ret); +} + +void +set_serial(struct zoneinfo *zp, u_int32_t serial) { + struct databuf *dp; + u_char *cp; + + dp = findzonesoa(zp); + if (!dp) + ns_panic(ns_log_update, 1, + "set_serial(%s): can't locate zone SOA", + zp->z_origin); + cp = findsoaserial(dp->d_data); + PUTLONG(serial, cp); + zp->z_serial = serial; + zp->z_flags &= ~Z_NEED_SOAUPDATE; + zp->z_soaincrtime = 0; + zp->z_updatecnt = 0; +#ifdef BIND_NOTIFY + sysnotify(zp->z_origin, zp->z_class, T_SOA); +#endif + /* + * Note: caller is responsible for scheduling a dump + */ +} + +/* + * Increment serial number in zoneinfo structure and hash table SOA databuf + */ + +int +incr_serial(struct zoneinfo *zp) { + u_int32_t serial, old_serial; + FILE *fp; + time_t t; + + old_serial = get_serial(zp); + serial = old_serial + 1; + if (serial == 0) + serial = 1; + set_serial(zp, serial); + + (void) gettime(&tt); + t = (time_t)tt.tv_sec; + fp = open_transaction_log(zp); + if (fp == NULL) + return (-1); + fprintf(fp, "[INCR_SERIAL] from %u to %u %s\n", + old_serial, serial, checked_ctime(&t)); + if (close_transaction_log(zp, fp)<0) + return (-1); + + /* + * This shouldn't happen, but we check to be sure. + */ + if (!(zp->z_flags & Z_NEED_DUMP)) { + ns_warning(ns_log_update, + "incr_serial: Z_NEED_DUMP not set for zone '%s'", + zp->z_origin); + (void)schedule_dump(zp); + } + + sched_zone_maint(zp); + + return (0); +} + +void +dynamic_about_to_exit(void) { + struct zoneinfo *zp; + + ns_debug(ns_log_update, 1, + "shutting down; dumping zones that need it"); + for (zp = zones; zp < &zones[nzones]; zp++) { + if ((zp->z_flags & Z_DYNAMIC) && + ((zp->z_flags & Z_NEED_SOAUPDATE) || + (zp->z_flags & Z_NEED_DUMP))) + (void)zonedump(zp); + } +} |