/* dns.c Domain Name Service subroutines. */ /* * Copyright (C) 1992 by Ted Lemon. * Copyright (c) 1997 The Internet Software Consortium. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of The Internet Software Consortium nor the names * of its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * This file is based on software written in 1992 by Ted Lemon for * a portable network boot loader. That original code base has been * substantially modified for use in the Internet Software Consortium * DHCP suite. * * These later modifications were done on behalf of the Internet * Software Consortium by Ted Lemon in cooperation * with Vixie Enterprises. To learn more about the Internet Software * Consortium, see ``http://www.vix.com/isc''. To learn more about * Vixie Enterprises, see ``http://www.vix.com''. */ #ifndef lint static char copyright[] = "$Id: dns.c,v 1.6 1998/01/12 01:00:09 mellon Exp $ Copyright (c) 1997 The Internet Software Consortium. All rights reserved.\n"; #endif /* not lint */ #include "dhcpd.h" #include "arpa/nameser.h" int dns_protocol_initialized; int dns_protocol_fd; static int addlabel PROTO ((u_int8_t *, char *)); static int skipname PROTO ((u_int8_t *)); static int copy_out_name PROTO ((u_int8_t *, u_int8_t *, char *)); static int nslookup PROTO ((u_int8_t, char *, int, u_int16_t, u_int16_t)); static int zonelookup PROTO ((u_int8_t, char *, int, u_int16_t)); u_int16_t dns_port; struct dns_query *queries [65536]; /* Initialize the DNS protocol. */ void dns_startup () { struct servent *srv; struct sockaddr_in from; /* Only initialize icmp once. */ if (dns_protocol_initialized) error ("attempted to reinitialize dns protocol"); dns_protocol_initialized = 1; /* Get the protocol number (should be 1). */ srv = getservbyname ("domain", "tcp"); if (srv) dns_port = srv -> s_port; else dns_port = htons (53); /* Get a socket for the DNS protocol. */ dns_protocol_fd = socket (AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (dns_protocol_fd < 0) error ("unable to create dns socket: %m"); pick_name_server (); add_protocol ("dns", dns_protocol_fd, dns_packet, 0); } /* Label manipulation stuff; see RFC1035, page 28 section 4.1.2 and page 30, section 4.1.4. */ /* addlabel copies a label into the specified buffer, putting the length of the label in the first character, the contents of the label in subsequent characters, and returning the length of the conglomeration. */ static int addlabel (buf, label) u_int8_t *buf; char *label; { *buf = strlen (label); memcpy (buf + 1, label, *buf); return *buf + 1; } /* skipname skips over all of the labels in a single domain name, returning the length of the domain name. */ static int skipname (label) u_int8_t *label; { if (*label & INDIR_MASK) return 2; if (*label == 0) return 1; return *label + 1 + skipname (label + *label + 1); } /* copy_out_name copies out the name appearing at the specified location into a string, stored as fields seperated by dots rather than lengths and labels. The length of the label-formatted name is returned. */ static int copy_out_name (base, name, buf) u_int8_t *base; u_int8_t *name; char *buf; { if (*name & INDIR_MASK) { int offset = (*name & ~INDIR_MASK) + (*name + 1); return copy_out_name (base, base + offset, buf); } if (!*name) { *buf = 0; return 1; } memcpy (buf, name + 1, *name); *(buf + *name) = '.'; return (*name + 1 + copy_out_name (base, name + *name + 1, buf + *name + 1)); } /* ns_inaddr_lookup constructs a PTR lookup query for an internet address - e.g., 1.200.9.192.in-addr.arpa. If the specified timeout period passes before the query is satisfied, or if the query fails, the callback is called with a null pointer. Otherwise, the callback is called with the address of the string returned by the name server. */ struct dns_query *ns_inaddr_lookup (inaddr, wakeup) struct iaddr inaddr; struct dns_wakeup *wakeup; { unsigned char question [512]; unsigned char *s; unsigned char *label; int i; unsigned char c; s = question; /* Copy out the digits. */ for (i = 3; i >= 0; --i) { label = s++; *label = 1; c = inaddr.iabuf [i]; if (c > 100) { ++*label; *s++ = '0' + c / 100; } if (c > 10) { ++*label; *s++ = '0' + ((c / 10) % 10); } *s++ = '0' + (c % 10); } s += addlabel (s, "in-addr"); s += addlabel (s, "arpa"); *s++ = 0; /* Set the query type. */ putUShort (s, T_PTR); s += sizeof (u_int16_t); /* Set the query class. */ putUShort (s, C_IN); s += sizeof (u_int16_t); return ns_query (question, s - question, wakeup); } struct dns_query *ns_query (question, len, wakeup) unsigned char *question; int len; struct dns_wakeup *wakeup; { HEADER *hdr; struct dns_query *query; unsigned char *s; /* See if there's already a query for this name, and allocate a query if none exists. */ query = find_dns_query (question, len, 1); /* If we can't allocate a query, report that the query failed. */ if (!query) { return (struct dns_query *)-1; } /* If the query has already been answered, return it. */ if (query -> expiry > cur_time) return query; /* The query hasn't yet been answered, so we have to wait, one way or another. Put the wakeup on the list. */ if (wakeup) { wakeup -> next = query -> wakeups; query -> wakeups = wakeup; } /* If the query has already been sent, but we don't yet have an answer, we're done. */ if (query -> sent) return (struct dns_query *)0; /* Construct a header... */ hdr = (HEADER *)query -> buf; memset (hdr, 0, sizeof *hdr); hdr -> id = query -> id; hdr -> rd = 1; hdr -> opcode = QUERY; hdr -> qdcount = htons (1); /* Copy the name into the buffer. */ s = (unsigned char *)hdr + 1; memcpy (s, question, len); query -> question = s; query -> question_len = len; /* Figure out how long the whole message is */ s += len; query -> len = s - query -> buf; /* Flag the query as having been sent. */ query -> sent = 1; /* Send the query. */ dns_timeout (query); /* No answer yet, obviously. */ return (struct dns_query *)0; } /* Retransmit a DNS query. */ void dns_timeout (qv) void *qv; { struct dns_query *query = qv; int status; /* Choose the server to send to. */ if (!query -> next_server) query -> next_server = first_name_server (); /* Send the query. */ if (query -> next_server) status = sendto (dns_protocol_fd, query -> buf, query -> len, 0, ((struct sockaddr *)&query -> next_server -> addr), sizeof query -> next_server -> addr); else status = -1; /* Look for the next server... */ query -> next_server = query -> next_server -> next; /* If this is our first time, backoff one second. */ if (!query -> backoff) query -> backoff = 1; /* If the send failed, don't advance the backoff. */ else if (status < 0) ; /* If we haven't run out of servers to try, don't backoff. */ else if (query -> next_server) ; /* If we haven't backed off enough yet, back off some more. */ else if (query -> backoff < 30) query -> backoff += random() % query -> backoff; /* Set up the timeout. */ add_timeout (cur_time + query -> backoff, dns_timeout, query); } /* Process a reply from a name server. */ void dns_packet (protocol) struct protocol *protocol; { HEADER *ns_header; struct sockaddr_in from; struct dns_wakeup *wakeup; unsigned char buf [512]; unsigned char *base; unsigned char *dptr, *name; u_int16_t type; u_int16_t class; TIME ttl; u_int16_t rdlength; int len, status; int i; struct dns_query *query; len = sizeof from; status = recvfrom (protocol -> fd, buf, sizeof buf, 0, (struct sockaddr *)&from, &len); if (status < 0) { warn ("dns_packet: %m"); return; } /* Response is too long? */ if (len > 512) { warn ("dns_packet: dns message too long (%d)", len); return; } ns_header = (HEADER *)buf; base = (unsigned char *)(ns_header + 1); /* Parse the response... */ dptr = base; /* If this is a response to a query from us, there should have been only one query. */ if (ntohs (ns_header -> qdcount) != 1) { dns_bogus (buf, len); return; } /* Find the start of the name in the query. */ name = dptr; /* Skip over the name. */ dptr += skipname (dptr); /* Skip over the query type and query class. */ dptr += 2 * sizeof (u_int16_t); /* See if we asked this question. */ query = find_dns_query (name, dptr - name, 0); if (!query) { dns_bogus (buf, len); return; } /* Save the response. */ memcpy (buf, query -> buf, len); /* Remember where the question is and how long it is. */ query -> question = name; query -> question_len = dptr - name; /* Remember where the answer is and how long it is. */ query -> answer = dptr; query -> answer_len = len - (dptr - buf); /* Wake up everybody who's waiting. */ for (wakeup = query -> wakeups; wakeup; wakeup = wakeup -> next) { (*wakeup -> func) (query); } }