/*
* 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 (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
*/
#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)) == NULL) {
DNSServiceRefDeallocate(ref);
data->status = NSS_UNAVAIL;
data->argp->h_errno = NO_RECOVERY;
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;
data->argp->h_errno = HOST_NOT_FOUND;
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;
}
data->argp->h_errno = 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 {
data->argp->h_errno = HOST_NOT_FOUND;
return (NSS_NOTFOUND);
}
name = strdup(qname);
if (name == NULL) {
data->argp->h_errno = NO_RECOVERY;
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;
data->argp->h_errno = HOST_NOT_FOUND;
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 '.' separator
*/
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 buffer 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);
}