/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include "mdns_common.h" static int _nss_mdns_queryrecord(const char *rrname, int rrclass, int rrtype, DNSServiceQueryRecordReply callback, struct mdns_querydata *data); static void _nss_mdns_get_svcstatetimestamp(struct timeval *); static void _nss_mdns_loadsmfcfg(mdns_backend_ptr_t); static void _nss_mdns_freesmfcfg(mdns_backend_ptr_t); static boolean_t cmpdmn(char *, char **, int); static char *RDataToName(char *data, char *buffer, int datalen, int buflen); static int searchdomain(mdns_backend_ptr_t, char *, int, char **); static boolean_t validdomain(mdns_backend_ptr_t, char *, int); /* * This file includes the functions to query for host name * information via Multicast DNS (mDNS). The function * _nss_mdns_queryrecord queries for the host information via * Multicast DNS. _nss_mdns_querybyname and _nss_mdns_querybyaddr * query for host IP address and hostname by querying for A/AAAA * and PTR DNS resource records respectively. DNSServiceQueryRecord * in libdns_sd sends a request to the mDNS daemon (mdnsd) to place * the DNS query via multicast and return the results. * mdnsd is managed by SMF (FMRI: svc:/network/dns/multicast:default). * * gethostent.c and gethostent6.c implement the nsswitch 'hosts' * backend module getXbyY functions: getbyname and getbyaddr. * getby* functions in gethostent.c supports only IPv4 and * getby* functions in gethostent6.c returns both IPv4 and * IPv6 results. Functions in gethostent.c and gethostent6.c * call the _nss_mdns_queryby* functions in mdns_common.c to * query for host information via mDNS. * * Configuration for mdns is stored in SMF and is accessed using * the FMRI: svc:/network/dns/multicast:default. Configuration * includes the list of valid DNS domains checked before querying host * information via mDNS and the search list to use for host lookup via * mDNS. The default valid domain list in the mDNS service supports host * lookups for hostnames in the ".local" domain and hostname queries * for link-local IPv4 and IPv6 addresses. _nss_mdns_loadsmfcfg * loads the nss_mdns configuration from SMF and the function * _nss_mdns_updatecfg checks for any updates in nss_mdns configuration. */ static int _nss_mdns_queryrecord(const char *rrname, int rrclass, int rrtype, DNSServiceQueryRecordReply callback, struct mdns_querydata *data) { int sockfd; int flags = kDNSServiceFlagsForceMulticast; /* Multicast only */ int opinterface = kDNSServiceInterfaceIndexAny; DNSServiceErrorType err; DNSServiceRef ref = NULL; int ret; struct fd_set readfds; struct timeval tv; data->status = NSS_NOTFOUND; #ifdef DEBUG syslog(LOG_DEBUG, "nss_mdns: query called rrname:%s rrtype:%d", rrname, rrtype); #endif err = DNSServiceQueryRecord(&ref, flags, opinterface, rrname, rrtype, rrclass, callback, data); if (err != kDNSServiceErr_NoError || ref == NULL || (sockfd = DNSServiceRefSockFD(ref)) == 0) { DNSServiceRefDeallocate(ref); data->status = NSS_UNAVAIL; return (NSS_UNAVAIL); } do { FD_ZERO(&readfds); FD_SET(sockfd, &readfds); tv.tv_sec = NSSMDNS_MAXQRYTMO; tv.tv_usec = 0; /* Wait until response received from mDNS daemon */ ret = select(sockfd + 1, &readfds, NULL, NULL, &tv); if (!((ret > 0) && FD_ISSET(sockfd, &readfds) && (DNSServiceProcessResult(ref) == kDNSServiceErr_NoError))) { data->status = NSS_NOTFOUND; if (errno != EINTR) data->qrydone = B_TRUE; } } while (data->qrydone != B_TRUE); if (data->status == NSS_SUCCESS && (data->withttlbuffer == NULL)) { nss_XbyY_args_t *argp = data->argp; if (argp->buf.result != NULL) { int stat; if (data->buffer == NULL) { data->status = NSS_NOTFOUND; DNSServiceRefDeallocate(ref); return (data->status); } stat = (*argp->str2ent)(data->buffer, strlen(data->buffer), argp->buf.result, argp->buf.buffer, argp->buf.buflen); if (stat == NSS_STR_PARSE_SUCCESS) { argp->returnval = argp->buf.result; argp->returnlen = 1; } else { data->status = NSS_NOTFOUND; if (stat == NSS_STR_PARSE_ERANGE) argp->erange = 1; } free(data->buffer); } else { argp->returnval = argp->buf.buffer; argp->returnlen = strlen(argp->buf.buffer); } data->buffer = NULL; data->buflen = 0; } if (data->status != NSS_SUCCESS) data->argp->h_errno = HOST_NOT_FOUND; DNSServiceRefDeallocate(ref); return (data->status); } static void /* LINTED E_FUNC_ARG_UNUSED */ _nss_mdns_querynamereply(DNSServiceRef sdRef, const DNSServiceFlags flags, /* LINTED E_FUNC_ARG_UNUSED */ uint32_t ifIndex, DNSServiceErrorType errorCode, const char *fullname, uint16_t rrtype, uint16_t rrclass, /* LINTED E_FUNC_ARG_UNUSED */ uint16_t rdlen, const void *rdata, uint32_t ttl, void *context) { struct mdns_querydata *qdata; nss_XbyY_args_t *argp; int firstent = 0; int af; char addrstore[INET6_ADDRSTRLEN]; char *buffer; int len; int remlen; qdata = (struct mdns_querydata *)context; argp = qdata->argp; if (errorCode != kDNSServiceErr_NoError) { qdata->qrydone = B_TRUE; return; } if ((flags & kDNSServiceFlagsMoreComing)) qdata->qrydone = B_FALSE; else qdata->qrydone = B_TRUE; if (!(flags & kDNSServiceFlagsAdd)) return; if (rrclass != kDNSServiceClass_IN) return; if (rrtype == kDNSServiceType_A) af = AF_INET; else if (rrtype == kDNSServiceType_AAAA) af = AF_INET6; else return; if (qdata->buffer == NULL) { if (qdata->withttlbsize > 0) { remlen = qdata->buflen = qdata->withttlbsize; buffer = qdata->buffer = qdata->withttlbuffer; (void) memset(qdata->buffer, 0, remlen); } else { remlen = qdata->buflen = argp->buf.buflen; if (argp->buf.result != NULL) { buffer = qdata->buffer = calloc(1, remlen); } else { /* Return in file format */ (void) memset(argp->buf.buffer, 0, remlen); buffer = qdata->buffer = argp->buf.buffer; } } firstent = 1; } else { buffer = qdata->buffer + strlen(qdata->buffer); remlen = qdata->buflen - strlen(qdata->buffer); } #ifdef DEBUG syslog(LOG_DEBUG, "nss_mdns: querynamereply remlen:%d", remlen); #endif if (inet_ntop(af, rdata, addrstore, INET6_ADDRSTRLEN) != NULL) { if (firstent) len = snprintf(buffer, remlen, "%s %s", addrstore, fullname); else len = snprintf(buffer, remlen, "\n%s %s", addrstore, fullname); if (len >= remlen || len < 0) { qdata->status = NSS_NOTFOUND; qdata->argp->erange = 1; qdata->argp->h_errno = HOST_NOT_FOUND; return; } qdata->ttl = ttl; qdata->status = NSS_SUCCESS; #ifdef DEBUG syslog(LOG_DEBUG, "nss_mdns: querynamereply buffer:%s", buffer); #endif } else { qdata->status = NSS_NOTFOUND; qdata->argp->h_errno = HOST_NOT_FOUND; } } int _nss_mdns_querybyname(mdns_backend_ptr_t be, char *qname, int af, struct mdns_querydata *data) { int rrtype; int rrclass; int srchidx = 0; int rc; char hname[MAXDNAME]; char *name; char *sname; rrclass = kDNSServiceClass_IN; if (af == AF_INET6) rrtype = kDNSServiceType_ANY; else if (af == AF_INET) rrtype = kDNSServiceType_A; else return (NSS_NOTFOUND); name = strdup(qname); if (name == NULL) return (NSS_UNAVAIL); while ((srchidx = searchdomain(be, name, srchidx, &sname)) != -1) { if (sname != NULL) (void) snprintf(hname, sizeof (hname), "%s.%s", name, sname); else (void) strlcpy(hname, name, sizeof (hname)); #ifdef DEBUG syslog(LOG_DEBUG, "nss_mdns: querybyname called" \ " srchidx:%d af:%d hname:%s", srchidx, af, qname); #endif rc = _nss_mdns_queryrecord(hname, rrclass, rrtype, _nss_mdns_querynamereply, data); if ((rc == NSS_UNAVAIL) || (rc == NSS_SUCCESS)) { free(name); return (rc); } } free(name); return (NSS_NOTFOUND); } static void /* LINTED E_FUNC_ARG_UNUSED */ _nss_mdns_queryaddrreply(DNSServiceRef sdRef, const DNSServiceFlags flags, /* LINTED E_FUNC_ARG_UNUSED */ uint32_t ifIndex, DNSServiceErrorType errorCode, /* LINTED E_FUNC_ARG_UNUSED */ const char *fullname, uint16_t rrtype, uint16_t rrclass, uint16_t rdlen, const void *rdata, uint32_t ttl, void *context) { struct mdns_querydata *qdata; nss_XbyY_args_t *argp; char hostname[NI_MAXHOST]; int firstent = 0; char *buffer; int len; int remlen; qdata = (struct mdns_querydata *)context; argp = qdata->argp; if (errorCode != kDNSServiceErr_NoError) { qdata->qrydone = B_TRUE; return; } if ((flags & kDNSServiceFlagsMoreComing)) qdata->qrydone = B_FALSE; else qdata->qrydone = B_TRUE; if (!(flags & kDNSServiceFlagsAdd)) return; if (rrclass != kDNSServiceClass_IN) return; if (rrtype != kDNSServiceType_PTR) return; if (qdata->buffer == NULL) { remlen = qdata->buflen = argp->buf.buflen; if (argp->buf.result != NULL) { buffer = qdata->buffer = calloc(1, remlen); } else { /* Return in file format */ (void) memset(argp->buf.buffer, 0, remlen); buffer = qdata->buffer = argp->buf.buffer; } firstent = 1; } else { buffer = qdata->buffer + strlen(qdata->buffer); remlen = qdata->buflen - strlen(qdata->buffer); } if (RDataToName((char *)rdata, hostname, rdlen, NI_MAXHOST) == NULL) { qdata->status = NSS_NOTFOUND; qdata->argp->h_errno = HOST_NOT_FOUND; return; } #ifdef DEBUG syslog(LOG_DEBUG, "nss_mdns: querynamereply remlen:%d", remlen); #endif if (firstent) len = snprintf(buffer, remlen, "%s %s", qdata->paddrbuf, hostname); else len = snprintf(buffer, remlen, "\n%s %s", qdata->paddrbuf, hostname); if (len >= remlen || len < 0) { qdata->status = NSS_NOTFOUND; qdata->argp->erange = 1; qdata->argp->h_errno = HOST_NOT_FOUND; return; } qdata->status = NSS_SUCCESS; qdata->ttl = ttl; } int /* LINTED E_FUNC_ARG_UNUSED */ _nss_mdns_querybyaddr(mdns_backend_ptr_t be, char *name, int af, struct mdns_querydata *data) { int rrtype; int rrclass; #ifdef DEBUG syslog(LOG_DEBUG, "nss_mdns: querybyaddr called" \ " af:%d addr:%s", af, name); #endif rrclass = kDNSServiceClass_IN; rrtype = kDNSServiceType_PTR; if (validdomain(be, name, 0) == B_FALSE) { data->status = NSS_NOTFOUND; return (NSS_NOTFOUND); } return (_nss_mdns_queryrecord(name, rrclass, rrtype, _nss_mdns_queryaddrreply, data)); } /* * Converts the encoded name in RData returned * by mDNS query to name in file format */ static char * RDataToName(char *data, char *buffer, int datalen, int buflen) { char *src = data; char *srcend = data + datalen; char *ptr = buffer; char *end; char *bend = buffer + buflen - 1; /* terminal '\0' */ int domainlen = 0; while ((src < srcend) && (*src != 0)) { /* first byte is len */ domainlen = *src++; end = src + domainlen; while ((src < end) && (ptr < bend)) { uint8_t ch = *src++; if (ch == '.' || ch == '\\') { *ptr++ = '\\'; } *ptr++ = ch; } /* * Check if we copied entire domain str. and * if space is still remaining for '.' seperator */ if ((src != end) || (ptr == bend)) return (NULL); *ptr++ = '.'; } *ptr = '\0'; return (ptr); } nss_backend_t * _nss_mdns_constr(mdns_backend_op_t ops[], int n_ops) { mdns_backend_ptr_t be; if ((be = (mdns_backend_ptr_t)calloc(1, sizeof (*be))) == NULL) return (NULL); be->ops = ops; be->n_ops = n_ops; _nss_mdns_updatecfg(be); return ((nss_backend_t *)be); } void _nss_mdns_destr(mdns_backend_ptr_t be) { if (be != NULL) { _nss_mdns_freesmfcfg(be); free(be); } } static int searchdomain(mdns_backend_ptr_t be, char *name, int srchidx, char **sname) { int trailing_dot = 0; char *ch; *sname = NULL; ch = name + strlen(name) - 1; if ((*ch) == '.') trailing_dot++; if (trailing_dot && srchidx > 0) /* * If there is a trailing dot in the query * name, do not perform any additional queries * with search domains. */ return (-1); if (srchidx == 0) { /* * If there is a trailing dot in the query * or atleast one dot in the query name then * perform a query as-is once first. */ ++srchidx; if ((trailing_dot || (strchr(name, '.') != NULL))) { if (validdomain(be, name, 1) == B_TRUE) return (srchidx); else if (trailing_dot) return (-1); } } if ((srchidx > NSSMDNS_MAXSRCHDMNS) || (be->dmnsrchlist[srchidx-1] == NULL)) return (-1); *sname = be->dmnsrchlist[srchidx-1]; return (++srchidx); } /* * This function determines if the domain name in the query * matches any of the valid & search domains in the nss_mdns * configuration. */ static boolean_t validdomain(mdns_backend_ptr_t be, char *name, int chksrchdmns) { char *nameptr; /* Remove any trailing and leading dots in the name */ nameptr = name + strlen(name) - 1; while (*nameptr && (nameptr != name) && (*nameptr == '.')) nameptr--; *(++nameptr) = '\0'; nameptr = name; while (*nameptr && (*nameptr == '.')) nameptr++; if (*nameptr == '\0') return (B_FALSE); /* Compare with search domains */ if (chksrchdmns && (cmpdmn(nameptr, be->dmnsrchlist, NSSMDNS_MAXSRCHDMNS) == B_TRUE)) return (B_TRUE); /* Compare with valid domains */ return (cmpdmn(nameptr, be->validdmnlist, NSSMDNS_MAXVALIDDMNS)); } static boolean_t cmpdmn(char *name, char **dmnlist, int maxdmns) { char *vptr; int vdlen; char *cptr; int nlen; int i; nlen = strlen(name); for (i = 0; (i < maxdmns) && ((vptr = dmnlist[i]) != NULL); i++) { vdlen = strlen(vptr); if (vdlen > nlen) continue; cptr = name + nlen - vdlen; if (strncasecmp(cptr, vptr, vdlen) == 0) return (B_TRUE); } return (B_FALSE); } static void _nss_mdns_get_svcstatetimestamp(struct timeval *ptv) { scf_handle_t *h; scf_simple_prop_t *sprop; int32_t nsec; (void) memset(ptv, 0, sizeof (struct timeval)); h = scf_handle_create(SCF_VERSION); if (h == NULL) return; if (scf_handle_bind(h) == -1) { scf_handle_destroy(h); return; } if ((sprop = scf_simple_prop_get(h, SMF_MDNS_FMRI, SCF_PG_RESTARTER, SCF_PROPERTY_STATE_TIMESTAMP)) != NULL) { ptv->tv_sec = *(time_t *)(scf_simple_prop_next_time(sprop, &nsec)); ptv->tv_usec = nsec / 1000; scf_simple_prop_free(sprop); } if (h != NULL) scf_handle_destroy(h); } void _nss_mdns_updatecfg(mdns_backend_ptr_t be) { struct timeval statetimestamp; /* * Update configuration if current svc state timestamp * is different from last known svc state timestamp */ _nss_mdns_get_svcstatetimestamp(&statetimestamp); if ((statetimestamp.tv_sec == 0) && (statetimestamp.tv_usec == 0)) { syslog(LOG_ERR, "nss_mdns: error checking " \ "svc:/network/dns/multicast:default" \ " service timestamp"); } else if ((be->conftimestamp.tv_sec == statetimestamp.tv_sec) && (be->conftimestamp.tv_usec == statetimestamp.tv_usec)) { return; } _nss_mdns_freesmfcfg(be); _nss_mdns_loadsmfcfg(be); be->conftimestamp.tv_sec = statetimestamp.tv_sec; be->conftimestamp.tv_usec = statetimestamp.tv_usec; } static void load_mdns_domaincfg(scf_handle_t *h, char **storelist, const char *scfprop, int maxprops) { scf_simple_prop_t *sprop; char *tchr; char *pchr; int tlen; int cnt = 0; if ((sprop = scf_simple_prop_get(h, SMF_MDNS_FMRI, SMF_NSSMDNSCFG_PROPGRP, scfprop)) == NULL) return; while ((cnt < maxprops) && (tchr = scf_simple_prop_next_astring(sprop)) != NULL) { /* Remove beginning & trailing '.' chars */ while (*tchr && (*tchr == '.')) tchr++; if (*tchr && ((tlen = strlen(tchr)) < MAXDNAME)) { pchr = &tchr[tlen-1]; while ((pchr != tchr) && (*pchr == '.')) pchr--; *(++pchr) = '\0'; storelist[cnt] = strdup(tchr); cnt++; } } scf_simple_prop_free(sprop); } static void _nss_mdns_loadsmfcfg(mdns_backend_ptr_t be) { scf_handle_t *h; h = scf_handle_create(SCF_VERSION); if (h == NULL) return; if (scf_handle_bind(h) == -1) { scf_handle_destroy(h); return; } load_mdns_domaincfg(h, &(be->dmnsrchlist[0]), SMF_NSSMDNSCFG_SRCHPROP, NSSMDNS_MAXSRCHDMNS); load_mdns_domaincfg(h, &(be->validdmnlist[0]), SMF_NSSMDNSCFG_DMNPROP, NSSMDNS_MAXVALIDDMNS); if (h != NULL) scf_handle_destroy(h); } static void _nss_mdns_freesmfcfg(mdns_backend_ptr_t be) { int idx; if (be == NULL) return; for (idx = 0; idx < NSSMDNS_MAXSRCHDMNS; idx++) { if (be->dmnsrchlist[idx] != NULL) { free(be->dmnsrchlist[idx]); be->dmnsrchlist[idx] = NULL; } } for (idx = 0; idx < NSSMDNS_MAXVALIDDMNS; idx++) { if (be->validdmnlist[idx] != NULL) { free(be->validdmnlist[idx]); be->validdmnlist[idx] = NULL; } } } /* * Performs lookup for IP address by hostname via mDNS and returns * results along with the TTL value from the mDNS resource records. * Called by nscd wth a ptr to packed bufer and packed buffer size. */ nss_status_t _nss_mdns_gethost_withttl(void *buffer, size_t bufsize, int ipnode) { nss_pheader_t *pbuf = (nss_pheader_t *)buffer; nss_XbyY_args_t arg; int dbop; int af; int len; int blen; char *dbname; nss_status_t sret; char *hname; struct mdns_querydata qdata; nssuint_t *pttl; mdns_backend_ptr_t be = NULL; (void) memset(&qdata, 0, sizeof (struct mdns_querydata)); qdata.argp = &arg; /* * Retrieve withttl buffer and size from the passed packed buffer. * Results are returned along with ttl in this buffer. */ qdata.withttlbsize = pbuf->data_len - sizeof (nssuint_t); qdata.withttlbuffer = (char *)buffer + pbuf->data_off; sret = nss_packed_getkey(buffer, bufsize, &dbname, &dbop, &arg); if (sret != NSS_SUCCESS) return (NSS_ERROR); if (ipnode) { if (arg.key.ipnode.flags != 0) return (NSS_ERROR); hname = (char *)arg.key.ipnode.name; af = arg.key.ipnode.af_family; } else { af = AF_INET; hname = (char *)arg.key.name; } if ((be = (mdns_backend_ptr_t)calloc(1, sizeof (*be))) == NULL) return (NSS_ERROR); _nss_mdns_updatecfg(be); /* Zero out the withttl buffer prior to use */ (void) memset(qdata.withttlbuffer, 0, qdata.withttlbsize); #ifdef DEBUG syslog(LOG_DEBUG, "nss_mdns: querybyname withttl called" \ " af:%d hname:%s", af, hname); #endif if (_nss_mdns_querybyname(be, hname, af, &qdata) == NSS_SUCCESS) { blen = strlen(qdata.buffer); len = ROUND_UP(blen, sizeof (nssuint_t)); if (len + sizeof (nssuint_t) > pbuf->data_len) { _nss_mdns_freesmfcfg(be); free(be); return (NSS_ERROR); } pbuf->ext_off = pbuf->data_off + len; pbuf->ext_len = sizeof (nssuint_t); pbuf->data_len = blen; /* Return ttl in the packed buffer at ext_off */ pttl = (nssuint_t *)((void *)((char *)pbuf + pbuf->ext_off)); *pttl = qdata.ttl; _nss_mdns_freesmfcfg(be); free(be); return (NSS_SUCCESS); } _nss_mdns_freesmfcfg(be); free(be); return (NSS_ERROR); }