| /* |
| 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. |
| */ |
| int |
| dns_rdata_to_name (const char * rdata, int rdlen, char * name, 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 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; |
| } |
| } |
| |
| |
| int |
| dns_rdata_to_name (const char * rdata, int rdlen, char * name, int name_len) |
| { |
| int i = 0; |
| // Index into 'name' |
| const 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) |
| { |
| 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; |
| } |