#if !defined(lint) && !defined(SABER) static char rcsid[] = "$Id: db_ixfr.c,v 8.23.2.2 2001/05/03 03:53:18 marka Exp $"; #endif /* * Portions Copyright (c) 1999 by Check Point Software Technologies, Inc. * * 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, and that * the name of Check Point Software Technologies Incorporated not be used * in advertising or publicity pertaining to distribution of the document * or software without specific, written prior permission. * * THE SOFTWARE IS PROVIDED "AS IS" AND CHECK POINT SOFTWARE TECHNOLOGIES * INCORPORATED DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. * IN NO EVENT SHALL CHECK POINT SOFTWARE TECHNOLOGIES INCORPRATED * 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. */ /* * Manage ixfr transaction log */ #include "port_before.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "port_after.h" #include "named.h" #define DBIXFR_ERROR (-1) #define DBIXFR_FOUND_RR 2 #define DBIXFR_END 3 static int ixfr_getdelta(struct zoneinfo *, FILE *, const char *, char *, ns_updque *, u_int32_t *, u_int32_t *); ns_deltalist * ixfr_get_change_list(struct zoneinfo *zp, u_int32_t from_serial, u_int32_t to_serial) { FILE * fp = NULL; u_int32_t old_serial, new_serial; char origin[MAXDNAME]; ns_deltalist *dlhead = NULL; int ret; ns_updrec *uprec; ns_delta *dl; if (SEQ_GT(from_serial, to_serial)) return (NULL); dlhead = memget(sizeof(*dlhead)); if (dlhead == NULL) return (NULL); INIT_LIST(*dlhead); if ((fp = fopen(zp->z_ixfr_base, "r")) == NULL) { ns_warning(ns_log_db, "%s: %s", zp->z_ixfr_base, strerror(errno)); goto cleanup; } strcpy(origin, zp->z_origin); lineno = 1; old_serial = new_serial = 0; for (;;) { dl = memget(sizeof *dl); if (dl == NULL) { ns_warning(ns_log_db, "ixfr_get_change_list: out of memory"); goto cleanup; } INIT_LINK(dl, d_link); INIT_LIST(dl->d_changes); ret = ixfr_getdelta(zp, fp, zp->z_ixfr_base, origin, &dl->d_changes, &old_serial, &new_serial); switch (ret) { case DBIXFR_ERROR: ns_warning(ns_log_db, "Logical error in %s: unlinking", zp->z_ixfr_base); unlink(zp->z_ixfr_base); goto cleanup; case DBIXFR_FOUND_RR: ns_debug(ns_log_default, 4, "ixfr_getdelta DBIXFR_FOUND_RR (%s)", zp->z_origin); if (EMPTY(*dlhead)) { /* skip updates prior to the one we want */ uprec = HEAD(dl->d_changes); INSIST(uprec != NULL); if ((uprec->r_zone < from_serial) || (uprec->r_zone > to_serial)) { while ((uprec = HEAD(dl->d_changes)) != NULL) { UNLINK(dl->d_changes, uprec, r_link); if (uprec->r_dp != NULL) db_freedata(uprec->r_dp); uprec->r_dp = NULL; res_freeupdrec(uprec); } memput(dl, sizeof *dl); break; } else if (uprec->r_zone > from_serial) { /* missed the boat */ ns_debug(ns_log_default, 3, "ixfr_getdelta first SOA is %d, asked for %d (%s)", uprec->r_zone, from_serial, zp->z_origin); goto cleanup; } } ns_debug(ns_log_default, 4, "adding to change list (%s)", zp->z_origin); APPEND(*dlhead, dl, d_link); break; case DBIXFR_END: ns_debug(ns_log_default, 4, "ixfr_getdelta DBIXFR_END (%s)", zp->z_origin); (void) my_fclose(fp); memput(dl, sizeof *dl); return (dlhead); default: (void) my_fclose(fp); if (dl != NULL) memput(dl, sizeof *dl); return (NULL); } } cleanup: if (fp != NULL) (void) my_fclose(fp); while ((dl = HEAD(*dlhead)) != NULL) { UNLINK(*dlhead, dl, d_link); while ((uprec = HEAD(dl->d_changes)) != NULL) { UNLINK(dl->d_changes, uprec, r_link); if (uprec->r_dp != NULL) db_freedata(uprec->r_dp); uprec->r_dp = NULL; res_freeupdrec(uprec); } memput(dl, sizeof *dl); } memput(dlhead, sizeof *dlhead); return (NULL); } /* * int ixfr_have_log(struct zoneinfo *zp,u_int32_t from_serial, * u_int32_t to_serial) * * verify that ixfr transaction log contains changes * from from_serial to to_serial * * returns: * 0 = serial number is up to date * 1 = transmission is possible * -1 = error while opening the ixfr transaction log * -2 = error in parameters * -3 = logical error in the history file */ int ixfr_have_log(struct zoneinfo *zp, u_int32_t from_serial, u_int32_t to_serial) { FILE *fp; u_int32_t old_serial = 0, new_serial = 0; u_int32_t last_serial = 0; u_int32_t first_serial = 0; char buf[BUFSIZ]; char *cp; struct stat st; int nonempty_lineno = -1, prev_pktdone = 0, cont = 0, inside_next = 0; int err; int first = 0; int rval = 0; int id, rcode = NOERROR; if (SEQ_GT(from_serial, to_serial)) return (-2); if (from_serial == to_serial) return (0); /* If there is no log file, just return. */ if (zp->z_ixfr_base == NULL || zp->z_updatelog == NULL) return (-1); if (zp->z_serial_ixfr_start > 0) { if (from_serial >= zp->z_serial_ixfr_start) return (1); } if (stat(zp->z_ixfr_base, &st) < 0) { if (errno != ENOENT) ns_error(ns_log_db, "unexpected stat(%s) failure: %s", zp->z_ixfr_base, strerror(errno)); return (-1); } if ((fp = fopen(zp->z_ixfr_base, "r")) == NULL) { ns_warning(ns_log_db, "%s: %s", zp->z_ixfr_base, strerror(errno)); return (-1); } if (fgets(buf, sizeof(buf), fp) == NULL) { ns_error(ns_log_update, "fgets() from %s failed: %s", zp->z_ixfr_base, strerror(errno)); fclose(fp); return (-1); } if (strcmp(buf, LogSignature) != 0) { ns_error(ns_log_update, "invalid log file %s", zp->z_ixfr_base); fclose(fp); return (-3); } lineno = 1; first = 1; for (;;) { if (getword(buf, sizeof buf, fp, 0)) { nonempty_lineno = lineno; } else { if (lineno == (nonempty_lineno + 1)) continue; inside_next = 0; prev_pktdone = 1; cont = 1; } if (!strcasecmp(buf, "[DYNAMIC_UPDATE]") || !strcasecmp(buf, "[IXFR_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, "serial")) { cp = fgets(buf, sizeof buf, fp); if (cp != NULL) lineno++; if (sscanf((char *) cp, "%u", &old_serial)) { if (first == 1) { first = 0; first_serial = old_serial; } last_serial = old_serial; if (from_serial >= old_serial) { rval = 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) != 2) { rval = -3; break; } else if (from_serial >= old_serial) { if (first == 1) { first = 0; first_serial = old_serial; } last_serial = old_serial; rval = 1; } } if (prev_pktdone) { prev_pktdone = 0; if (feof(fp)) break; } } fclose(fp); if (last_serial +1 < zp->z_serial) { ns_warning(ns_log_db, "%s: File Deleted. Found gap between serial:" " %d and current serial: %d", zp->z_ixfr_base, last_serial, zp->z_serial); (void) unlink(zp->z_ixfr_base); rval = -3; } if (from_serial < first_serial || from_serial > last_serial) rval = -3; if (rval == 1) zp->z_serial_ixfr_start = first_serial; return (rval); } /* 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 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 /* * read a line from the history of a zone. * * returns: * * DBIXFR_ERROR = an error occured * DBIXFR_FOUND_RR = a rr encountered * DBIXFR_END = end of file */ static int ixfr_getdelta(struct zoneinfo *zp, FILE *fp, const char *filename, char *origin, ns_updque *listuprec, u_int32_t *old_serial, u_int32_t *new_serial) { char data[MAXDATA], dnbuf[MAXDNAME], sclass[3]; char *dname, *cp, *cp1; char buf[MAXDATA]; u_int32_t serial, ttl; u_int32_t current_serial; int nonempty_lineno = -1, prev_pktdone = 0, cont = 0, inside_next = 0; int id; int i, c, section, opcode, matches, zonenum, err, multiline; int type, class; u_int32_t n; enum transport transport; struct map *mp; int zonelist[MAXDNAME]; struct databuf *dp; struct in_addr ina; int datasize; ns_updrec * rrecp; u_long l; #define ERRTO(msg) if (1) { errtype = msg; goto err; } else (void)NULL err = 0; transport = primary_trans; lineno = 1; for (;;) { if (!getword(buf, sizeof buf, fp, 0)) { if (lineno == (nonempty_lineno + 1) && !(feof(fp))) { /* * End of a nonempty line inside an update * packet or not inside an update packet. */ continue; } /* * Empty line or EOF. */ if (feof(fp)) break; inside_next = 0; cont = 1; } else { nonempty_lineno = lineno; } if (!strcasecmp(buf, "[DYNAMIC_UPDATE]") || !strcasecmp(buf, "[IXFR_UPDATE]")) { cp = fgets(buf, sizeof buf, fp); if (cp != NULL) lineno++; if (cp == NULL || !sscanf((char *) cp, "id %d", &id)) id = -1; inside_next = 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) != 2) { ns_error(ns_log_update, "incr_serial problem with %s", zp->z_updatelog); } else { serial = get_serial(zp); } cont = 1; } else if (!strcasecmp(buf, "[END_DELTA]")) { prev_pktdone = 1; cont = 1; lineno++; } if (prev_pktdone) { if (!EMPTY(*listuprec)) { n++; return (DBIXFR_FOUND_RR); } prev_pktdone = 0; } 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 %lu", origin, sclass, &serial); if (current_serial == 0) current_serial = serial; else if (current_serial != serial) { ns_debug(ns_log_update, 1, "%s:line %d serial # askew %d %d", filename, lineno, serial, current_serial); current_serial = serial; err++; } if (n != 3 || ns_samename(origin, zp->z_origin) != 1) err++; if (cp) lineno++; 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 */ if (ns_parse_ttl(buf, &l) < 0) { err++; break; } ttl = l; (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; n++; memcpy(data + n + 1, buf, i); n += 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 || ns_samename(dname, zp->z_origin) != 1) { err++; break; } c = getnonblank(fp, zp->z_updatelog, 0); if (c == '(') { multiline = 1; } else { multiline = 0; ungetc(c, fp); } n = getnum(fp, zp->z_updatelog, GETNUM_SERIAL, &multiline); if (getnum_error) { err++; break; } if (opcode == ADD) *new_serial = n; current_serial = n; PUTLONG(n, cp); for (i = 0; i < 4; i++) { if (!getword(buf, sizeof buf, fp, 1)) { err++; break; } if (ns_parse_ttl(buf, &l) < 0) { err++; break; } n = l; PUTLONG(n, cp); } if (multiline) { c = getnonblank(fp, zp->z_updatelog, 1); if (c != ')') { ungetc(c, fp); err++; break; } } endline(fp); n = cp - data; 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 > MAXCHARSTRING) { if (datasize <= MAXCHARSTRING) { ns_error(ns_log_update, "record too big"); return (-1); } datasize -= MAXCHARSTRING; *cp++ = (char)MAXCHARSTRING; memcpy(cp, cp1, MAXCHARSTRING); cp += MAXCHARSTRING; cp1 += MAXCHARSTRING; i -= MAXCHARSTRING; } 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; case ns_t_sig: case ns_t_nxt: case ns_t_key: case ns_t_cert:{ char *errmsg = NULL; n = parse_sec_rdata(buf, sizeof(buf), 1, (u_char *) data, sizeof(data), fp, zp, dname, ttl, type, domain_ctx, transport, &errmsg); if (errmsg) { err++; endline(fp); n = 0; } 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 = zp->z_class; } } } 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); return (DBIXFR_ERROR); } 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; rrecp->r_opcode = opcode; } else { rrecp->r_zone = zonenum; rrecp->r_opcode = opcode; } /* remove add/delete pairs */ if (section == S_UPDATE) { ns_updrec *arp; int foundmatch; arp = TAIL(*listuprec); foundmatch = 0; while (arp) { if (arp->r_section == S_UPDATE && ((arp->r_opcode == DELETE && opcode == ADD) || (opcode == DELETE && arp->r_opcode == ADD)) && arp->r_dp->d_type == dp->d_type && arp->r_dp->d_class == dp->d_class && arp->r_dp->d_ttl == dp->d_ttl && ns_samename(arp->r_dname, dname) == 1 && db_cmp(arp->r_dp, dp) == 0) { db_freedata(dp); db_freedata(arp->r_dp); UNLINK(*listuprec, arp, r_link); res_freeupdrec(arp); res_freeupdrec(rrecp); foundmatch = 1; break; } arp = PREV(arp, r_link); } if (foundmatch) continue; } APPEND(*listuprec, rrecp, r_link); /* Override zone number with current zone serial number */ rrecp->r_zone = serial; } if (err) return (DBIXFR_ERROR); return (DBIXFR_END); }