#include "k5-int.h"
#include <stdio.h>
+#ifdef KRB5_DNS_LOOKUP
+/* for old Unixes and friends ... */
+#ifndef MAXHOSTNAMELEN
+#define MAXHOSTNAMELEN 64
+#endif
+
+#define MAX_DNS_NAMELEN (15*(MAXHOSTNAMELEN + 1)+1)
+
+extern int krb5_try_realm_txt_rr(char *,char *, char **);
+#endif /* KRB5_DNS_LOOKUP */
+
/*
* Retrieves the default realm to be used if no user-specified realm is
* available. [e.g. to interpret a user-typed principal name with the
retval = profile_get_string(context->profile, "libdefaults",
"default_realm", 0, 0,
&context->default_realm);
- if (context->default_realm == 0)
- return(KRB5_CONFIG_NODEFREALM);
+#ifdef KRB5_DNS_LOOKUP
+ if (context->default_realm == 0) {
+ /*
+ * Since this didn't appear in our config file, try looking
+ * it up via DNS. Look for a TXT records of the form:
+ *
+ * _kerberos.<localhost>
+ * _kerberos.<domainname>
+ * _kerberos.<searchlist>
+ *
+ */
+ char localhost[MAX_DNS_NAMELEN+1];
+ char * p;
+ localhost[0] = localhost[sizeof(localhost)-1] = 0;
+ gethostname(localhost,MAX_DNS_NAMELEN);
+
+ if ( localhost[0] ) {
+ p = localhost;
+ do {
+ retval = krb5_try_realm_txt_rr("_kerberos", p,
+ &context->default_realm);
+ p = strchr(p,'.');
+ if (p)
+ p++;
+ } while (retval && p && p[0]);
+
+ if (retval)
+ retval = krb5_try_realm_txt_rr("_kerberos", "",
+ &context->default_realm);
+ } else {
+ retval = krb5_try_realm_txt_rr("_kerberos", "",
+ &context->default_realm);
+ }
+ if (retval) {
+ return(KRB5_CONFIG_NODEFREALM);
+ }
+ }
+#endif /* KRB5_DNS_LOOKUP */
}
-
- realm = context->default_realm;
+ if (context->default_realm == 0)
+ return(KRB5_CONFIG_NODEFREALM);
+ if (context->default_realm[0] == 0) {
+ free (context->default_realm);
+ context->default_realm = 0;
+ return KRB5_CONFIG_NODEFREALM;
+ }
+
+ realm = context->default_realm;
+
if (!(*lrealm = cp = malloc((unsigned int) strlen(realm) + 1)))
return ENOMEM;
strcpy(cp, realm);
#include <strings.h>
#endif
+#ifdef KRB5_DNS_LOOKUP
+#ifdef WSHELPER
+#include <wshelper.h>
+#else /* WSHELPER */
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <resolv.h>
+#include <netdb.h>
+#endif /* WSHELPER */
+#endif /* KRB5_DNS_LOOKUP */
+
/* for old Unixes and friends ... */
#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN 64
#endif
+#define MAX_DNS_NAMELEN (15*(MAXHOSTNAMELEN + 1)+1)
+
+#ifdef KRB5_DNS_LOOKUP
+/*
+ * Try to look up a TXT record pointing to a Kerberos realm
+ */
+
+krb5_error_code
+krb5_try_realm_txt_rr(prefix, name, realm)
+ const char *prefix, *name;
+ char **realm;
+{
+ union {
+ unsigned char bytes[2048];
+ HEADER hdr;
+ } answer;
+ unsigned char *p;
+ char host[MAX_DNS_NAMELEN], *h;
+ int size;
+ int type, class, numanswers, numqueries, rdlen, len;
+
+ /*
+ * Form our query, and send it via DNS
+ */
+
+ if (name == NULL || name[0] == '\0') {
+ strcpy(host,prefix);
+ } else {
+ if ( strlen(prefix) + strlen(name) + 3 > MAX_DNS_NAMELEN )
+ return KRB5_ERR_HOST_REALM_UNKNOWN;
+ sprintf(host,"%s.%s", prefix, name);
+ }
+ /* Realm names don't (normally) end with ".", but if the query
+ doesn't end with "." and doesn't get an answer as is, the
+ resolv code will try appending the local domain. Since the
+ realm names are absolutes, let's stop that. */
+ h = host + strlen (host);
+ if (h > host && h[-1] != '.')
+ strcpy (h, ".");
+
+ size = res_search(host, C_IN, T_TXT, answer.bytes, sizeof(answer.bytes));
+
+ if (size < 0)
+ return KRB5_ERR_HOST_REALM_UNKNOWN;
+
+ p = answer.bytes;
+
+ numqueries = ntohs(answer.hdr.qdcount);
+ numanswers = ntohs(answer.hdr.ancount);
+
+ p += sizeof(HEADER);
+
+ /*
+ * We need to skip over the questions before we can get to the answers,
+ * which means we have to iterate over every query record. We use
+ * dn_expand to tell us how long each compressed name is.
+ */
+
+#define INCR_CHECK(x, y) x += y; if (x > size + answer.bytes) \
+ return KRB5_ERR_HOST_REALM_UNKNOWN
+#define CHECK(x, y) if (x + y > size + answer.bytes) \
+ return KRB5_ERR_HOST_REALM_UNKNOWN
+#define NTOHSP(x, y) x[0] << 8 | x[1]; x += y
+
+ while (numqueries--) {
+ len = dn_expand(answer.bytes, answer.bytes + size, p, host,
+ sizeof(host));
+ if (len < 0)
+ return KRB5_ERR_HOST_REALM_UNKNOWN;
+ INCR_CHECK(p, len + 4); /* Name plus type plus class */
+ }
+
+ /*
+ * We're now pointing at the answer records. Process the first
+ * TXT record we find.
+ */
+
+ while (numanswers--) {
+
+ /* First the name; use dn_expand to get the compressed size */
+ len = dn_expand(answer.bytes, answer.bytes + size, p,
+ host, sizeof(host));
+ if (len < 0)
+ return KRB5_ERR_HOST_REALM_UNKNOWN;
+ INCR_CHECK(p, len);
+
+ /* Next is the query type */
+ CHECK(p, 2);
+ type = NTOHSP(p,2);
+
+ /* Next is the query class; also skip over 4 byte TTL */
+ CHECK(p,6);
+ class = NTOHSP(p,6);
+
+ /* Record data length - make sure we aren't truncated */
+
+ CHECK(p,2);
+ rdlen = NTOHSP(p,2);
+
+ if (p + rdlen > answer.bytes + size)
+ return KRB5_ERR_HOST_REALM_UNKNOWN;
+
+ /*
+ * If this is a TXT record, return the string. Note that the
+ * string has a 1-byte length in the front
+ */
+ /* XXX What about flagging multiple TXT records as an error? */
+
+ if (class == C_IN && type == T_TXT) {
+ len = *p++;
+ if (p + len > answer.bytes + size)
+ return KRB5_ERR_HOST_REALM_UNKNOWN;
+ *realm = malloc(len + 1);
+ if (*realm == NULL)
+ return ENOMEM;
+ strncpy(*realm, (char *) p, len);
+ (*realm)[len] = '\0';
+ /* Avoid a common error. */
+ if ( (*realm)[len-1] == '.' )
+ (*realm)[len-1] = '\0';
+ return 0;
+ }
+ }
+
+ return KRB5_ERR_HOST_REALM_UNKNOWN;
+}
+#endif /* KRB5_DNS_LOOKUP */
+
+
KRB5_DLLIMP krb5_error_code KRB5_CALLCONV
krb5_get_host_realm(context, host, realmsp)
krb5_context context;
char *default_realm, *realm, *cp;
krb5_error_code retval;
int l;
- char local_host[MAXHOSTNAMELEN+1];
+ char local_host[MAX_DNS_NAMELEN+1];
if (host)
- strncpy(local_host, host, MAXHOSTNAMELEN);
+ strncpy(local_host, host, MAX_DNS_NAMELEN);
else {
if (gethostname(local_host, sizeof(local_host)-1) == -1)
return SOCKET_ERRNO;
}
- local_host[sizeof(local_host)-1] = '\0';
+ local_host[MAX_DNS_NAMELEN] = '\0';
for (cp = local_host; *cp; cp++) {
if (isupper(*cp))
*cp = tolower(*cp);
}
}
+#ifdef KRB5_DNS_LOOKUP
if (realm == (char *)NULL) {
- if (default_realm != (char *)NULL) {
- /* We are defaulting to the realm of the host */
- if (!(cp = (char *)malloc(strlen(default_realm)+1)))
- return ENOMEM;
- strcpy(cp, default_realm);
- realm = cp;
-
- /* Assume the realm name is upper case */
- for (cp = realm; *cp; cp++)
- if (islower(*cp))
- *cp = toupper(*cp);
- } else {
- /* We are defaulting to the local realm */
- retval = krb5_get_default_realm(context, &realm);
- if (retval) {
- return retval;
- }
- }
+ /*
+ * Since this didn't appear in our config file, try looking
+ * it up via DNS. Look for a TXT records of the form:
+ *
+ * _kerberos.<hostname>
+ * _kerberos.<searchlist>
+ * _kerberos.<defaultrealm>
+ *
+ */
+ cp = local_host;
+ do {
+ retval = krb5_try_realm_txt_rr("_kerberos", cp, &realm);
+ cp = strchr(cp,'.');
+ if (cp)
+ cp++;
+ } while (retval && cp && cp[0]);
+ if (retval)
+ retval = krb5_try_realm_txt_rr("_kerberos", "", &realm);
+ if (retval && default_realm) {
+ cp = default_realm;
+ do {
+ retval = krb5_try_realm_txt_rr("_kerberos", cp, &realm);
+ cp = strchr(cp,'.');
+ if (cp)
+ cp++;
+ } while (retval && cp && cp[0]);
+ }
+ }
+#endif /* KRB5_DNS_LOOKUP */
+ if (realm == (char *)NULL) {
+ if (default_realm != (char *)NULL) {
+ /* We are defaulting to the realm of the host */
+ if (!(cp = (char *)malloc(strlen(default_realm)+1)))
+ return ENOMEM;
+ strcpy(cp, default_realm);
+ realm = cp;
+
+ /* Assume the realm name is upper case */
+ for (cp = realm; *cp; cp++)
+ if (islower(*cp))
+ *cp = toupper(*cp);
+ } else {
+ /* We are defaulting to the local realm */
+ retval = krb5_get_default_realm(context, &realm);
+ if (retval) {
+ return retval;
+ }
+ }
}
if (!(retrealms = (char **)calloc(2, sizeof(*retrealms)))) {
if (realm != (char *)NULL)
#define NEED_SOCKETS
#include "k5-int.h"
#include <stdio.h>
+#ifdef KRB5_DNS_LOOKUP
+#ifdef WSHELPER
+#include <wshelper.h>
+#else /* WSHELPER */
+#include <arpa/inet.h>
+#include <arpa/nameser.h>
+#include <resolv.h>
+#include <netdb.h>
+#endif /* WSHELPER */
+#ifndef T_SRV
+#define T_SRV 33
+#endif /* T_SRV */
+
+/* for old Unixes and friends ... */
+#ifndef MAXHOSTNAMELEN
+#define MAXHOSTNAMELEN 64
+#endif
+
+#define MAX_DNS_NAMELEN (15*(MAXHOSTNAMELEN + 1)+1)
+#endif /* KRB5_DNS_LOOKUP */
/*
* returns count of number of addresses found
* the master kdc
*/
-krb5_error_code
-krb5_locate_kdc(context, realm, addr_pp, naddrs, master_index, nmasters)
+static krb5_error_code
+krb5_locate_srv_conf(context, realm, name, addr_pp, naddrs, master_index, nmasters)
krb5_context context;
const krb5_data *realm;
+ const char * name;
struct sockaddr **addr_pp;
int *naddrs;
int *master_index;
int *nmasters;
{
- const char *realm_kdc_names[4];
+ const char *realm_srv_names[4];
char **masterlist, **hostlist, *host, *port, *cp;
krb5_error_code code;
int i, j, out, count, ismaster;
masterlist = NULL;
- realm_kdc_names[0] = "realms";
- realm_kdc_names[1] = host;
- realm_kdc_names[2] = "kdc";
- realm_kdc_names[3] = 0;
+ realm_srv_names[0] = "realms";
+ realm_srv_names[1] = host;
+ realm_srv_names[2] = name;
+ realm_srv_names[3] = 0;
- code = profile_get_values(context->profile, realm_kdc_names, &hostlist);
+ code = profile_get_values(context->profile, realm_srv_names, &hostlist);
if (code) {
- if (code == PROF_NO_SECTION || code == PROF_NO_RELATION)
- code = KRB5_REALM_UNKNOWN;
- krb5_xfree(host);
- return code;
- }
+ if (code == PROF_NO_SECTION || code == PROF_NO_RELATION)
+ code = KRB5_REALM_UNKNOWN;
+ krb5_xfree(host);
+ return code;
+ }
#ifdef HAVE_NETINET_IN_H
if ((sp = getservbyname(KDC_PORTNAME, "udp")))
*master_index = 0;
*nmasters = 0;
- realm_kdc_names[0] = "realms";
- realm_kdc_names[1] = host;
- realm_kdc_names[2] = "admin_server";
- realm_kdc_names[3] = 0;
+ realm_srv_names[0] = "realms";
+ realm_srv_names[1] = host;
+ realm_srv_names[2] = "admin_server";
+ realm_srv_names[3] = 0;
- code = profile_get_values(context->profile, realm_kdc_names,
+ code = profile_get_values(context->profile, realm_srv_names,
&masterlist);
krb5_xfree(host);
*naddrs = out;
return 0;
}
+
+#ifdef KRB5_DNS_LOOKUP
+
+/*
+ * Lookup a KDC via DNS SRV records
+ */
+
+static krb5_error_code
+krb5_locate_srv_dns(realm, service, protocol, addr_pp, naddrs)
+ const krb5_data *realm;
+ const char *service;
+ const char *protocol;
+ struct sockaddr **addr_pp;
+ int *naddrs;
+{
+ krb5_error_code code;
+ int out, j, count;
+ union {
+ unsigned char bytes[2048];
+ HEADER hdr;
+ } answer;
+ unsigned char *p=NULL;
+ char host[MAX_DNS_NAMELEN];
+ struct sockaddr *addr = NULL;
+ struct sockaddr_in *sin = NULL;
+ struct hostent *hp = NULL;
+ int type, class;
+ int status, priority, weight, size, len, numanswers, numqueries, rdlen;
+ unsigned short port;
+ const int hdrsize = sizeof(HEADER);
+ struct srv_dns_entry {
+ struct srv_dns_entry *next;
+ int priority;
+ int weight;
+ unsigned short port;
+ char *host;
+ };
+
+ struct srv_dns_entry *head = NULL;
+ struct srv_dns_entry *srv = NULL, *entry = NULL;
+
+ out = 0;
+ addr = (struct sockaddr *) malloc(sizeof(struct sockaddr));
+ if (addr == NULL)
+ return ENOMEM;
+
+ count = 1;
+
+ /*
+ * First off, build a query of the form:
+ *
+ * service.protocol.realm
+ *
+ * which will most likely be something like:
+ *
+ * _kerberos._udp.REALM
+ *
+ */
+
+ if ( strlen(service) + strlen(protocol) + realm->length + 5
+ > MAX_DNS_NAMELEN )
+ goto out;
+ sprintf(host, "%s.%s.%.*s", service, protocol, realm->length,
+ realm->data);
+
+ size = res_search(host, C_IN, T_SRV, answer.bytes, sizeof(answer.bytes));
+
+ if (size < hdrsize)
+ goto out;
+
+ /*
+ * We got an answer! First off, parse the header and figure out how
+ * many answers we got back.
+ */
+
+ p = answer.bytes;
+
+ numqueries = ntohs(answer.hdr.qdcount);
+ numanswers = ntohs(answer.hdr.ancount);
+
+ p += sizeof(HEADER);
+
+ /*
+ * We need to skip over all of the questions, so we have to iterate
+ * over every query record. dn_expand() is able to tell us the size
+ * of compress DNS names, so we use it.
+ */
+
+#define INCR_CHECK(x,y) x += y; if (x > size + answer.bytes) goto out
+#define CHECK(x,y) if (x + y > size + answer.bytes) goto out
+#define NTOHSP(x,y) x[0] << 8 | x[1]; x += y
+
+ while (numqueries--) {
+ len = dn_expand(answer.bytes, answer.bytes + size, p, host, sizeof(host));
+ if (len < 0)
+ goto out;
+ INCR_CHECK(p, len + 4);
+ }
+
+ /*
+ * We're now pointing at the answer records. Only process them if
+ * they're actually T_SRV records (they might be CNAME records,
+ * for instance).
+ *
+ * But in a DNS reply, if you get a CNAME you always get the associated
+ * "real" RR for that CNAME. RFC 1034, 3.6.2:
+ *
+ * CNAME RRs cause special action in DNS software. When a name server
+ * fails to find a desired RR in the resource set associated with the
+ * domain name, it checks to see if the resource set consists of a CNAME
+ * record with a matching class. If so, the name server includes the CNAME
+ * record in the response and restarts the query at the domain name
+ * specified in the data field of the CNAME record. The one exception to
+ * this rule is that queries which match the CNAME type are not restarted.
+ *
+ * In other words, CNAMEs do not need to be expanded by the client.
+ */
+
+ while (numanswers--) {
+
+ /* First is the name; use dn_expand to get the compressed size */
+ len = dn_expand(answer.bytes, answer.bytes + size, p, host, sizeof(host));
+ if (len < 0)
+ goto out;
+ INCR_CHECK(p, len);
+
+ /* Next is the query type */
+ CHECK(p, 2);
+ type = NTOHSP(p,2);
+
+ /* Next is the query class; also skip over 4 byte TTL */
+ CHECK(p, 6);
+ class = NTOHSP(p,6);
+
+ /* Record data length */
+
+ CHECK(p,2);
+ rdlen = NTOHSP(p,2);
+
+ /*
+ * If this is an SRV record, process it. Record format is:
+ *
+ * Priority
+ * Weight
+ * Port
+ * Server name
+ */
+
+ if (class == C_IN && type == T_SRV) {
+ CHECK(p,2);
+ priority = NTOHSP(p,2);
+ CHECK(p, 2);
+ weight = NTOHSP(p,2);
+ CHECK(p, 2);
+ port = NTOHSP(p,2);
+ len = dn_expand(answer.bytes, answer.bytes + size, p, host, sizeof(host));
+ if (len < 0)
+ goto out;
+ INCR_CHECK(p, len);
+
+ /*
+ * We got everything! Insert it into our list, but make sure
+ * it's in the right order. Right now we don't do anything
+ * with the weight field
+ */
+
+ srv = (struct srv_dns_entry *) malloc(sizeof(struct srv_dns_entry));
+ if (srv == NULL)
+ goto out;
+
+ srv->priority = priority;
+ srv->weight = weight;
+ srv->port = port;
+ srv->host = strdup(host);
+
+ if (head == NULL || head->priority > srv->priority) {
+ srv->next = head;
+ head = srv;
+ } else
+ /*
+ * This is confusing. Only insert an entry into this
+ * spot if:
+ * The next person has a higher priority (lower priorities
+ * are preferred).
+ * Or
+ * There is no next entry (we're at the end)
+ */
+ for (entry = head; entry != NULL; entry = entry->next)
+ if ((entry->next &&
+ entry->next->priority > srv->priority) ||
+ entry->next == NULL) {
+ srv->next = entry->next;
+ entry->next = srv;
+ break;
+ }
+ } else
+ INCR_CHECK(p, rdlen);
+ }
+
+ /*
+ * Okay! Now we've got a linked list of entries sorted by
+ * priority. Start looking up A records and returning
+ * addresses.
+ */
+
+ if (head == NULL)
+ goto out;
+
+ for (entry = head; entry != NULL; entry = entry->next) {
+ hp = gethostbyname(entry->host);
+ if (hp != 0) {
+ switch (hp->h_addrtype) {
+#ifdef HAVE_NETINET_IN_H
+ case AF_INET:
+ for (j=0; hp->h_addr_list[j]; j++) {
+ sin = (struct sockaddr_in *) &addr[out++];
+ memset ((char *) sin, 0, sizeof (struct sockaddr));
+ sin->sin_family = hp->h_addrtype;
+ sin->sin_port = htons(entry->port);
+ memcpy((char *) &sin->sin_addr,
+ (char *) hp->h_addr_list[j],
+ sizeof(struct in_addr));
+ if (out + 1 >= count) {
+ count += 5;
+ addr = (struct sockaddr *)
+ realloc((char *) addr,
+ sizeof(struct sockaddr) * count);
+ if (!addr)
+ goto out;
+ }
+ }
+ break;
+#endif /* HAVE_NETINET_IN_H */
+ default:
+ break;
+ }
+ }
+ }
+
+ for (entry = head; entry != NULL; ) {
+ free(entry->host);
+ entry->host = NULL;
+ srv = entry;
+ entry = entry->next;
+ free(srv);
+ srv = NULL;
+ }
+
+ out:
+ if (srv)
+ free(srv);
+
+ if (out == 0) { /* No good servers */
+ if (addr)
+ free(addr);
+ return KRB5_REALM_CANT_RESOLVE;
+ }
+
+ *addr_pp = addr;
+ *naddrs = out;
+ return 0;
+}
+#endif /* KRB5_DNS_LOOKUP */
+
+/*
+ * Wrapper function for the two backends
+ */
+
+krb5_error_code
+krb5_locate_kdc(context, realm, addr_pp, naddrs, master_index, nmasters)
+ krb5_context context;
+ const krb5_data *realm;
+ struct sockaddr **addr_pp;
+ int *naddrs;
+ int *master_index;
+ int *nmasters;
+{
+ krb5_error_code code;
+#ifdef KRB5_DNS_LOOKUP
+ struct sockaddr *admin_addr_p, *kdc_addr_p;
+ int nadmin_addrs, nkdc_addrs;
+ int i,j;
+#endif /* KRB5_DNS_LOOKUP */
+
+ /*
+ * We always try the local file first
+ */
+
+ code = krb5_locate_srv_conf(context, realm, "kdc", addr_pp, naddrs,
+ master_index, nmasters);
+
+#ifdef KRB5_DNS_LOOKUP
+ if (code) {
+ code = krb5_locate_srv_dns(realm, "_kerberos", "_udp",
+ addr_pp, naddrs);
+ if ( master_index && nmasters ) {
+
+ code = krb5_locate_srv_dns(realm, "_kadmin", "_tcp",
+ &admin_addr_p, &nadmin_addrs);
+ if ( code ) {
+ free(*addr_pp);
+ *addr_pp = NULL;
+ *naddrs = 0;
+ return(code);
+ }
+
+ kdc_addr_p = *addr_pp;
+ nkdc_addrs = *naddrs;
+
+ *naddrs = 0;
+ *addr_pp = (struct sockaddr *) malloc(sizeof(*kdc_addr_p));
+ if ( *addr_pp == NULL ) {
+ free(kdc_addr_p);
+ free(admin_addr_p);
+ return ENOMEM;
+ }
+
+ for ( i=0; i<nkdc_addrs; i++ ) {
+ for ( j=0 ; j<nadmin_addrs; j++) {
+ if ( !memcmp(&kdc_addr_p[i].sa_data[2],&admin_addr_p[j].sa_data[2],4) ) {
+ memcpy(&(*addr_pp)[(*naddrs)],&kdc_addr_p[i],
+ sizeof(struct sockaddr));
+ (*naddrs)++;
+ break;
+ }
+ }
+ }
+
+ free(kdc_addr_p);
+ free(admin_addr_p);
+
+ if ( *naddrs == 0 ) {
+ free(*addr_pp);
+ *addr_pp = NULL;
+ return KRB5_REALM_CANT_RESOLVE;
+ }
+ *master_index = 1;
+ *nmasters = *naddrs;
+ }
+ }
+#endif /* KRB5_DNS_LOOKUP */
+ return (code);
+}
+
+#if 0 /* Why is this useful? It's not used now, and it's certainly
+ not useful if you don't have the DNS code enabled. -KR */
+
+/*
+ * It turns out that it is really useful to be able to use these functions
+ * for other things (like admin servers), so create an abstract function
+ * for this
+ */
+
+krb5_error_code
+krb5_locate_server(realm, name, proto, addr_pp, naddrs)
+ const krb5_data *realm;
+ const char *name, *proto;
+ struct sockaddr **addr_pp;
+ int *naddrs;
+{
+ krb5_error_code code = KRB5_REALM_UNKNOWN;
+#ifdef KRB5_DNS_LOOKUP
+ code = krb5_locate_srv_dns(realm, name, proto,
+ (struct sockaddr **) addr_pp, naddrs);
+#endif /* KRB5_DNS_LOOKUP */
+ return (code);
+}
+#endif