472 lines
12 KiB
C
472 lines
12 KiB
C
|
#if !defined(lint) && !defined(SABER)
|
||
|
static char rcsid[] = "$Id: nsupdate.c,v 8.5 1998/02/14 20:54:48 halley Exp $";
|
||
|
#endif /* not lint */
|
||
|
|
||
|
/*
|
||
|
* Copyright (c) 1996 by Internet Software Consortium.
|
||
|
*
|
||
|
* Permission to use, copy, modify, and distribute this software for any
|
||
|
* purpose with or without fee is hereby granted, provided that the above
|
||
|
* copyright notice and this permission notice appear in all copies.
|
||
|
*
|
||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
|
||
|
* ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
|
||
|
* OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
|
||
|
* CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
||
|
* DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
|
||
|
* PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
|
||
|
* ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
|
||
|
* SOFTWARE.
|
||
|
*/
|
||
|
|
||
|
#include "port_before.h"
|
||
|
#include <sys/param.h>
|
||
|
#include <sys/socket.h>
|
||
|
#include <netinet/in.h>
|
||
|
#include <arpa/inet.h>
|
||
|
#include <arpa/nameser.h>
|
||
|
#include <errno.h>
|
||
|
#include <limits.h>
|
||
|
#include <netdb.h>
|
||
|
#include <resolv.h>
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <ctype.h>
|
||
|
#include "port_after.h"
|
||
|
|
||
|
/* XXX all of this stuff should come from libbind.a */
|
||
|
|
||
|
/*
|
||
|
* Map class and type names to number
|
||
|
*/
|
||
|
struct map {
|
||
|
char token[10];
|
||
|
int val;
|
||
|
};
|
||
|
|
||
|
struct map class_strs[] = {
|
||
|
{ "in", C_IN },
|
||
|
{ "chaos", C_CHAOS },
|
||
|
{ "hs", C_HS },
|
||
|
};
|
||
|
#define M_CLASS_CNT (sizeof(class_strs) / sizeof(struct map))
|
||
|
|
||
|
struct map type_strs[] = {
|
||
|
{ "a", T_A },
|
||
|
{ "ns", T_NS },
|
||
|
{ "cname", T_CNAME },
|
||
|
{ "soa", T_SOA },
|
||
|
{ "mb", T_MB },
|
||
|
{ "mg", T_MG },
|
||
|
{ "mr", T_MR },
|
||
|
{ "null", T_NULL },
|
||
|
{ "wks", T_WKS },
|
||
|
{ "ptr", T_PTR },
|
||
|
{ "hinfo", T_HINFO },
|
||
|
{ "minfo", T_MINFO },
|
||
|
{ "mx", T_MX },
|
||
|
{ "txt", T_TXT },
|
||
|
{ "rp", T_RP },
|
||
|
{ "afsdb", T_AFSDB },
|
||
|
{ "x25", T_X25 },
|
||
|
{ "isdn", T_ISDN },
|
||
|
{ "rt", T_RT },
|
||
|
{ "nsap", T_NSAP },
|
||
|
{ "nsap_ptr", T_NSAP_PTR },
|
||
|
{ "px", T_PX },
|
||
|
{ "loc", T_LOC },
|
||
|
};
|
||
|
#define M_TYPE_CNT (sizeof(type_strs) / sizeof(struct map))
|
||
|
|
||
|
struct map section_strs[] = {
|
||
|
{ "zone", S_ZONE },
|
||
|
{ "prereq", S_PREREQ },
|
||
|
{ "update", S_UPDATE },
|
||
|
{ "reserved", S_ADDT },
|
||
|
};
|
||
|
#define M_SECTION_CNT (sizeof(section_strs) / sizeof(struct map))
|
||
|
|
||
|
struct map opcode_strs[] = {
|
||
|
{ "nxdomain", NXDOMAIN },
|
||
|
{ "yxdomain", YXDOMAIN },
|
||
|
{ "nxrrset", NXRRSET },
|
||
|
{ "yxrrset", YXRRSET },
|
||
|
{ "delete", DELETE },
|
||
|
{ "add", ADD },
|
||
|
};
|
||
|
#define M_OPCODE_CNT (sizeof(opcode_strs) / sizeof(struct map))
|
||
|
|
||
|
static char *progname;
|
||
|
static FILE *log;
|
||
|
|
||
|
static void usage(void);
|
||
|
static int getword_str(char *, int, char **, char *);
|
||
|
|
||
|
/*
|
||
|
* format of file read by nsupdate is kept the same as the log
|
||
|
* file generated by updates, so that the log file can be fed
|
||
|
* to nsupdate to reconstruct lost updates.
|
||
|
*
|
||
|
* file is read on line at a time using fgets() rather than
|
||
|
* one word at a time using getword() so that it is easy to
|
||
|
* adapt nsupdate to read piped input from other scripts
|
||
|
*
|
||
|
* overloading of class/type has to be deferred to res_update()
|
||
|
* because class is needed by res_update() to determined the
|
||
|
* zone to which a resource record belongs
|
||
|
*/
|
||
|
int
|
||
|
main(argc, argv)
|
||
|
int argc;
|
||
|
char **argv;
|
||
|
{
|
||
|
FILE *fp = NULL;
|
||
|
char buf[BUFSIZ], buf2[BUFSIZ], hostbuf[100], filebuf[100];
|
||
|
char dnbuf[MAXDNAME];
|
||
|
u_char packet[PACKETSZ], answer[PACKETSZ];
|
||
|
char *host = hostbuf, *batchfile = filebuf;
|
||
|
char *r_dname, *cp, *startp, *endp, *svstartp;
|
||
|
char section[15], opcode[10];
|
||
|
int i, c, n, n1, inside, lineno = 0, vc = 0,
|
||
|
debug = 0, r_size, r_section, r_opcode,
|
||
|
prompt = 0, ret = 0;
|
||
|
int16_t r_class, r_type;
|
||
|
u_int32_t r_ttl;
|
||
|
struct map *mp;
|
||
|
ns_updrec *rrecp_start = NULL, *rrecp, *tmprrecp;
|
||
|
struct in_addr hostaddr;
|
||
|
extern int getopt();
|
||
|
extern char *optarg;
|
||
|
extern int optind, opterr, optopt;
|
||
|
|
||
|
|
||
|
progname = argv[0];
|
||
|
|
||
|
while ((c = getopt(argc, argv, "dv")) != EOF) {
|
||
|
switch (c) {
|
||
|
case 'v':
|
||
|
vc = 1;
|
||
|
break;
|
||
|
case 'd':
|
||
|
debug = 1;
|
||
|
break;
|
||
|
default:
|
||
|
usage();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ((argc - optind) == 0) {
|
||
|
/* no file specified, read from stdin */
|
||
|
ret = system("tty -s");
|
||
|
if (ret == 0) /* terminal */
|
||
|
prompt = 1;
|
||
|
else /* stdin redirect from a file or a pipe */
|
||
|
prompt = 0;
|
||
|
} else {
|
||
|
/* file specified, open it */
|
||
|
/* XXX - currently accepts only one filename */
|
||
|
if ((fp = fopen(argv[optind], "r")) == NULL) {
|
||
|
fprintf(stderr, "error opening file: %s\n", argv[optind]);
|
||
|
exit (1);
|
||
|
}
|
||
|
}
|
||
|
for (;;) {
|
||
|
|
||
|
inside = 1;
|
||
|
if (prompt)
|
||
|
fprintf(stdout, "> ");
|
||
|
if (!fp)
|
||
|
cp = fgets(buf, sizeof buf, stdin);
|
||
|
else
|
||
|
cp = fgets(buf, sizeof buf, fp);
|
||
|
if (cp == NULL) /* EOF */
|
||
|
break;
|
||
|
lineno++;
|
||
|
|
||
|
/* get rid of the trailing newline */
|
||
|
n = strlen(buf);
|
||
|
buf[--n] = '\0';
|
||
|
|
||
|
startp = cp;
|
||
|
endp = strchr(cp, ';');
|
||
|
if (endp != NULL)
|
||
|
endp--;
|
||
|
else
|
||
|
endp = cp + n - 1;
|
||
|
|
||
|
/* verify section name */
|
||
|
if (!getword_str(section, sizeof section, &startp, endp)) {
|
||
|
/* empty line */
|
||
|
inside = 0;
|
||
|
}
|
||
|
if (inside) {
|
||
|
/* inside the same update packet,
|
||
|
* continue accumulating records */
|
||
|
r_section = -1;
|
||
|
n1 = strlen(section);
|
||
|
if (section[n1-1] == ':')
|
||
|
section[--n1] = '\0';
|
||
|
for (mp = section_strs; mp < section_strs+M_SECTION_CNT; mp++)
|
||
|
if (!strcasecmp(section, mp->token)) {
|
||
|
r_section = mp->val;
|
||
|
break;
|
||
|
}
|
||
|
if (r_section == -1) {
|
||
|
fprintf(stderr, "incorrect section name: %s\n", section);
|
||
|
exit (1);
|
||
|
}
|
||
|
if (r_section == S_ZONE) {
|
||
|
fprintf(stderr, "section ZONE not permitted\n");
|
||
|
exit (1);
|
||
|
}
|
||
|
/* read operation code */
|
||
|
if (!getword_str(opcode, sizeof opcode, &startp, endp)) {
|
||
|
fprintf(stderr, "failed to read operation code\n");
|
||
|
exit (1);
|
||
|
}
|
||
|
r_opcode = -1;
|
||
|
if (opcode[0] == '{') {
|
||
|
n1 = strlen(opcode);
|
||
|
for (i = 0; i < n1; i++)
|
||
|
opcode[i] = opcode[i+1];
|
||
|
if (opcode[n1-2] == '}')
|
||
|
opcode[n1-2] = '\0';
|
||
|
}
|
||
|
for (mp = opcode_strs; mp < opcode_strs+M_OPCODE_CNT; mp++) {
|
||
|
if (!strcasecmp(opcode, mp->token)) {
|
||
|
r_opcode = mp->val;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (r_opcode == -1) {
|
||
|
fprintf(stderr, "incorrect operation code: %s\n", opcode);
|
||
|
exit (1);
|
||
|
}
|
||
|
/* read owner's domain name */
|
||
|
if (!getword_str(dnbuf, sizeof dnbuf, &startp, endp)) {
|
||
|
fprintf(stderr, "failed to read owner name\n");
|
||
|
exit (1);
|
||
|
}
|
||
|
r_dname = dnbuf;
|
||
|
r_ttl = 0;
|
||
|
r_type = -1;
|
||
|
r_class = C_IN; /* default to IN */
|
||
|
r_size = 0;
|
||
|
|
||
|
(void) getword_str(buf2, sizeof buf2, &startp, endp);
|
||
|
|
||
|
if (isdigit(buf2[0])) { /* ttl */
|
||
|
r_ttl = strtoul(buf2, 0, 10);
|
||
|
if (errno == ERANGE && r_ttl == ULONG_MAX) {
|
||
|
fprintf(stderr, "oversized ttl: %s\n", buf2);
|
||
|
exit (1);
|
||
|
}
|
||
|
(void) getword_str(buf2, sizeof buf2, &startp, endp);
|
||
|
}
|
||
|
|
||
|
if (buf2[0]) { /* possibly class */
|
||
|
for (mp = class_strs; mp < class_strs+M_CLASS_CNT; mp++) {
|
||
|
if (!strcasecmp(buf2, mp->token)) {
|
||
|
r_class = mp->val;
|
||
|
(void) getword_str(buf2, sizeof buf2, &startp, endp);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
/*
|
||
|
* type and rdata field may or may not be required depending
|
||
|
* on the section and operation
|
||
|
*/
|
||
|
switch (r_section) {
|
||
|
case S_PREREQ:
|
||
|
if (r_ttl) {
|
||
|
fprintf(stderr, "nonzero ttl in prereq section: %ul\n",
|
||
|
r_ttl);
|
||
|
r_ttl = 0;
|
||
|
}
|
||
|
switch (r_opcode) {
|
||
|
case NXDOMAIN:
|
||
|
case YXDOMAIN:
|
||
|
if (buf2[0]) {
|
||
|
fprintf (stderr, "invalid field: %s, ignored\n",
|
||
|
buf2);
|
||
|
exit (1);
|
||
|
}
|
||
|
break;
|
||
|
case NXRRSET:
|
||
|
case YXRRSET:
|
||
|
if (buf2[0])
|
||
|
for (mp = type_strs; mp < type_strs+M_TYPE_CNT; mp++)
|
||
|
if (!strcasecmp(buf2, mp->token)) {
|
||
|
r_type = mp->val;
|
||
|
break;
|
||
|
}
|
||
|
if (r_type == -1) {
|
||
|
fprintf (stderr, "invalid type for RRset: %s\n",
|
||
|
buf2);
|
||
|
exit (1);
|
||
|
}
|
||
|
if (r_opcode == NXRRSET)
|
||
|
break;
|
||
|
/*
|
||
|
* for RRset exists (value dependent) case,
|
||
|
* nonempty rdata field will be present.
|
||
|
* simply copy the whole string now and let
|
||
|
* res_update() interpret the various fields
|
||
|
* depending on type
|
||
|
*/
|
||
|
cp = startp;
|
||
|
while (cp <= endp && isspace(*cp))
|
||
|
cp++;
|
||
|
r_size = endp - cp + 1;
|
||
|
break;
|
||
|
default:
|
||
|
fprintf (stderr,
|
||
|
"unknown operation in prereq section\"%s\"\n",
|
||
|
opcode);
|
||
|
exit (1);
|
||
|
}
|
||
|
break;
|
||
|
case S_UPDATE:
|
||
|
switch (r_opcode) {
|
||
|
case DELETE:
|
||
|
r_ttl = 0;
|
||
|
r_type = T_ANY;
|
||
|
/* read type, if specified */
|
||
|
if (buf2[0])
|
||
|
for (mp = type_strs; mp < type_strs+M_TYPE_CNT; mp++)
|
||
|
if (!strcasecmp(buf2, mp->token)) {
|
||
|
r_type = mp->val;
|
||
|
svstartp = startp;
|
||
|
(void) getword_str(buf2, sizeof buf2,
|
||
|
&startp, endp);
|
||
|
if (buf2[0]) /* unget preference */
|
||
|
startp = svstartp;
|
||
|
break;
|
||
|
}
|
||
|
/* read rdata portion, if specified */
|
||
|
cp = startp;
|
||
|
while (cp <= endp && isspace(*cp))
|
||
|
cp++;
|
||
|
r_size = endp - cp + 1;
|
||
|
break;
|
||
|
case ADD:
|
||
|
if (r_ttl == 0) {
|
||
|
fprintf (stderr,
|
||
|
"ttl must be specified for record to be added: %s\n", buf);
|
||
|
exit (1);
|
||
|
}
|
||
|
/* read type */
|
||
|
if (buf2[0])
|
||
|
for (mp = type_strs; mp < type_strs+M_TYPE_CNT; mp++)
|
||
|
if (!strcasecmp(buf2, mp->token)) {
|
||
|
r_type = mp->val;
|
||
|
break;
|
||
|
}
|
||
|
if (r_type == -1) {
|
||
|
fprintf(stderr,
|
||
|
"invalid type for record to be added: %s\n", buf2);
|
||
|
exit (1);
|
||
|
}
|
||
|
/* read rdata portion */
|
||
|
cp = startp;
|
||
|
while (cp < endp && isspace(*cp))
|
||
|
cp++;
|
||
|
r_size = endp - cp + 1;
|
||
|
if (r_size <= 0) {
|
||
|
fprintf(stderr,
|
||
|
"nonempty rdata field needed to add the record at line %d\n",
|
||
|
lineno);
|
||
|
exit (1);
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
fprintf(stderr,
|
||
|
"unknown operation in update section\"%s\"\n", opcode);
|
||
|
exit (1);
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
fprintf(stderr,
|
||
|
"unknown section identifier \"%s\"\n", section);
|
||
|
exit (1);
|
||
|
}
|
||
|
|
||
|
if ( !(rrecp = res_mkupdrec(r_section, r_dname, r_class,
|
||
|
r_type, r_ttl)) ||
|
||
|
(r_size > 0 && !(rrecp->r_data = (u_char *)malloc(r_size))) ) {
|
||
|
fprintf(stderr, "saverrec error\n");
|
||
|
exit (1);
|
||
|
}
|
||
|
rrecp->r_opcode = r_opcode;
|
||
|
rrecp->r_size = r_size;
|
||
|
(void) strncpy((char *)rrecp->r_data, cp, r_size);
|
||
|
/* append current record to the end of linked list of
|
||
|
* records seen so far */
|
||
|
if (rrecp_start == NULL)
|
||
|
rrecp_start = rrecp;
|
||
|
else {
|
||
|
tmprrecp = rrecp_start;
|
||
|
while (tmprrecp->r_next != NULL)
|
||
|
tmprrecp = tmprrecp->r_next;
|
||
|
tmprrecp->r_next = rrecp;
|
||
|
}
|
||
|
} else { /* end of an update packet */
|
||
|
(void) res_init();
|
||
|
if (vc)
|
||
|
_res.options |= RES_USEVC | RES_STAYOPEN;
|
||
|
if (debug)
|
||
|
_res.options |= RES_DEBUG;
|
||
|
if (rrecp_start) {
|
||
|
if ((n = res_update(rrecp_start)) < 0)
|
||
|
fprintf(stderr, "failed update packet\n");
|
||
|
/* free malloc'ed memory */
|
||
|
while(rrecp_start) {
|
||
|
tmprrecp = rrecp_start;
|
||
|
rrecp_start = rrecp_start->r_next;
|
||
|
free((char *)tmprrecp->r_dname);
|
||
|
free((char *)tmprrecp);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
} /* for */
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
usage() {
|
||
|
fprintf(stderr, "Usage: %s [-d] [-v] [file]\n",
|
||
|
progname);
|
||
|
exit(1);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Get a whitespace delimited word from a string (not file)
|
||
|
* into buf. modify the start pointer to point after the
|
||
|
* word in the string.
|
||
|
*/
|
||
|
static int
|
||
|
getword_str(char *buf, int size, char **startpp, char *endp) {
|
||
|
char *cp;
|
||
|
int c;
|
||
|
|
||
|
for (cp = buf; *startpp <= endp; ) {
|
||
|
c = **startpp;
|
||
|
if (isspace(c) || c == '\0') {
|
||
|
if (cp != buf) /* trailing whitespace */
|
||
|
break;
|
||
|
else { /* leading whitespace */
|
||
|
(*startpp)++;
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
(*startpp)++;
|
||
|
if (cp >= buf+size-1)
|
||
|
break;
|
||
|
*cp++ = (u_char)c;
|
||
|
}
|
||
|
*cp = '\0';
|
||
|
return (cp != buf);
|
||
|
}
|