summaryrefslogtreecommitdiffstats
path: root/mDNSResponder/mDNSPosix/nss_mdns.c
diff options
context:
space:
mode:
Diffstat (limited to 'mDNSResponder/mDNSPosix/nss_mdns.c')
-rwxr-xr-xmDNSResponder/mDNSPosix/nss_mdns.c2723
1 files changed, 2723 insertions, 0 deletions
diff --git a/mDNSResponder/mDNSPosix/nss_mdns.c b/mDNSResponder/mDNSPosix/nss_mdns.c
new file mode 100755
index 00000000..afadb3c6
--- /dev/null
+++ b/mDNSResponder/mDNSPosix/nss_mdns.c
@@ -0,0 +1,2723 @@
+/*
+ NICTA Public Software Licence
+ Version 1.0
+
+ Copyright © 2004 National ICT Australia Ltd
+
+ All rights reserved.
+
+ By this licence, National ICT Australia Ltd (NICTA) grants permission,
+ free of charge, to any person who obtains a copy of this software
+ and any associated documentation files ("the Software") to use and
+ deal with the Software in source code and binary forms without
+ restriction, with or without modification, and to permit persons
+ to whom the Software is furnished to do so, provided that the
+ following conditions are met:
+
+ - Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimers.
+ - Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimers in
+ the documentation and/or other materials provided with the
+ distribution.
+ - The name of NICTA may not be used to endorse or promote products
+ derived from this Software without specific prior written permission.
+
+ EXCEPT AS EXPRESSLY STATED IN THIS LICENCE AND TO THE FULL EXTENT
+ PERMITTED BY APPLICABLE LAW, THE SOFTWARE IS PROVIDED "AS-IS" AND
+ NICTA MAKES NO REPRESENTATIONS, WARRANTIES OR CONDITIONS OF ANY
+ KIND, EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY
+ REPRESENTATIONS, WARRANTIES OR CONDITIONS REGARDING THE CONTENTS
+ OR ACCURACY OF THE SOFTWARE, OR OF TITLE, MERCHANTABILITY, FITNESS
+ FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, THE ABSENCE OF LATENT
+ OR OTHER DEFECTS, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR
+ NOT DISCOVERABLE.
+
+ TO THE FULL EXTENT PERMITTED BY APPLICABLE LAW, IN NO EVENT WILL
+ NICTA BE LIABLE ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
+ NEGLIGENCE) FOR ANY LOSS OR DAMAGE WHATSOEVER, INCLUDING (WITHOUT
+ LIMITATION) LOSS OF PRODUCTION OR OPERATION TIME, LOSS, DAMAGE OR
+ CORRUPTION OF DATA OR RECORDS; OR LOSS OF ANTICIPATED SAVINGS,
+ OPPORTUNITY, REVENUE, PROFIT OR GOODWILL, OR OTHER ECONOMIC LOSS;
+ OR ANY SPECIAL, INCIDENTAL, INDIRECT, CONSEQUENTIAL, PUNITIVE OR
+ EXEMPLARY DAMAGES ARISING OUT OF OR IN CONNECTION WITH THIS LICENCE,
+ THE SOFTWARE OR THE USE OF THE SOFTWARE, EVEN IF NICTA HAS BEEN
+ ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+ If applicable legislation implies warranties or conditions, or
+ imposes obligations or liability on NICTA in respect of the Software
+ that cannot be wholly or partly excluded, restricted or modified,
+ NICTA's liability is limited, to the full extent permitted by the
+ applicable legislation, at its option, to:
+
+ a. in the case of goods, any one or more of the following:
+ i. the replacement of the goods or the supply of equivalent goods;
+ ii. the repair of the goods;
+ iii. the payment of the cost of replacing the goods or of acquiring
+ equivalent goods;
+ iv. the payment of the cost of having the goods repaired; or
+ b. in the case of services:
+ i. the supplying of the services again; or
+ ii. the payment of the cost of having the services supplied
+ again.
+ */
+
+/*
+ NSSwitch Implementation of mDNS interface.
+
+ Andrew White (Andrew.White@nicta.com.au)
+ May 2004
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <syslog.h>
+#include <pthread.h>
+#include <ctype.h>
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+
+#include <arpa/inet.h>
+#define BIND_8_COMPAT 1
+#include <arpa/nameser.h>
+
+#include <dns_sd.h>
+
+
+//----------
+// Public functions
+
+/*
+ Count the number of dots in a name string.
+ */
+int
+count_dots (const char * name);
+
+
+/*
+ Test whether a domain name is local.
+
+ Returns
+ 1 if name ends with ".local" or ".local."
+ 0 otherwise
+ */
+int
+islocal (const char * name);
+
+
+/*
+ Format an address structure as a string appropriate for DNS reverse (PTR)
+ lookup, based on address type.
+
+ Parameters
+ prefixlen
+ Prefix length, in bits. When formatting, this will be rounded up
+ to the nearest appropriate size. If -1, assume maximum.
+ buf
+ Output buffer. Must be long enough to hold largest possible
+ output.
+ Returns
+ Pointer to (first character of) output buffer,
+ or NULL on error.
+ */
+char *
+format_reverse_addr (int af, const void * addr, int prefixlen, char * buf);
+
+
+/*
+ Format an address structure as a string appropriate for DNS reverse (PTR)
+ lookup for AF_INET. Output is in .in-addr.arpa domain.
+
+ Parameters
+ prefixlen
+ Prefix length, in bits. When formatting, this will be rounded up
+ to the nearest byte (8). If -1, assume 32.
+ buf
+ Output buffer. Must be long enough to hold largest possible
+ output. For AF_INET, this is 29 characters (including null).
+ Returns
+ Pointer to (first character of) output buffer,
+ or NULL on error.
+ */
+char *
+format_reverse_addr_in (
+ const struct in_addr * addr,
+ int prefixlen,
+ char * buf
+ );
+#define DNS_PTR_AF_INET_SIZE 29
+
+/*
+ Format an address structure as a string appropriate for DNS reverse (PTR)
+ lookup for AF_INET6. Output is in .ip6.arpa domain.
+
+ Parameters
+ prefixlen
+ Prefix length, in bits. When formatting, this will be rounded up
+ to the nearest nibble (4). If -1, assume 128.
+ buf
+ Output buffer. Must be long enough to hold largest possible
+ output. For AF_INET6, this is 72 characters (including null).
+ Returns
+ Pointer to (first character of) output buffer,
+ or NULL on error.
+ */
+char *
+format_reverse_addr_in6 (
+ const struct in6_addr * addr,
+ int prefixlen,
+ char * buf
+ );
+#define DNS_PTR_AF_INET6_SIZE 72
+
+
+/*
+ Compare whether the given dns name has the given domain suffix.
+ A single leading '.' on the name or leading or trailing '.' on the
+ domain is ignored for the purposes of the comparison.
+ Multiple leading or trailing '.'s are an error. Other DNS syntax
+ errors are not checked for. The comparison is case insensitive.
+
+ Returns
+ 1 on success (match)
+ 0 on failure (no match)
+ < 0 on error
+ */
+int
+cmp_dns_suffix (const char * name, const char * domain);
+enum
+{
+ CMP_DNS_SUFFIX_SUCCESS = 1,
+ CMP_DNS_SUFFIX_FAILURE = 0,
+ CMP_DNS_SUFFIX_BAD_NAME = 1,
+ CMP_DNS_SUFFIX_BAD_DOMAIN = -2
+};
+
+typedef int ns_type_t;
+typedef int ns_class_t;
+
+/*
+ Convert a DNS resource record (RR) code to an address family (AF) code.
+
+ Parameters
+ rrtype
+ resource record type (from nameser.h)
+
+ Returns
+ Appropriate AF code (from socket.h), or AF_UNSPEC if an appropriate
+ mapping couldn't be determined
+ */
+int
+rr_to_af (ns_type_t rrtype);
+
+
+/*
+ Convert an address family (AF) code to a DNS resource record (RR) code.
+
+ Parameters
+ int
+ address family code (from socket.h)
+ Returns
+ Appropriate RR code (from nameser.h), or ns_t_invalid if an appropriate
+ mapping couldn't be determined
+ */
+ns_type_t
+af_to_rr (int af);
+
+
+/*
+ Convert a string to an address family (case insensitive).
+
+ Returns
+ Matching AF code, or AF_UNSPEC if no match found.
+ */
+int
+str_to_af (const char * str);
+
+
+/*
+ Convert a string to an ns_class_t (case insensitive).
+
+ Returns
+ Matching ns_class_t, or ns_c_invalid if no match found.
+ */
+ns_class_t
+str_to_ns_class (const char * str);
+
+
+/*
+ Convert a string to an ns_type_t (case insensitive).
+
+ Returns
+ Matching ns_type_t, or ns_t_invalid if no match found.
+ */
+ns_type_t
+str_to_ns_type (const char * str);
+
+
+/*
+ Convert an address family code to a string.
+
+ Returns
+ String representation of AF,
+ or NULL if address family unrecognised or invalid.
+ */
+const char *
+af_to_str (int in);
+
+
+/*
+ Convert an ns_class_t code to a string.
+
+ Returns
+ String representation of ns_class_t,
+ or NULL if ns_class_t unrecognised or invalid.
+ */
+const char *
+ns_class_to_str (ns_class_t in);
+
+
+/*
+ Convert an ns_type_t code to a string.
+
+ Returns
+ String representation of ns_type_t,
+ or NULL if ns_type_t unrecognised or invalid.
+ */
+const char *
+ns_type_to_str (ns_type_t in);
+
+
+/*
+ Convert DNS rdata in label format (RFC1034, RFC1035) to a name.
+
+ On error, partial data is written to name (as much as was successfully
+ processed) and an error code is returned. Errors include a name too
+ long for the buffer and a pointer in the label (which cannot be
+ resolved).
+
+ Parameters
+ rdata
+ Rdata formatted as series of labels.
+ rdlen
+ Length of rdata buffer.
+ name
+ Buffer to store fully qualified result in.
+ By RFC1034 section 3.1, a 255 character buffer (256 characters
+ including null) is long enough for any legal name.
+ name_len
+ Number of characters available in name buffer, not including
+ trailing null.
+
+ Returns
+ Length of name buffer (not including trailing null).
+ < 0 on error.
+ A return of 0 implies the empty domain.
+ */
+static int
+dns_rdata_to_name (const unsigned char * rdata, int rdlen, char * name, unsigned int name_len);
+enum
+{
+ DNS_RDATA_TO_NAME_BAD_FORMAT = -1,
+ // Format is broken. Usually because we ran out of data
+ // (according to rdata) before the labels said we should.
+ DNS_RDATA_TO_NAME_TOO_LONG = -2,
+ // The converted rdata is longer than the name buffer.
+ DNS_RDATA_TO_NAME_PTR = -3,
+ // The rdata contains a pointer.
+};
+
+#define DNS_LABEL_MAXLEN 63
+// Maximum length of a single DNS label
+#define DNS_NAME_MAXLEN 256
+// Maximum length of a DNS name
+
+//----------
+// Public types
+
+typedef int errcode_t;
+// Used for 0 = success, non-zero = error code functions
+
+
+//----------
+// Public functions
+
+/*
+ Test whether a domain name is in a domain covered by nss_mdns.
+ The name is assumed to be fully qualified (trailing dot optional);
+ unqualified names will be processed but may return unusual results
+ if the unqualified prefix happens to match a domain suffix.
+
+ Returns
+ 1 success
+ 0 failure
+ -1 error, check errno
+ */
+int
+config_is_mdns_suffix (const char * name);
+
+
+/*
+ Loads all relevant data from configuration file. Other code should
+ rarely need to call this function, since all other public configuration
+ functions do so implicitly. Once loaded, configuration info doesn't
+ change.
+
+ Returns
+ 0 configuration ready
+ non-zero configuration error code
+ */
+errcode_t
+init_config ();
+
+#define ENTNAME hostent
+#define DATABASE "hosts"
+
+#include <nss.h>
+// For nss_status
+#include <netdb.h>
+// For hostent
+#include <sys/types.h>
+// For size_t
+
+typedef enum nss_status nss_status;
+typedef struct hostent hostent;
+
+/*
+ gethostbyname implementation
+
+ name:
+ name to look up
+ result_buf:
+ resulting entry
+ buf:
+ auxillary buffer
+ buflen:
+ length of auxillary buffer
+ errnop:
+ pointer to errno
+ h_errnop:
+ pointer to h_errno
+ */
+nss_status
+_nss_mdns_gethostbyname_r (
+ const char *name,
+ hostent * result_buf,
+ char *buf,
+ size_t buflen,
+ int *errnop,
+ int *h_errnop
+ );
+
+
+/*
+ gethostbyname2 implementation
+
+ name:
+ name to look up
+ af:
+ address family
+ result_buf:
+ resulting entry
+ buf:
+ auxillary buffer
+ buflen:
+ length of auxillary buffer
+ errnop:
+ pointer to errno
+ h_errnop:
+ pointer to h_errno
+ */
+nss_status
+_nss_mdns_gethostbyname2_r (
+ const char *name,
+ int af,
+ hostent * result_buf,
+ char *buf,
+ size_t buflen,
+ int *errnop,
+ int *h_errnop
+ );
+
+
+/*
+ gethostbyaddr implementation
+
+ addr:
+ address structure to look up
+ len:
+ length of address structure
+ af:
+ address family
+ result_buf:
+ resulting entry
+ buf:
+ auxillary buffer
+ buflen:
+ length of auxillary buffer
+ errnop:
+ pointer to errno
+ h_errnop:
+ pointer to h_errno
+ */
+nss_status
+_nss_mdns_gethostbyaddr_r (
+ const void *addr,
+ socklen_t len,
+ int af,
+ hostent * result_buf,
+ char *buf,
+ size_t buflen,
+ int *errnop,
+ int *h_errnop
+ );
+
+
+//----------
+// Types and Constants
+
+const int MDNS_VERBOSE = 0;
+// This enables verbose syslog messages
+// If zero, only "imporant" messages will appear in syslog
+
+#define k_hostname_maxlen 256
+// As per RFC1034 and RFC1035
+#define k_aliases_max 15
+#define k_addrs_max 15
+
+typedef struct buf_header
+{
+ char hostname [k_hostname_maxlen + 1];
+ char * aliases [k_aliases_max + 1];
+ char * addrs [k_addrs_max + 1];
+} buf_header_t;
+
+typedef struct result_map
+{
+ int done;
+ nss_status status;
+ hostent * hostent;
+ buf_header_t * header;
+ int aliases_count;
+ int addrs_count;
+ char * buffer;
+ int addr_idx;
+ // Index for addresses - grow from low end
+ // Index points to first empty space
+ int alias_idx;
+ // Index for aliases - grow from high end
+ // Index points to lowest entry
+ int r_errno;
+ int r_h_errno;
+} result_map_t;
+
+static const struct timeval
+k_select_time = { 0, 500000 };
+// 0 seconds, 500 milliseconds
+
+//----------
+// Local prototypes
+
+static nss_status
+mdns_gethostbyname2 (
+ const char *name,
+ int af,
+ hostent * result_buf,
+ char *buf,
+ size_t buflen,
+ int *errnop,
+ int *h_errnop
+ );
+
+
+/*
+ Lookup name using mDNS server
+ */
+static nss_status
+mdns_lookup_name (
+ const char * fullname,
+ int af,
+ result_map_t * result
+ );
+
+/*
+ Lookup address using mDNS server
+ */
+static nss_status
+mdns_lookup_addr (
+ const void * addr,
+ socklen_t len,
+ int af,
+ const char * addr_str,
+ result_map_t * result
+ );
+
+
+/*
+ Handle incoming MDNS events
+ */
+static nss_status
+handle_events (DNSServiceRef sdref, result_map_t * result, const char * str);
+
+
+// Callback for mdns_lookup operations
+//DNSServiceQueryRecordReply mdns_lookup_callback;
+typedef void
+mdns_lookup_callback_t
+(
+ DNSServiceRef sdref,
+ DNSServiceFlags flags,
+ uint32_t interface_index,
+ DNSServiceErrorType error_code,
+ const char *fullname,
+ uint16_t rrtype,
+ uint16_t rrclass,
+ uint16_t rdlen,
+ const void *rdata,
+ uint32_t ttl,
+ void *context
+);
+
+mdns_lookup_callback_t mdns_lookup_callback;
+
+
+static int
+init_result (
+ result_map_t * result,
+ hostent * result_buf,
+ char * buf,
+ size_t buflen
+ );
+
+static int
+callback_body_ptr (
+ const char * fullname,
+ result_map_t * result,
+ int rdlen,
+ const void * rdata
+ );
+
+static void *
+add_address_to_buffer (result_map_t * result, const void * data, int len);
+static char *
+add_alias_to_buffer (result_map_t * result, const char * data, int len);
+static char *
+add_hostname_len (result_map_t * result, const char * fullname, int len);
+static char *
+add_hostname_or_alias (result_map_t * result, const char * data, int len);
+
+static void *
+contains_address (result_map_t * result, const void * data, int len);
+static char *
+contains_alias (result_map_t * result, const char * data);
+
+
+static const char *
+is_applicable_name (
+ result_map_t * result,
+ const char * name,
+ char * lookup_name
+ );
+
+static const char *
+is_applicable_addr (
+ result_map_t * result,
+ const void * addr,
+ int af,
+ char * addr_str
+ );
+
+
+// Error code functions
+
+static nss_status
+set_err (result_map_t * result, nss_status status, int err, int herr);
+
+static nss_status set_err_notfound (result_map_t * result);
+static nss_status set_err_bad_hostname (result_map_t * result);
+static nss_status set_err_buf_too_small (result_map_t * result);
+static nss_status set_err_internal_resource_full (result_map_t * result);
+static nss_status set_err_system (result_map_t * result);
+static nss_status set_err_mdns_failed (result_map_t * result);
+static nss_status set_err_success (result_map_t * result);
+
+
+//----------
+// Global variables
+
+
+//----------
+// NSS functions
+
+nss_status
+_nss_mdns_gethostbyname_r (
+ const char *name,
+ hostent * result_buf,
+ char *buf,
+ size_t buflen,
+ int *errnop,
+ int *h_errnop
+ )
+{
+ if (MDNS_VERBOSE)
+ syslog (LOG_DEBUG,
+ "mdns: Called nss_mdns_gethostbyname with %s",
+ name
+ );
+
+ return
+ mdns_gethostbyname2 (
+ name, AF_INET, result_buf, buf, buflen, errnop, h_errnop
+ );
+}
+
+
+nss_status
+_nss_mdns_gethostbyname2_r (
+ const char *name,
+ int af,
+ hostent * result_buf,
+ char *buf,
+ size_t buflen,
+ int *errnop,
+ int *h_errnop
+ )
+{
+ if (MDNS_VERBOSE)
+ syslog (LOG_DEBUG,
+ "mdns: Called nss_mdns_gethostbyname2 with %s",
+ name
+ );
+
+ return
+ mdns_gethostbyname2 (
+ name, af, result_buf, buf, buflen, errnop, h_errnop
+ );
+}
+
+
+nss_status
+_nss_mdns_gethostbyaddr_r (
+ const void *addr,
+ socklen_t len,
+ int af,
+ hostent * result_buf,
+ char *buf,
+ size_t buflen,
+ int *errnop,
+ int *h_errnop
+ )
+{
+ char addr_str [NI_MAXHOST + 1];
+ result_map_t result;
+ int err_status;
+
+ if (inet_ntop (af, addr, addr_str, NI_MAXHOST) == NULL)
+ {
+ const char * family = af_to_str (af);
+ if (family == NULL)
+ {
+ family = "Unknown";
+ }
+
+ syslog (LOG_WARNING,
+ "mdns: Couldn't covert address, family %d (%s) in nss_mdns_gethostbyaddr: %s",
+ af,
+ family,
+ strerror (errno)
+ );
+
+ // This address family never applicable to us, so return NOT_FOUND
+
+ *errnop = ENOENT;
+ *h_errnop = HOST_NOT_FOUND;
+ return NSS_STATUS_NOTFOUND;
+ }
+ if (MDNS_VERBOSE)
+ {
+ syslog (LOG_DEBUG,
+ "mdns: Called nss_mdns_gethostbyaddr with %s",
+ addr_str
+ );
+ }
+
+ // Initialise result
+ err_status = init_result (&result, result_buf, buf, buflen);
+ if (err_status)
+ {
+ *errnop = err_status;
+ *h_errnop = NETDB_INTERNAL;
+ return NSS_STATUS_TRYAGAIN;
+ }
+
+ if (is_applicable_addr (&result, addr, af, addr_str))
+ {
+ nss_status rv;
+
+ rv = mdns_lookup_addr (addr, len, af, addr_str, &result);
+ if (rv == NSS_STATUS_SUCCESS)
+ {
+ return rv;
+ }
+ }
+
+ // Return current error status (defaults to NOT_FOUND)
+
+ *errnop = result.r_errno;
+ *h_errnop = result.r_h_errno;
+ return result.status;
+}
+
+
+//----------
+// Local functions
+
+nss_status
+mdns_gethostbyname2 (
+ const char *name,
+ int af,
+ hostent * result_buf,
+ char *buf,
+ size_t buflen,
+ int *errnop,
+ int *h_errnop
+ )
+{
+ char lookup_name [k_hostname_maxlen + 1];
+ result_map_t result;
+ int err_status;
+
+ // Initialise result
+ err_status = init_result (&result, result_buf, buf, buflen);
+ if (err_status)
+ {
+ *errnop = err_status;
+ *h_errnop = NETDB_INTERNAL;
+ return NSS_STATUS_TRYAGAIN;
+ }
+
+ if (is_applicable_name (&result, name, lookup_name))
+ {
+ // Try using mdns
+ nss_status rv;
+
+ if (MDNS_VERBOSE)
+ syslog (LOG_DEBUG,
+ "mdns: Local name: %s",
+ name
+ );
+
+ rv = mdns_lookup_name (name, af, &result);
+ if (rv == NSS_STATUS_SUCCESS)
+ {
+ return rv;
+ }
+ }
+
+ // Return current error status (defaults to NOT_FOUND)
+
+ *errnop = result.r_errno;
+ *h_errnop = result.r_h_errno;
+ return result.status;
+}
+
+
+/*
+ Lookup a fully qualified hostname using the default record type
+ for the specified address family.
+
+ Parameters
+ fullname
+ Fully qualified hostname. If not fully qualified the code will
+ still 'work', but the lookup is unlikely to succeed.
+ af
+ Either AF_INET or AF_INET6. Other families are not supported.
+ result
+ Initialised 'result' data structure.
+ */
+static nss_status
+mdns_lookup_name (
+ const char * fullname,
+ int af,
+ result_map_t * result
+ )
+{
+ // Lookup using mDNS.
+ DNSServiceErrorType errcode;
+ DNSServiceRef sdref;
+ ns_type_t rrtype;
+ nss_status status;
+
+ if (MDNS_VERBOSE)
+ syslog (LOG_DEBUG,
+ "mdns: Attempting lookup of %s",
+ fullname
+ );
+
+ switch (af)
+ {
+ case AF_INET:
+ rrtype = kDNSServiceType_A;
+ result->hostent->h_length = 4;
+ // Length of an A record
+ break;
+
+ case AF_INET6:
+ rrtype = kDNSServiceType_AAAA;
+ result->hostent->h_length = 16;
+ // Length of an AAAA record
+ break;
+
+ default:
+ syslog (LOG_WARNING,
+ "mdns: Unsupported address family %d",
+ af
+ );
+ return set_err_bad_hostname (result);
+ }
+ result->hostent->h_addrtype = af;
+
+ errcode =
+ DNSServiceQueryRecord (
+ &sdref,
+ kDNSServiceFlagsForceMulticast, // force multicast query
+ kDNSServiceInterfaceIndexAny, // all interfaces
+ fullname, // full name to query for
+ rrtype, // resource record type
+ kDNSServiceClass_IN, // internet class records
+ mdns_lookup_callback, // callback
+ result // Context - result buffer
+ );
+
+ if (errcode)
+ {
+ syslog (LOG_WARNING,
+ "mdns: Failed to initialise lookup, error %d",
+ errcode
+ );
+ return set_err_mdns_failed (result);
+ }
+
+ status = handle_events (sdref, result, fullname);
+ DNSServiceRefDeallocate (sdref);
+ return status;
+}
+
+
+/*
+ Reverse (PTR) lookup for the specified address.
+
+ Parameters
+ addr
+ Either a struct in_addr or a struct in6_addr
+ addr_len
+ size of the address
+ af
+ Either AF_INET or AF_INET6. Other families are not supported.
+ Must match addr
+ addr_str
+ Address in format suitable for PTR lookup.
+ AF_INET: a.b.c.d -> d.c.b.a.in-addr.arpa
+ AF_INET6: reverse nibble format, x.x.x...x.ip6.arpa
+ result
+ Initialised 'result' data structure.
+ */
+static nss_status
+mdns_lookup_addr (
+ const void * addr,
+ socklen_t addr_len,
+ int af,
+ const char * addr_str,
+ result_map_t * result
+ )
+{
+ DNSServiceErrorType errcode;
+ DNSServiceRef sdref;
+ nss_status status;
+
+ if (MDNS_VERBOSE)
+ syslog (LOG_DEBUG,
+ "mdns: Attempting lookup of %s",
+ addr_str
+ );
+
+ result->hostent->h_addrtype = af;
+ result->hostent->h_length = addr_len;
+
+ // Query address becomes "address" in result.
+ if (!add_address_to_buffer (result, addr, addr_len))
+ {
+ return result->status;
+ }
+
+ result->hostent->h_name [0] = 0;
+
+ errcode =
+ DNSServiceQueryRecord (
+ &sdref,
+ kDNSServiceFlagsForceMulticast, // force multicast query
+ kDNSServiceInterfaceIndexAny, // all interfaces
+ addr_str, // address string to query for
+ kDNSServiceType_PTR, // pointer RRs
+ kDNSServiceClass_IN, // internet class records
+ mdns_lookup_callback, // callback
+ result // Context - result buffer
+ );
+
+ if (errcode)
+ {
+ syslog (LOG_WARNING,
+ "mdns: Failed to initialise mdns lookup, error %d",
+ errcode
+ );
+ return set_err_mdns_failed (result);
+ }
+
+ status = handle_events (sdref, result, addr_str);
+ DNSServiceRefDeallocate (sdref);
+ return status;
+}
+
+
+/*
+ Wait on result of callback, and process it when it arrives.
+
+ Parameters
+ sdref
+ dns-sd reference
+ result
+ Initialised 'result' data structure.
+ str
+ lookup string, used for status/error reporting.
+ */
+static nss_status
+handle_events (DNSServiceRef sdref, result_map_t * result, const char * str)
+{
+ int dns_sd_fd = DNSServiceRefSockFD(sdref);
+ int nfds = dns_sd_fd + 1;
+ fd_set readfds;
+ struct timeval tv;
+ int select_result;
+
+ while (!result->done)
+ {
+ FD_ZERO(&readfds);
+ FD_SET(dns_sd_fd, &readfds);
+
+ tv = k_select_time;
+
+ select_result =
+ select (nfds, &readfds, (fd_set*)NULL, (fd_set*)NULL, &tv);
+ if (select_result > 0)
+ {
+ if (FD_ISSET(dns_sd_fd, &readfds))
+ {
+ if (MDNS_VERBOSE)
+ syslog (LOG_DEBUG,
+ "mdns: Reply received for %s",
+ str
+ );
+ DNSServiceProcessResult(sdref);
+ }
+ else
+ {
+ syslog (LOG_WARNING,
+ "mdns: Unexpected return from select on lookup of %s",
+ str
+ );
+ }
+ }
+ else
+ {
+ // Terminate loop due to timer expiry
+ if (MDNS_VERBOSE)
+ syslog (LOG_DEBUG,
+ "mdns: %s not found - timer expired",
+ str
+ );
+ set_err_notfound (result);
+ break;
+ }
+ }
+
+ return result->status;
+}
+
+
+/*
+ Examine incoming data and add to relevant fields in result structure.
+ This routine is called from DNSServiceProcessResult where appropriate.
+ */
+void
+mdns_lookup_callback
+(
+ DNSServiceRef sdref,
+ DNSServiceFlags flags,
+ uint32_t interface_index,
+ DNSServiceErrorType error_code,
+ const char *fullname,
+ uint16_t rrtype,
+ uint16_t rrclass,
+ uint16_t rdlen,
+ const void *rdata,
+ uint32_t ttl,
+ void *context
+)
+{
+ // A single record is received
+
+ result_map_t * result = (result_map_t *) context;
+
+ (void)sdref; // Unused
+ (void)interface_index; // Unused
+ (void)ttl; // Unused
+
+ if (!(flags & kDNSServiceFlagsMoreComing) )
+ {
+ result->done = 1;
+ }
+
+ if (error_code == kDNSServiceErr_NoError)
+ {
+ ns_type_t expected_rr_type =
+ af_to_rr (result->hostent->h_addrtype);
+
+ // Idiot check class
+ if (rrclass != C_IN)
+ {
+ syslog (LOG_WARNING,
+ "mdns: Received bad RR class: expected %d (%s),"
+ " got %d (%s), RR type %d (%s)",
+ C_IN,
+ ns_class_to_str (C_IN),
+ rrclass,
+ ns_class_to_str (rrclass),
+ rrtype,
+ ns_type_to_str (rrtype)
+ );
+ return;
+ }
+
+ // If a PTR
+ if (rrtype == kDNSServiceType_PTR)
+ {
+ if (callback_body_ptr (fullname, result, rdlen, rdata) < 0)
+ return;
+ }
+ else if (rrtype == expected_rr_type)
+ {
+ if (!
+ add_hostname_or_alias (
+ result,
+ fullname,
+ strlen (fullname)
+ )
+ )
+ {
+ result->done = 1;
+ return;
+ // Abort on error
+ }
+
+ if (!add_address_to_buffer (result, rdata, rdlen) )
+ {
+ result->done = 1;
+ return;
+ // Abort on error
+ }
+ }
+ else
+ {
+ syslog (LOG_WARNING,
+ "mdns: Received bad RR type: expected %d (%s),"
+ " got %d (%s)",
+ expected_rr_type,
+ ns_type_to_str (expected_rr_type),
+ rrtype,
+ ns_type_to_str (rrtype)
+ );
+ return;
+ }
+
+ if (result->status != NSS_STATUS_SUCCESS)
+ set_err_success (result);
+ }
+ else
+ {
+ // For now, dump message to syslog and continue
+ syslog (LOG_WARNING,
+ "mdns: callback returned error %d",
+ error_code
+ );
+ }
+}
+
+static int
+callback_body_ptr (
+ const char * fullname,
+ result_map_t * result,
+ int rdlen,
+ const void * rdata
+ )
+{
+ char result_name [k_hostname_maxlen + 1];
+ int rv;
+
+ // Fullname should be .in-addr.arpa or equivalent, which we're
+ // not interested in. Ignore it.
+
+ rv = dns_rdata_to_name (rdata, rdlen, result_name, k_hostname_maxlen);
+ if (rv < 0)
+ {
+ const char * errmsg;
+
+ switch (rv)
+ {
+ case DNS_RDATA_TO_NAME_BAD_FORMAT:
+ errmsg = "mdns: PTR '%s' result badly formatted ('%s...')";
+ break;
+
+ case DNS_RDATA_TO_NAME_TOO_LONG:
+ errmsg = "mdns: PTR '%s' result too long ('%s...')";
+ break;
+
+ case DNS_RDATA_TO_NAME_PTR:
+ errmsg = "mdns: PTR '%s' result contained pointer ('%s...')";
+ break;
+
+ default:
+ errmsg = "mdns: PTR '%s' result conversion failed ('%s...')";
+ }
+
+ syslog (LOG_WARNING,
+ errmsg,
+ fullname,
+ result_name
+ );
+
+ return -1;
+ }
+
+ if (MDNS_VERBOSE)
+ {
+ syslog (LOG_DEBUG,
+ "mdns: PTR '%s' resolved to '%s'",
+ fullname,
+ result_name
+ );
+ }
+
+ // Data should be a hostname
+ if (!
+ add_hostname_or_alias (
+ result,
+ result_name,
+ rv
+ )
+ )
+ {
+ result->done = 1;
+ return -1;
+ }
+
+ return 0;
+}
+
+
+/*
+ Add an address to the buffer.
+
+ Parameter
+ result
+ Result structure to write to
+ data
+ Incoming address data buffer
+ Must be 'int' aligned
+ len
+ Length of data buffer (in bytes)
+ Must match data alignment
+
+ Result
+ Pointer to start of newly written data,
+ or NULL on error.
+ If address already exists in buffer, returns pointer to that instead.
+ */
+static void *
+add_address_to_buffer (result_map_t * result, const void * data, int len)
+{
+ int new_addr;
+ void * start;
+ void * temp;
+
+ if ((temp = contains_address (result, data, len)))
+ {
+ return temp;
+ }
+
+ if (result->addrs_count >= k_addrs_max)
+ {
+ // Not enough addr slots
+ set_err_internal_resource_full (result);
+ syslog (LOG_ERR,
+ "mdns: Internal address buffer full; increase size"
+ );
+ return NULL;
+ }
+
+ // Idiot check
+ if (len != result->hostent->h_length)
+ {
+ syslog (LOG_WARNING,
+ "mdns: Unexpected rdata length for address. Expected %d, got %d",
+ result->hostent->h_length,
+ len
+ );
+ // XXX And continue for now.
+ }
+
+ new_addr = result->addr_idx + len;
+
+ if (new_addr > result->alias_idx)
+ {
+ // Not enough room
+ set_err_buf_too_small (result);
+ if (MDNS_VERBOSE)
+ syslog (LOG_DEBUG,
+ "mdns: Ran out of buffer when adding address %d",
+ result->addrs_count + 1
+ );
+ return NULL;
+ }
+
+ start = result->buffer + result->addr_idx;
+ memcpy (start, data, len);
+ result->addr_idx = new_addr;
+ result->header->addrs [result->addrs_count] = start;
+ result->addrs_count++;
+ result->header->addrs [result->addrs_count] = NULL;
+
+ return start;
+}
+
+
+static void *
+contains_address (result_map_t * result, const void * data, int len)
+{
+ int i;
+
+ // Idiot check
+ if (len != result->hostent->h_length)
+ {
+ syslog (LOG_WARNING,
+ "mdns: Unexpected rdata length for address. Expected %d, got %d",
+ result->hostent->h_length,
+ len
+ );
+ // XXX And continue for now.
+ }
+
+ for (i = 0; result->header->addrs [i]; i++)
+ {
+ if (memcmp (result->header->addrs [i], data, len) == 0)
+ {
+ return result->header->addrs [i];
+ }
+ }
+
+ return NULL;
+}
+
+
+/*
+ Add an alias to the buffer.
+
+ Parameter
+ result
+ Result structure to write to
+ data
+ Incoming alias (null terminated)
+ len
+ Length of data buffer (in bytes), including trailing null
+
+ Result
+ Pointer to start of newly written data,
+ or NULL on error
+ If alias already exists in buffer, returns pointer to that instead.
+ */
+static char *
+add_alias_to_buffer (result_map_t * result, const char * data, int len)
+{
+ int new_alias;
+ char * start;
+ char * temp;
+
+ if ((temp = contains_alias (result, data)))
+ {
+ return temp;
+ }
+
+ if (result->aliases_count >= k_aliases_max)
+ {
+ // Not enough alias slots
+ set_err_internal_resource_full (result);
+ syslog (LOG_ERR,
+ "mdns: Internal alias buffer full; increase size"
+ );
+ return NULL;
+ }
+
+ new_alias = result->alias_idx - len;
+
+ if (new_alias < result->addr_idx)
+ {
+ // Not enough room
+ set_err_buf_too_small (result);
+ if (MDNS_VERBOSE)
+ syslog (LOG_DEBUG,
+ "mdns: Ran out of buffer when adding alias %d",
+ result->aliases_count + 1
+ );
+ return NULL;
+ }
+
+ start = result->buffer + new_alias;
+ memcpy (start, data, len);
+ result->alias_idx = new_alias;
+ result->header->aliases [result->aliases_count] = start;
+ result->aliases_count++;
+ result->header->aliases [result->aliases_count] = NULL;
+
+ return start;
+}
+
+
+static char *
+contains_alias (result_map_t * result, const char * alias)
+{
+ int i;
+
+ for (i = 0; result->header->aliases [i]; i++)
+ {
+ if (strcmp (result->header->aliases [i], alias) == 0)
+ {
+ return result->header->aliases [i];
+ }
+ }
+
+ return NULL;
+}
+
+
+/*
+ Add fully qualified hostname to result.
+
+ Parameter
+ result
+ Result structure to write to
+ fullname
+ Fully qualified hostname
+
+ Result
+ Pointer to start of hostname buffer,
+ or NULL on error (usually hostname too long)
+ */
+
+static char *
+add_hostname_len (result_map_t * result, const char * fullname, int len)
+{
+ if (len >= k_hostname_maxlen)
+ {
+ set_err_bad_hostname (result);
+ syslog (LOG_WARNING,
+ "mdns: Hostname too long '%.*s': len %d, max %d",
+ len,
+ fullname,
+ len,
+ k_hostname_maxlen
+ );
+ return NULL;
+ }
+
+ result->hostent->h_name =
+ strcpy (result->header->hostname, fullname);
+
+ return result->header->hostname;
+}
+
+
+/*
+ Add fully qualified name as hostname or alias.
+
+ If hostname is not fully qualified this is not an error, but the data
+ returned may be not what the application wanted.
+
+ Parameter
+ result
+ Result structure to write to
+ data
+ Incoming alias (null terminated)
+ len
+ Length of data buffer (in bytes), including trailing null
+
+ Result
+ Pointer to start of newly written data,
+ or NULL on error
+ If alias or hostname already exists, returns pointer to that instead.
+ */
+static char *
+add_hostname_or_alias (result_map_t * result, const char * data, int len)
+{
+ char * hostname = result->hostent->h_name;
+
+ if (*hostname)
+ {
+ if (strcmp (hostname, data) == 0)
+ {
+ return hostname;
+ }
+ else
+ {
+ return add_alias_to_buffer (result, data, len);
+ }
+ }
+ else
+ {
+ return add_hostname_len (result, data, len);
+ }
+}
+
+
+static int
+init_result (
+ result_map_t * result,
+ hostent * result_buf,
+ char * buf,
+ size_t buflen
+ )
+{
+ if (buflen < sizeof (buf_header_t))
+ {
+ return ERANGE;
+ }
+
+ result->hostent = result_buf;
+ result->header = (buf_header_t *) buf;
+ result->header->hostname[0] = 0;
+ result->aliases_count = 0;
+ result->header->aliases[0] = NULL;
+ result->addrs_count = 0;
+ result->header->addrs[0] = NULL;
+ result->buffer = buf + sizeof (buf_header_t);
+ result->addr_idx = 0;
+ result->alias_idx = buflen - sizeof (buf_header_t);
+ result->done = 0;
+ set_err_notfound (result);
+
+ // Point hostent to the right buffers
+ result->hostent->h_name = result->header->hostname;
+ result->hostent->h_aliases = result->header->aliases;
+ result->hostent->h_addr_list = result->header->addrs;
+
+ return 0;
+}
+
+/*
+ Set the status in the result.
+
+ Parameters
+ result
+ Result structure to update
+ status
+ New nss_status value
+ err
+ New errno value
+ herr
+ New h_errno value
+
+ Returns
+ New status value
+ */
+static nss_status
+set_err (result_map_t * result, nss_status status, int err, int herr)
+{
+ result->status = status;
+ result->r_errno = err;
+ result->r_h_errno = herr;
+
+ return status;
+}
+
+static nss_status
+set_err_notfound (result_map_t * result)
+{
+ return set_err (result, NSS_STATUS_NOTFOUND, ENOENT, HOST_NOT_FOUND);
+}
+
+static nss_status
+set_err_bad_hostname (result_map_t * result)
+{
+ return set_err (result, NSS_STATUS_TRYAGAIN, ENOENT, NO_RECOVERY);
+}
+
+static nss_status
+set_err_buf_too_small (result_map_t * result)
+{
+ return set_err (result, NSS_STATUS_TRYAGAIN, ERANGE, NETDB_INTERNAL);
+}
+
+static nss_status
+set_err_internal_resource_full (result_map_t * result)
+{
+ return set_err (result, NSS_STATUS_RETURN, ERANGE, NO_RECOVERY);
+}
+
+static nss_status
+set_err_system (result_map_t * result)
+{
+ return set_err (result, NSS_STATUS_UNAVAIL, errno, NETDB_INTERNAL);
+}
+
+static nss_status
+set_err_mdns_failed (result_map_t * result)
+{
+ return set_err (result, NSS_STATUS_TRYAGAIN, EAGAIN, TRY_AGAIN);
+}
+
+static nss_status
+set_err_success (result_map_t * result)
+{
+ result->status = NSS_STATUS_SUCCESS;
+ return result->status;
+}
+
+
+/*
+ Test whether name is applicable for mdns to process, and if so copy into
+ lookup_name buffer (if non-NULL).
+
+ Returns
+ Pointer to name to lookup up, if applicable, or NULL otherwise.
+ */
+static const char *
+is_applicable_name (
+ result_map_t * result,
+ const char * name,
+ char * lookup_name
+ )
+{
+ int match = config_is_mdns_suffix (name);
+ if (match > 0)
+ {
+ if (lookup_name)
+ {
+ strncpy (lookup_name, name, k_hostname_maxlen + 1);
+ return lookup_name;
+ }
+ else
+ {
+ return name;
+ }
+ }
+ else
+ {
+ if (match < 0)
+ {
+ set_err_system (result);
+ }
+ return NULL;
+ }
+}
+
+/*
+ Test whether address is applicable for mdns to process, and if so copy into
+ addr_str buffer as an address suitable for ptr lookup.
+
+ Returns
+ Pointer to name to lookup up, if applicable, or NULL otherwise.
+ */
+static const char *
+is_applicable_addr (
+ result_map_t * result,
+ const void * addr,
+ int af,
+ char * addr_str
+ )
+{
+ int match;
+
+ if (!format_reverse_addr (af, addr, -1, addr_str))
+ {
+ if (MDNS_VERBOSE)
+ syslog (LOG_DEBUG,
+ "mdns: Failed to create reverse address"
+ );
+ return NULL;
+ }
+
+ if (MDNS_VERBOSE)
+ syslog (LOG_DEBUG,
+ "mdns: Reverse address: %s",
+ addr_str
+ );
+
+ match = config_is_mdns_suffix (addr_str);
+ if (match > 0)
+ {
+ return addr_str;
+ }
+ else
+ {
+ if (match < 0)
+ {
+ set_err_system (result);
+ }
+ return NULL;
+ }
+}
+
+//----------
+// Types and Constants
+
+const char * k_conf_file = "/etc/nss_mdns.conf";
+#define CONF_LINE_SIZE 1024
+
+const char k_comment_char = '#';
+
+const char * k_keyword_domain = "domain";
+
+const char * k_default_domains [] =
+{
+ "local",
+ "254.169.in-addr.arpa",
+ "8.e.f.ip6.int",
+ "8.e.f.ip6.arpa",
+ "9.e.f.ip6.int",
+ "9.e.f.ip6.arpa",
+ "a.e.f.ip6.int",
+ "a.e.f.ip6.arpa",
+ "b.e.f.ip6.int",
+ "b.e.f.ip6.arpa",
+ NULL
+ // Always null terminated
+};
+
+// Linked list of domains
+typedef struct domain_entry
+{
+ char * domain;
+ struct domain_entry * next;
+} domain_entry_t;
+
+
+// Config
+typedef struct
+{
+ domain_entry_t * domains;
+} config_t;
+
+const config_t k_empty_config =
+{
+ NULL
+};
+
+
+// Context - tracks position in config file, used for error reporting
+typedef struct
+{
+ const char * filename;
+ int linenum;
+} config_file_context_t;
+
+
+//----------
+// Local prototypes
+
+static errcode_t
+load_config (config_t * conf);
+
+static errcode_t
+process_config_line (
+ config_t * conf,
+ char * line,
+ config_file_context_t * context
+ );
+
+static char *
+get_next_word (char * input, char **next);
+
+static errcode_t
+default_config (config_t * conf);
+
+static errcode_t
+add_domain (config_t * conf, const char * domain);
+
+static int
+contains_domain (const config_t * conf, const char * domain);
+
+static int
+contains_domain_suffix (const config_t * conf, const char * addr);
+
+
+//----------
+// Global variables
+
+static config_t * g_config = NULL;
+// Configuration info
+
+pthread_mutex_t g_config_mutex =
+#ifdef PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP
+ PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;
+#else
+ PTHREAD_MUTEX_INITIALIZER;
+#endif
+
+
+//----------
+// Configuration functions
+
+
+/*
+ Initialise the configuration from the config file.
+
+ Returns
+ 0 success
+ non-zero error code on failure
+ */
+errcode_t
+init_config ()
+{
+ if (g_config)
+ {
+ /*
+ Safe to test outside mutex.
+ If non-zero, initialisation is complete and g_config can be
+ safely used read-only. If zero, then we do proper mutex
+ testing before initialisation.
+ */
+ return 0;
+ }
+ else
+ {
+ int errcode = -1;
+ int presult;
+ config_t * temp_config;
+
+ // Acquire mutex
+ presult = pthread_mutex_lock (&g_config_mutex);
+ if (presult)
+ {
+ syslog (LOG_ERR,
+ "mdns: Fatal mutex lock error in nss_mdns:init_config, %s:%d: %d: %s",
+ __FILE__, __LINE__, presult, strerror (presult)
+ );
+ return presult;
+ }
+
+ // Test again now we have mutex, in case initialisation occurred while
+ // we were waiting
+ if (!g_config)
+ {
+ temp_config = (config_t *) malloc (sizeof (config_t));
+ if (temp_config)
+ {
+ // Note: This code will leak memory if initialisation fails
+ // repeatedly. This should only happen in the case of a memory
+ // error, so I'm not sure if it's a meaningful problem. - AW
+ *temp_config = k_empty_config;
+ errcode = load_config (temp_config);
+
+ if (!errcode)
+ {
+ g_config = temp_config;
+ }
+ }
+ else
+ {
+ syslog (LOG_ERR,
+ "mdns: Can't allocate memory in nss_mdns:init_config, %s:%d",
+ __FILE__, __LINE__
+ );
+ errcode = errno;
+ }
+ }
+
+ presult = pthread_mutex_unlock (&g_config_mutex);
+ if (presult)
+ {
+ syslog (LOG_ERR,
+ "mdns: Fatal mutex unlock error in nss_mdns:init_config, %s:%d: %d: %s",
+ __FILE__, __LINE__, presult, strerror (presult)
+ );
+ errcode = presult;
+ }
+
+ return errcode;
+ }
+}
+
+
+int
+config_is_mdns_suffix (const char * name)
+{
+ int errcode = init_config ();
+ if (!errcode)
+ {
+ return contains_domain_suffix (g_config, name);
+ }
+ else
+ {
+ errno = errcode;
+ return -1;
+ }
+}
+
+
+//----------
+// Local functions
+
+static errcode_t
+load_config (config_t * conf)
+{
+ FILE * cf;
+ char line [CONF_LINE_SIZE];
+ config_file_context_t context;
+
+ context.filename = k_conf_file;
+ context.linenum = 0;
+
+
+ cf = fopen (context.filename, "r");
+ if (!cf)
+ {
+ syslog (LOG_INFO,
+ "mdns: Couldn't open nss_mdns configuration file %s, using default.",
+ context.filename
+ );
+ return default_config (conf);
+ }
+
+ while (fgets (line, CONF_LINE_SIZE, cf))
+ {
+ int errcode;
+ context.linenum++;
+ errcode = process_config_line (conf, line, &context);
+ if (errcode)
+ {
+ // Critical error, give up
+ fclose(cf);
+ return errcode;
+ }
+ }
+
+ fclose (cf);
+
+ return 0;
+}
+
+
+/*
+ Parse a line of the configuration file.
+ For each keyword recognised, perform appropriate handling.
+ If the keyword is not recognised, print a message to syslog
+ and continue.
+
+ Returns
+ 0 success, or recoverable config file error
+ non-zero serious system error, processing aborted
+ */
+static errcode_t
+process_config_line (
+ config_t * conf,
+ char * line,
+ config_file_context_t * context
+ )
+{
+ char * curr = line;
+ char * word;
+
+ word = get_next_word (curr, &curr);
+ if (!word || word [0] == k_comment_char)
+ {
+ // Nothing interesting on this line
+ return 0;
+ }
+
+ if (strcmp (word, k_keyword_domain) == 0)
+ {
+ word = get_next_word (curr, &curr);
+ if (word)
+ {
+ int errcode = add_domain (conf, word);
+ if (errcode)
+ {
+ // something badly wrong, bail
+ return errcode;
+ }
+
+ if (get_next_word (curr, NULL))
+ {
+ syslog (LOG_WARNING,
+ "%s, line %d: ignored extra text found after domain",
+ context->filename,
+ context->linenum
+ );
+ }
+ }
+ else
+ {
+ syslog (LOG_WARNING,
+ "%s, line %d: no domain specified",
+ context->filename,
+ context->linenum
+ );
+ }
+ }
+ else
+ {
+ syslog (LOG_WARNING,
+ "%s, line %d: unknown keyword %s - skipping",
+ context->filename,
+ context->linenum,
+ word
+ );
+ }
+
+ return 0;
+}
+
+
+/*
+ Get next word (whitespace separated) from input string.
+ A null character is written into the first whitespace character following
+ the word.
+
+ Parameters
+ input
+ Input string. This string is modified by get_next_word.
+ next
+ If non-NULL and the result is non-NULL, a pointer to the
+ character following the end of the word (after the null)
+ is written to 'next'.
+ If no word is found, the original value is unchanged.
+ If the word extended to the end of the string, 'next' points
+ to the trailling NULL.
+ It is safe to pass 'str' as 'input' and '&str' as 'next'.
+ Returns
+ Pointer to the first non-whitespace character (and thus word) found.
+ if no word is found, returns NULL.
+ */
+static char *
+get_next_word (char * input, char **next)
+{
+ char * curr = input;
+ char * result;
+
+ while (isspace (*curr))
+ {
+ curr++;
+ }
+
+ if (*curr == 0)
+ {
+ return NULL;
+ }
+
+ result = curr;
+ while (*curr && !isspace (*curr))
+ {
+ curr++;
+ }
+ if (*curr)
+ {
+ *curr = 0;
+ if (next)
+ {
+ *next = curr+1;
+ }
+ }
+ else
+ {
+ if (next)
+ {
+ *next = curr;
+ }
+ }
+
+ return result;
+}
+
+
+static errcode_t
+default_config (config_t * conf)
+{
+ int i;
+ for (i = 0; k_default_domains [i]; i++)
+ {
+ int errcode =
+ add_domain (conf, k_default_domains [i]);
+ if (errcode)
+ {
+ // Something has gone (badly) wrong - let's bail
+ return errcode;
+ }
+ }
+
+ return 0;
+}
+
+
+static errcode_t
+add_domain (config_t * conf, const char * domain)
+{
+ if (!contains_domain (conf, domain))
+ {
+ domain_entry_t * d =
+ (domain_entry_t *) malloc (sizeof (domain_entry_t));
+ if (!d)
+ {
+ syslog (LOG_ERR,
+ "mdns: Can't allocate memory in nss_mdns:init_config, %s:%d",
+ __FILE__, __LINE__
+ );
+ return ENOMEM;
+ }
+
+ d->domain = strdup (domain);
+ if (!d->domain)
+ {
+ syslog (LOG_ERR,
+ "mdns: Can't allocate memory in nss_mdns:init_config, %s:%d",
+ __FILE__, __LINE__
+ );
+ free (d);
+ return ENOMEM;
+ }
+ d->next = conf->domains;
+ conf->domains = d;
+ }
+
+ return 0;
+}
+
+
+static int
+contains_domain (const config_t * conf, const char * domain)
+{
+ const domain_entry_t * curr = conf->domains;
+
+ while (curr != NULL)
+ {
+ if (strcasecmp (curr->domain, domain) == 0)
+ {
+ return 1;
+ }
+
+ curr = curr->next;
+ }
+
+ return 0;
+}
+
+
+static int
+contains_domain_suffix (const config_t * conf, const char * addr)
+{
+ const domain_entry_t * curr = conf->domains;
+
+ while (curr != NULL)
+ {
+ if (cmp_dns_suffix (addr, curr->domain) > 0)
+ {
+ return 1;
+ }
+
+ curr = curr->next;
+ }
+
+ return 0;
+}
+
+//----------
+// Types and Constants
+
+static const char * k_local_suffix = "local";
+static const char k_dns_separator = '.';
+
+static const unsigned int k_label_maxlen = DNS_LABEL_MAXLEN;
+// Label entries longer than this are actually pointers.
+
+typedef struct
+{
+ int value;
+ const char * name;
+ const char * comment;
+} table_entry_t;
+
+static const table_entry_t k_table_af [] =
+{
+ { AF_UNSPEC, NULL, NULL },
+ { AF_LOCAL, "LOCAL", NULL },
+ { AF_UNIX, "UNIX", NULL },
+ { AF_INET, "INET", NULL },
+ { AF_INET6, "INET6", NULL }
+};
+static const int k_table_af_size =
+ sizeof (k_table_af) / sizeof (*k_table_af);
+
+static const char * k_table_ns_class [] =
+{
+ NULL,
+ "IN"
+};
+static const int k_table_ns_class_size =
+ sizeof (k_table_ns_class) / sizeof (*k_table_ns_class);
+
+static const char * k_table_ns_type [] =
+{
+ NULL,
+ "A",
+ "NS",
+ "MD",
+ "MF",
+ "CNAME",
+ "SOA",
+ "MB",
+ "MG",
+ "MR",
+ "NULL",
+ "WKS",
+ "PTR",
+ "HINFO",
+ "MINFO",
+ "MX",
+ "TXT",
+ "RP",
+ "AFSDB",
+ "X25",
+ "ISDN",
+ "RT",
+ "NSAP",
+ NULL,
+ "SIG",
+ "KEY",
+ "PX",
+ "GPOS",
+ "AAAA",
+ "LOC",
+ "NXT",
+ "EID",
+ "NIMLOC",
+ "SRV",
+ "ATMA",
+ "NAPTR",
+ "KX",
+ "CERT",
+ "A6",
+ "DNAME",
+ "SINK",
+ "OPT"
+};
+static const int k_table_ns_type_size =
+ sizeof (k_table_ns_type) / sizeof (*k_table_ns_type);
+
+
+//----------
+// Local prototypes
+
+static int
+simple_table_index (const char * table [], int size, const char * str);
+
+static int
+table_index_name (const table_entry_t table [], int size, const char * str);
+
+static int
+table_index_value (const table_entry_t table [], int size, int n);
+
+
+//----------
+// Global variables
+
+
+//----------
+// Util functions
+
+int
+count_dots (const char * name)
+{
+ int count = 0;
+ int i;
+ for (i = 0; name[i]; i++)
+ {
+ if (name [i] == k_dns_separator)
+ count++;
+ }
+
+ return count;
+}
+
+
+int
+islocal (const char * name)
+{
+ return cmp_dns_suffix (name, k_local_suffix) > 0;
+}
+
+
+int
+rr_to_af (ns_type_t rrtype)
+{
+ switch (rrtype)
+ {
+ case kDNSServiceType_A:
+ return AF_INET;
+
+ case kDNSServiceType_AAAA:
+ return AF_INET6;
+
+ default:
+ return AF_UNSPEC;
+ }
+}
+
+
+ns_type_t
+af_to_rr (int af)
+{
+ switch (af)
+ {
+ case AF_INET:
+ return kDNSServiceType_A;
+
+ case AF_INET6:
+ return kDNSServiceType_AAAA;
+
+ default:
+ //return ns_t_invalid;
+ return 0;
+ }
+}
+
+
+int
+str_to_af (const char * str)
+{
+ int result =
+ table_index_name (k_table_af, k_table_af_size, str);
+ if (result < 0)
+ result = 0;
+
+ return k_table_af [result].value;
+}
+
+
+ns_class_t
+str_to_ns_class (const char * str)
+{
+ return (ns_class_t)
+ simple_table_index (k_table_ns_class, k_table_ns_class_size, str);
+}
+
+
+ns_type_t
+str_to_ns_type (const char * str)
+{
+ return (ns_type_t)
+ simple_table_index (k_table_ns_type, k_table_ns_type_size, str);
+}
+
+
+const char *
+af_to_str (int in)
+{
+ int result =
+ table_index_value (k_table_af, k_table_af_size, in);
+ if (result < 0)
+ result = 0;
+
+ return k_table_af [result].name;
+}
+
+
+const char *
+ns_class_to_str (ns_class_t in)
+{
+ if (in < k_table_ns_class_size)
+ return k_table_ns_class [in];
+ else
+ return NULL;
+}
+
+
+const char *
+ns_type_to_str (ns_type_t in)
+{
+ if (in < k_table_ns_type_size)
+ return k_table_ns_type [in];
+ else
+ return NULL;
+}
+
+
+char *
+format_reverse_addr_in (
+ const struct in_addr * addr,
+ int prefixlen,
+ char * buf
+ )
+{
+ char * curr = buf;
+ int i;
+
+ const uint8_t * in_addr_a = (uint8_t *) addr;
+
+ if (prefixlen > 32)
+ return NULL;
+ if (prefixlen < 0)
+ prefixlen = 32;
+
+ i = (prefixlen + 7) / 8;
+ // divide prefixlen into bytes, rounding up
+
+ while (i > 0)
+ {
+ i--;
+ curr += sprintf (curr, "%d.", in_addr_a [i]);
+ }
+ sprintf (curr, "in-addr.arpa");
+
+ return buf;
+}
+
+
+char *
+format_reverse_addr_in6 (
+ const struct in6_addr * addr,
+ int prefixlen,
+ char * buf
+ )
+{
+ char * curr = buf;
+ int i;
+
+ const uint8_t * in_addr_a = (uint8_t *) addr;
+
+ if (prefixlen > 128)
+ return NULL;
+ if (prefixlen < 0)
+ prefixlen = 128;
+
+ i = (prefixlen + 3) / 4;
+ // divide prefixlen into nibbles, rounding up
+
+ // Special handling for first
+ if (i % 2)
+ {
+ curr += sprintf (curr, "%d.", (in_addr_a [i/2] >> 4) & 0x0F);
+ }
+ i >>= 1;
+ // Convert i to bytes (divide by 2)
+
+ while (i > 0)
+ {
+ uint8_t val;
+
+ i--;
+ val = in_addr_a [i];
+ curr += sprintf (curr, "%x.%x.", val & 0x0F, (val >> 4) & 0x0F);
+ }
+ sprintf (curr, "ip6.arpa");
+
+ return buf;
+}
+
+
+char *
+format_reverse_addr (
+ int af,
+ const void * addr,
+ int prefixlen,
+ char * buf
+ )
+{
+ switch (af)
+ {
+ case AF_INET:
+ return
+ format_reverse_addr_in (
+ (struct in_addr *) addr, prefixlen, buf
+ );
+ break;
+
+ case AF_INET6:
+ return
+ format_reverse_addr_in6 (
+ (struct in6_addr *) addr, prefixlen, buf
+ );
+ break;
+
+ default:
+ return NULL;
+ }
+}
+
+
+int
+cmp_dns_suffix (const char * name, const char * domain)
+{
+ const char * nametail;
+ const char * domaintail;
+
+ // Idiot checks
+ if (*name == 0 || *name == k_dns_separator)
+ {
+ // Name can't be empty or start with separator
+ return CMP_DNS_SUFFIX_BAD_NAME;
+ }
+
+ if (*domain == 0)
+ {
+ return CMP_DNS_SUFFIX_SUCCESS;
+ // trivially true
+ }
+
+ if (*domain == k_dns_separator)
+ {
+ // drop leading separator from domain
+ domain++;
+ if (*domain == k_dns_separator)
+ {
+ return CMP_DNS_SUFFIX_BAD_DOMAIN;
+ }
+ }
+
+ // Find ends of strings
+ for (nametail = name; *nametail; nametail++)
+ ;
+ for (domaintail = domain; *domaintail; domaintail++)
+ ;
+
+ // Shuffle back to last real character, and drop any trailing '.'
+ // while we're at it.
+ nametail--;
+ if (*nametail == k_dns_separator)
+ {
+ nametail--;
+ if (*nametail == k_dns_separator)
+ {
+ return CMP_DNS_SUFFIX_BAD_NAME;
+ }
+ }
+ domaintail--;
+ if (*domaintail == k_dns_separator)
+ {
+ domaintail--;
+ if (*domaintail == k_dns_separator)
+ {
+ return CMP_DNS_SUFFIX_BAD_DOMAIN;
+ }
+ }
+
+ // Compare.
+ while (
+ nametail >= name
+ && domaintail >= domain
+ && tolower(*nametail) == tolower(*domaintail))
+ {
+ nametail--;
+ domaintail--;
+ }
+
+ /* A successful finish will be one of the following:
+ (leading and trailing . ignored)
+
+ name : domain2.domain1
+ domain: domain2.domain1
+ ^
+
+ name : domain3.domain2.domain1
+ domain: domain2.domain1
+ ^
+ */
+ if (
+ domaintail < domain
+ && (nametail < name || *nametail == k_dns_separator)
+ )
+ {
+ return CMP_DNS_SUFFIX_SUCCESS;
+ }
+ else
+ {
+ return CMP_DNS_SUFFIX_FAILURE;
+ }
+}
+
+
+static int
+dns_rdata_to_name (const unsigned char * rdata, int rdlen, char * name, unsigned int name_len)
+{
+ int i = 0;
+ // Index into 'name'
+ const unsigned char * rdata_curr = rdata;
+
+ if (rdlen == 0) return DNS_RDATA_TO_NAME_BAD_FORMAT;
+
+ /*
+ In RDATA, a DNS name is stored as a series of labels.
+ Each label consists of a length octet (max value 63)
+ followed by the data for that label.
+ The series is terminated with a length 0 octet.
+ A length octet beginning with bits 11 is a pointer to
+ somewhere else in the payload, but we don't support these
+ since we don't have access to the entire payload.
+
+ See RFC1034 section 3.1 and RFC1035 section 3.1.
+ */
+ while (1)
+ {
+ unsigned int term_len = *rdata_curr;
+ rdata_curr++;
+
+ if (term_len == 0)
+ {
+ break;
+ // 0 length record terminates label
+ }
+ else if (term_len > k_label_maxlen)
+ {
+ name [i] = 0;
+ return DNS_RDATA_TO_NAME_PTR;
+ }
+ else if (rdata_curr + term_len > rdata + rdlen)
+ {
+ name [i] = 0;
+ return DNS_RDATA_TO_NAME_BAD_FORMAT;
+ }
+
+ if (name_len < i + term_len + 1)
+ // +1 is separator
+ {
+ name [i] = 0;
+ return DNS_RDATA_TO_NAME_TOO_LONG;
+ }
+
+ memcpy (name + i, rdata_curr, term_len);
+
+ i += term_len;
+ rdata_curr += term_len;
+
+ name [i] = k_dns_separator;
+ i++;
+ }
+
+ name [i] = 0;
+ return i;
+}
+
+
+//----------
+// Local functions
+
+/*
+ Find the index of an string entry in a table. A case insenitive match
+ is performed. If no entry is found, 0 is returned.
+
+ Parameters
+ table
+ Lookup table
+ Table entries may be NULL. NULL entries will never match.
+ size
+ number of entries in table
+ str
+ lookup string
+
+ Result
+ index of first matching entry, or 0 if no matches
+ */
+static int
+simple_table_index (const char * table [], int size, const char * str)
+{
+ int i;
+ for (i = 0; i < size; i++)
+ {
+ if (
+ table [i]
+ && (strcasecmp (table [i], str) == 0)
+ )
+ {
+ return i;
+ }
+ }
+
+ return 0;
+}
+
+
+/*
+ Find the index of a name in a table.
+
+ Parameters
+ table
+ array of table_entry_t records. The name field is compared
+ (ignoring case) to the input string.
+ size
+ number of entries in table
+ str
+ lookup string
+
+ Result
+ index of first matching entry, or -1 if no matches
+ */
+static int
+table_index_name (const table_entry_t table [], int size, const char * str)
+{
+ int i;
+ for (i = 0; i < size; i++)
+ {
+ if (
+ table [i].name
+ && (strcasecmp (table [i].name, str) == 0)
+ )
+ {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+
+/*
+ Find the index of a value a table.
+
+ Parameters
+ table
+ array of table_entry_t records. The value field is compared to
+ the input value
+ size
+ number of entries in table
+ n
+ lookup value
+
+ Result
+ index of first matching entry, or -1 if no matches
+ */
+static int
+table_index_value (const table_entry_t table [], int size, int n)
+{
+ int i;
+ for (i = 0; i < size; i++)
+ {
+ if (table [i].value == n)
+ {
+ return i;
+ }
+ }
+
+ return -1;
+}