403 lines
10 KiB
C
403 lines
10 KiB
C
|
|
/*
|
||
|
|
* ldns-notify.c - ldns-notify(8)
|
||
|
|
*
|
||
|
|
* Copyright (c) 2001-2008, NLnet Labs, All right reserved
|
||
|
|
*
|
||
|
|
* See LICENSE for the license
|
||
|
|
*
|
||
|
|
* send a notify packet to a server
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include "config.h"
|
||
|
|
|
||
|
|
/* ldns */
|
||
|
|
#include <ldns/ldns.h>
|
||
|
|
|
||
|
|
#ifdef HAVE_NETDB_H
|
||
|
|
#include <netdb.h>
|
||
|
|
#endif
|
||
|
|
#include <errno.h>
|
||
|
|
|
||
|
|
static int verbose = 1;
|
||
|
|
static int max_num_retry = 15; /* times to try */
|
||
|
|
|
||
|
|
static void
|
||
|
|
usage(void)
|
||
|
|
{
|
||
|
|
fprintf(stderr, "usage: ldns-notify [other options] -z zone <servers>\n");
|
||
|
|
fprintf(stderr, "Ldns notify utility\n\n");
|
||
|
|
fprintf(stderr, " Supported options:\n");
|
||
|
|
fprintf(stderr, "\t-z zone\t\tThe zone\n");
|
||
|
|
fprintf(stderr, "\t-I <address>\tsource address to query from\n");
|
||
|
|
fprintf(stderr, "\t-s version\tSOA version number to include\n");
|
||
|
|
fprintf(stderr, "\t-y <name:key[:algo]>\tspecify named base64 tsig key"
|
||
|
|
", and optional an\n\t\t\t"
|
||
|
|
"algorithm (defaults to hmac-md5.sig-alg.reg.int)\n");
|
||
|
|
fprintf(stderr, "\t-p port\t\tport to use to send to\n");
|
||
|
|
fprintf(stderr, "\t-v\t\tPrint version information\n");
|
||
|
|
fprintf(stderr, "\t-d\t\tPrint verbose debug information\n");
|
||
|
|
fprintf(stderr, "\t-r num\t\tmax number of retries (%d)\n",
|
||
|
|
max_num_retry);
|
||
|
|
fprintf(stderr, "\t-h\t\tPrint this help information\n\n");
|
||
|
|
fprintf(stderr, "Report bugs to <dns-team@nlnetlabs.nl>\n");
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void
|
||
|
|
version(void)
|
||
|
|
{
|
||
|
|
fprintf(stderr, "%s version %s\n", PACKAGE_NAME, PACKAGE_VERSION);
|
||
|
|
fprintf(stderr, "Written by NLnet Labs.\n\n");
|
||
|
|
fprintf(stderr,
|
||
|
|
"Copyright (C) 2001-2008 NLnet Labs. This is free software.\n"
|
||
|
|
"There is NO warranty; not even for MERCHANTABILITY or FITNESS\n"
|
||
|
|
"FOR A PARTICULAR PURPOSE.\n");
|
||
|
|
exit(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void
|
||
|
|
notify_host(int s, struct addrinfo* res, uint8_t* wire, size_t wiresize,
|
||
|
|
const char* addrstr)
|
||
|
|
{
|
||
|
|
int timeout_retry = 5; /* seconds */
|
||
|
|
int num_retry = max_num_retry;
|
||
|
|
#ifndef S_SPLINT_S
|
||
|
|
fd_set rfds;
|
||
|
|
#endif
|
||
|
|
struct timeval tv;
|
||
|
|
int retval = 0;
|
||
|
|
ssize_t received = 0;
|
||
|
|
int got_ack = 0;
|
||
|
|
socklen_t addrlen = 0;
|
||
|
|
uint8_t replybuf[2048];
|
||
|
|
ldns_status status;
|
||
|
|
ldns_pkt* pkt = NULL;
|
||
|
|
|
||
|
|
while(!got_ack) {
|
||
|
|
/* send it */
|
||
|
|
if(sendto(s, (void*)wire, wiresize, 0,
|
||
|
|
res->ai_addr, res->ai_addrlen) == -1) {
|
||
|
|
printf("warning: send to %s failed: %s\n",
|
||
|
|
addrstr, strerror(errno));
|
||
|
|
#ifndef USE_WINSOCK
|
||
|
|
close(s);
|
||
|
|
#else
|
||
|
|
closesocket(s);
|
||
|
|
#endif
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* wait for ACK packet */
|
||
|
|
#ifndef S_SPLINT_S
|
||
|
|
FD_ZERO(&rfds);
|
||
|
|
FD_SET(s, &rfds);
|
||
|
|
tv.tv_sec = timeout_retry; /* seconds */
|
||
|
|
#endif
|
||
|
|
tv.tv_usec = 0; /* microseconds */
|
||
|
|
retval = select(s + 1, &rfds, NULL, NULL, &tv);
|
||
|
|
if (retval == -1) {
|
||
|
|
printf("error waiting for reply from %s: %s\n",
|
||
|
|
addrstr, strerror(errno));
|
||
|
|
#ifndef USE_WINSOCK
|
||
|
|
close(s);
|
||
|
|
#else
|
||
|
|
closesocket(s);
|
||
|
|
#endif
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if(retval == 0) {
|
||
|
|
num_retry--;
|
||
|
|
if(num_retry == 0) {
|
||
|
|
printf("error: failed to send notify to %s.\n",
|
||
|
|
addrstr);
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
printf("timeout (%d s) expired, retry notify to %s.\n",
|
||
|
|
timeout_retry, addrstr);
|
||
|
|
}
|
||
|
|
if (retval == 1) {
|
||
|
|
got_ack = 1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* got reply */
|
||
|
|
addrlen = res->ai_addrlen;
|
||
|
|
received = recvfrom(s, (void*)replybuf, sizeof(replybuf), 0,
|
||
|
|
res->ai_addr, &addrlen);
|
||
|
|
res->ai_addrlen = addrlen;
|
||
|
|
|
||
|
|
#ifndef USE_WINSOCK
|
||
|
|
close(s);
|
||
|
|
#else
|
||
|
|
closesocket(s);
|
||
|
|
#endif
|
||
|
|
if (received == -1) {
|
||
|
|
printf("recv %s failed: %s\n", addrstr, strerror(errno));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* check reply */
|
||
|
|
status = ldns_wire2pkt(&pkt, replybuf, (size_t)received);
|
||
|
|
if(status != LDNS_STATUS_OK) {
|
||
|
|
ssize_t i;
|
||
|
|
printf("Could not parse reply packet: %s\n",
|
||
|
|
ldns_get_errorstr_by_id(status));
|
||
|
|
if (verbose > 1) {
|
||
|
|
printf("hexdump of reply: ");
|
||
|
|
for(i=0; i<received; i++)
|
||
|
|
printf("%02x", (unsigned)replybuf[i]);
|
||
|
|
printf("\n");
|
||
|
|
}
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
if(verbose) {
|
||
|
|
ssize_t i;
|
||
|
|
printf("# reply from %s:\n", addrstr);
|
||
|
|
ldns_pkt_print(stdout, pkt);
|
||
|
|
if (verbose > 1) {
|
||
|
|
printf("hexdump of reply: ");
|
||
|
|
for(i=0; i<received; i++)
|
||
|
|
printf("%02x", (unsigned)replybuf[i]);
|
||
|
|
printf("\n");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
ldns_pkt_free(pkt);
|
||
|
|
}
|
||
|
|
|
||
|
|
int
|
||
|
|
main(int argc, char **argv)
|
||
|
|
{
|
||
|
|
int c;
|
||
|
|
int i;
|
||
|
|
|
||
|
|
/* LDNS types */
|
||
|
|
ldns_pkt *notify;
|
||
|
|
ldns_rr *question;
|
||
|
|
ldns_rdf *ldns_zone_name = NULL;
|
||
|
|
ldns_status status;
|
||
|
|
const char *zone_name = NULL;
|
||
|
|
int include_soa = 0;
|
||
|
|
uint32_t soa_version = 0;
|
||
|
|
int do_hexdump = 1;
|
||
|
|
uint8_t *wire = NULL;
|
||
|
|
size_t wiresize = 0;
|
||
|
|
const char *port = "53";
|
||
|
|
char *tsig_sep;
|
||
|
|
const char *tsig_name = NULL, *tsig_data = NULL, *tsig_algo = NULL;
|
||
|
|
int error;
|
||
|
|
struct addrinfo from_hints, *from0 = NULL;
|
||
|
|
|
||
|
|
srandom(time(NULL) ^ getpid());
|
||
|
|
|
||
|
|
while ((c = getopt(argc, argv, "vhdp:r:s:y:z:I:")) != -1) {
|
||
|
|
switch (c) {
|
||
|
|
case 'd':
|
||
|
|
verbose++;
|
||
|
|
break;
|
||
|
|
case 'p':
|
||
|
|
port = optarg;
|
||
|
|
break;
|
||
|
|
case 'r':
|
||
|
|
max_num_retry = atoi(optarg);
|
||
|
|
break;
|
||
|
|
case 's':
|
||
|
|
include_soa = 1;
|
||
|
|
soa_version = (uint32_t)atoi(optarg);
|
||
|
|
break;
|
||
|
|
case 'y':
|
||
|
|
if (!(tsig_sep = strchr(optarg, ':'))) {
|
||
|
|
printf("TSIG argument is not in form "
|
||
|
|
"<name:key[:algo]> %s\n", optarg);
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
tsig_name = optarg;
|
||
|
|
*tsig_sep++ = '\0';
|
||
|
|
tsig_data = tsig_sep;
|
||
|
|
if ((tsig_sep = strchr(tsig_sep, ':'))) {
|
||
|
|
*tsig_sep++ = '\0';
|
||
|
|
tsig_algo = tsig_sep;
|
||
|
|
} else {
|
||
|
|
tsig_algo = "hmac-md5.sig-alg.reg.int.";
|
||
|
|
}
|
||
|
|
/* With dig TSIG keys are also specified with -y,
|
||
|
|
* but format with drill is: -y <name:key[:algo]>
|
||
|
|
* and with dig: -y [hmac:]name:key
|
||
|
|
*
|
||
|
|
* When we detect an unknown tsig algorithm in algo,
|
||
|
|
* but a known algorithm in name, we cane assume dig
|
||
|
|
* order was used.
|
||
|
|
*
|
||
|
|
* Following if statement is to anticipate and correct
|
||
|
|
* dig order
|
||
|
|
*/
|
||
|
|
if (strcasecmp(tsig_algo, "hmac-md5.sig-alg.reg.int")&&
|
||
|
|
strcasecmp(tsig_algo, "hmac-md5") &&
|
||
|
|
strcasecmp(tsig_algo, "hmac-sha1") &&
|
||
|
|
strcasecmp(tsig_algo, "hmac-sha256") &&
|
||
|
|
strcasecmp(tsig_algo, "hmac-sha384") &&
|
||
|
|
strcasecmp(tsig_algo, "hmac-sha512") &&
|
||
|
|
! (strcasecmp(tsig_name, "hmac-md5.sig-alg.reg.int")
|
||
|
|
&& strcasecmp(tsig_name, "hmac-md5")
|
||
|
|
&& strcasecmp(tsig_name, "hmac-sha1")
|
||
|
|
&& strcasecmp(tsig_name, "hmac-sha256")
|
||
|
|
&& strcasecmp(tsig_name, "hmac-sha384")
|
||
|
|
&& strcasecmp(tsig_name, "hmac-sha512"))) {
|
||
|
|
|
||
|
|
/* Roll options */
|
||
|
|
const char *tmp_tsig_algo = tsig_name;
|
||
|
|
tsig_name = tsig_data;
|
||
|
|
tsig_data = tsig_algo;
|
||
|
|
tsig_algo = tmp_tsig_algo;
|
||
|
|
}
|
||
|
|
printf("Sign with name: %s, data: %s, algorithm: %s\n"
|
||
|
|
, tsig_name, tsig_data, tsig_algo);
|
||
|
|
break;
|
||
|
|
case 'z':
|
||
|
|
zone_name = optarg;
|
||
|
|
ldns_zone_name = ldns_dname_new_frm_str(zone_name);
|
||
|
|
if(!ldns_zone_name) {
|
||
|
|
printf("cannot parse zone name: %s\n",
|
||
|
|
zone_name);
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 'I':
|
||
|
|
memset(&from_hints, 0, sizeof(from_hints));
|
||
|
|
from_hints.ai_family = AF_UNSPEC;
|
||
|
|
from_hints.ai_socktype = SOCK_DGRAM;
|
||
|
|
from_hints.ai_protocol = IPPROTO_UDP;
|
||
|
|
from_hints.ai_flags = AI_NUMERICHOST;
|
||
|
|
error = getaddrinfo(optarg, 0, &from_hints, &from0);
|
||
|
|
if (error) {
|
||
|
|
printf("bad address: %s: %s\n", optarg,
|
||
|
|
gai_strerror(error));
|
||
|
|
exit(EXIT_FAILURE);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
case 'v':
|
||
|
|
version();
|
||
|
|
/* fallthrough */
|
||
|
|
case 'h':
|
||
|
|
case '?':
|
||
|
|
default:
|
||
|
|
usage();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
argc -= optind;
|
||
|
|
argv += optind;
|
||
|
|
|
||
|
|
if (argc == 0 || zone_name == NULL) {
|
||
|
|
usage();
|
||
|
|
}
|
||
|
|
|
||
|
|
notify = ldns_pkt_new();
|
||
|
|
question = ldns_rr_new();
|
||
|
|
|
||
|
|
if (!notify || !question) {
|
||
|
|
/* bail out */
|
||
|
|
printf("error: cannot create ldns types\n");
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* create the rr for inside the pkt */
|
||
|
|
ldns_rr_set_class(question, LDNS_RR_CLASS_IN);
|
||
|
|
ldns_rr_set_owner(question, ldns_zone_name);
|
||
|
|
ldns_rr_set_type(question, LDNS_RR_TYPE_SOA);
|
||
|
|
ldns_rr_set_question(question, true);
|
||
|
|
ldns_pkt_set_opcode(notify, LDNS_PACKET_NOTIFY);
|
||
|
|
ldns_pkt_push_rr(notify, LDNS_SECTION_QUESTION, question);
|
||
|
|
ldns_pkt_set_aa(notify, true);
|
||
|
|
ldns_pkt_set_random_id(notify);
|
||
|
|
if(include_soa) {
|
||
|
|
char buf[10240];
|
||
|
|
ldns_rr *soa_rr=NULL;
|
||
|
|
ldns_rdf *prev=NULL;
|
||
|
|
snprintf(buf, sizeof(buf), "%s 3600 IN SOA . . %u 0 0 0 0",
|
||
|
|
zone_name, (unsigned)soa_version);
|
||
|
|
/*printf("Adding soa %s\n", buf);*/
|
||
|
|
status = ldns_rr_new_frm_str(&soa_rr, buf, 3600, NULL, &prev);
|
||
|
|
if(status != LDNS_STATUS_OK) {
|
||
|
|
printf("Error adding SOA version: %s\n",
|
||
|
|
ldns_get_errorstr_by_id(status));
|
||
|
|
}
|
||
|
|
ldns_pkt_push_rr(notify, LDNS_SECTION_ANSWER, soa_rr);
|
||
|
|
}
|
||
|
|
|
||
|
|
if(tsig_name && tsig_data) {
|
||
|
|
#ifdef HAVE_SSL
|
||
|
|
status = ldns_pkt_tsig_sign(notify, tsig_name,
|
||
|
|
tsig_data, 300, tsig_algo, NULL);
|
||
|
|
if(status != LDNS_STATUS_OK) {
|
||
|
|
printf("Error TSIG sign query: %s\n",
|
||
|
|
ldns_get_errorstr_by_id(status));
|
||
|
|
}
|
||
|
|
#else
|
||
|
|
fprintf(stderr, "Warning: TSIG needs OpenSSL support, which has not been compiled in, TSIG skipped\n");
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
|
||
|
|
if(verbose) {
|
||
|
|
printf("# Sending packet:\n");
|
||
|
|
ldns_pkt_print(stdout, notify);
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
status = ldns_pkt2wire(&wire, notify, &wiresize);
|
||
|
|
if (status) {
|
||
|
|
printf("Error converting notify packet to hex: %s\n",
|
||
|
|
ldns_get_errorstr_by_id(status));
|
||
|
|
} else if(wiresize == 0) {
|
||
|
|
printf("Error converting notify packet to hex.\n");
|
||
|
|
exit(1);
|
||
|
|
}
|
||
|
|
if(do_hexdump && verbose > 1) {
|
||
|
|
printf("Hexdump of notify packet:\n");
|
||
|
|
for(i=0; i<(int)wiresize; i++)
|
||
|
|
printf("%02x", (unsigned)wire[i]);
|
||
|
|
printf("\n");
|
||
|
|
}
|
||
|
|
|
||
|
|
for(i=0; i<argc; i++)
|
||
|
|
{
|
||
|
|
struct addrinfo hints, *res0, *ai_res;
|
||
|
|
int default_family = AF_UNSPEC;
|
||
|
|
|
||
|
|
if(verbose)
|
||
|
|
printf("# sending to %s\n", argv[i]);
|
||
|
|
memset(&hints, 0, sizeof(hints));
|
||
|
|
hints.ai_family = default_family;
|
||
|
|
/* if(strchr(argv[i], ':')) hints.ai_family = AF_INET6; */
|
||
|
|
hints.ai_socktype = SOCK_DGRAM;
|
||
|
|
hints.ai_protocol = IPPROTO_UDP;
|
||
|
|
error = getaddrinfo(argv[i], port, &hints, &res0);
|
||
|
|
if (error) {
|
||
|
|
printf("skipping bad address: %s: %s\n", argv[i],
|
||
|
|
gai_strerror(error));
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
for (ai_res = res0; ai_res; ai_res = ai_res->ai_next) {
|
||
|
|
int s;
|
||
|
|
|
||
|
|
if (from0 && ai_res->ai_family != from0->ai_family)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
s = socket(ai_res->ai_family, ai_res->ai_socktype,
|
||
|
|
ai_res->ai_protocol);
|
||
|
|
if(s == -1)
|
||
|
|
continue;
|
||
|
|
if (from0 && bind(s, from0->ai_addr, from0->ai_addrlen)) {
|
||
|
|
perror("Could not bind to source IP");
|
||
|
|
exit(EXIT_FAILURE);
|
||
|
|
}
|
||
|
|
/* send the notify */
|
||
|
|
notify_host(s, ai_res, wire, wiresize, argv[i]);
|
||
|
|
}
|
||
|
|
freeaddrinfo(res0);
|
||
|
|
}
|
||
|
|
|
||
|
|
ldns_pkt_free(notify);
|
||
|
|
free(wire);
|
||
|
|
return 0;
|
||
|
|
}
|