2398 lines
56 KiB
C
2398 lines
56 KiB
C
/* $NetBSD: ns_update.c,v 1.1.1.1 1998/10/05 18:02:00 tron Exp $ */
|
|
|
|
#if !defined(lint) && !defined(SABER)
|
|
static char rcsid[] = "Id: ns_update.c,v 8.26 1998/05/05 19:45:10 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
|
|
if (!loading)
|
|
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];
|
|
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);
|
|
}
|
|
old_serial = get_serial(zp);
|
|
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
|
|
if (!loading)
|
|
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);
|
|
}
|
|
}
|