/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (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 2015 Nexenta Systems, Inc. All rights reserved.
*/
/*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Copyright (c) 2012 by Delphix. All rights reserved.
*/
/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/* All Rights Reserved */
/*
* University Copyright- Copyright (c) 1982, 1986, 1988
* The Regents of the University of California
* All Rights Reserved
*
* University Acknowledgment- Portions of this document are derived from
* software developed by the University of California, Berkeley, and its
* contributors.
*/
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <syslog.h>
#include <rpc/rpc.h>
#include <rpcsvc/sm_inter.h>
#include <rpcsvc/nsm_addr.h>
#include <memory.h>
#include <net/if.h>
#include <sys/sockio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <netdir.h>
#include <synch.h>
#include <thread.h>
#include <ifaddrs.h>
#include <errno.h>
#include <assert.h>
#include "sm_statd.h"
static int local_state; /* fake local sm state */
/* client name-to-address translation table */
static name_addr_entry_t *name_addr = NULL;
#define LOGHOST "loghost"
static void delete_mon(char *mon_name, my_id *my_idp);
static void insert_mon(mon *monp);
static void pr_mon(char *);
static int statd_call_lockd(mon *monp, int state);
static int hostname_eq(char *host1, char *host2);
static char *get_system_id(char *hostname);
static void add_aliases(struct hostent *phost);
static void *thr_send_notice(void *);
static void delete_onemon(char *mon_name, my_id *my_idp,
mon_entry **monitor_q);
static void send_notice(char *mon_name, int state);
static void add_to_host_array(char *host);
static int in_host_array(char *host);
static void pr_name_addr(name_addr_entry_t *name_addr);
extern int self_check(char *hostname);
extern struct lifconf *getmyaddrs(void);
/* ARGSUSED */
void
sm_stat_svc(sm_name *namep, sm_stat_res *resp)
{
if (debug)
(void) printf("proc sm_stat: mon_name = %s\n",
namep->mon_name);
resp->res_stat = stat_succ;
resp->state = LOCAL_STATE;
}
/* ARGSUSED */
void
sm_mon_svc(mon *monp, sm_stat_res *resp)
{
mon_id *monidp;
monidp = &monp->mon_id;
rw_rdlock(&thr_rwlock);
if (debug) {
(void) printf("proc sm_mon: mon_name = %s, id = %d\n",
monidp->mon_name, *((int *)monp->priv));
pr_mon(monp->mon_id.mon_name);
}
/* only monitor other hosts */
if (self_check(monp->mon_id.mon_name) == 0) {
/* store monitor request into monitor_q */
insert_mon(monp);
}
pr_mon(monp->mon_id.mon_name);
resp->res_stat = stat_succ;
resp->state = local_state;
rw_unlock(&thr_rwlock);
}
/* ARGSUSED */
void
sm_unmon_svc(mon_id *monidp, sm_stat *resp)
{
rw_rdlock(&thr_rwlock);
if (debug) {
(void) printf(
"proc sm_unmon: mon_name = %s, [%s, %d, %d, %d]\n",
monidp->mon_name, monidp->my_id.my_name,
monidp->my_id.my_prog, monidp->my_id.my_vers,
monidp->my_id.my_proc);
pr_mon(monidp->mon_name);
}
delete_mon(monidp->mon_name, &monidp->my_id);
pr_mon(monidp->mon_name);
resp->state = local_state;
rw_unlock(&thr_rwlock);
}
/* ARGSUSED */
void
sm_unmon_all_svc(my_id *myidp, sm_stat *resp)
{
rw_rdlock(&thr_rwlock);
if (debug)
(void) printf("proc sm_unmon_all: [%s, %d, %d, %d]\n",
myidp->my_name,
myidp->my_prog, myidp->my_vers,
myidp->my_proc);
delete_mon(NULL, myidp);
pr_mon(NULL);
resp->state = local_state;
rw_unlock(&thr_rwlock);
}
/*
* Notifies lockd specified by name that state has changed for this server.
*/
void
sm_notify_svc(stat_chge *ntfp)
{
rw_rdlock(&thr_rwlock);
if (debug)
(void) printf("sm_notify: %s state =%d\n",
ntfp->mon_name, ntfp->state);
send_notice(ntfp->mon_name, ntfp->state);
rw_unlock(&thr_rwlock);
}
/* ARGSUSED */
void
sm_simu_crash_svc(void *myidp)
{
int i;
struct mon_entry *monitor_q;
int found = 0;
if (debug)
(void) printf("proc sm_simu_crash\n");
/* Only one crash should be running at a time. */
mutex_lock(&crash_lock);
if (in_crash != 0) {
mutex_unlock(&crash_lock);
return;
}
in_crash = 1;
mutex_unlock(&crash_lock);
for (i = 0; i < MAX_HASHSIZE; i++) {
mutex_lock(&mon_table[i].lock);
monitor_q = mon_table[i].sm_monhdp;
if (monitor_q != NULL) {
mutex_unlock(&mon_table[i].lock);
found = 1;
break;
}
mutex_unlock(&mon_table[i].lock);
}
/*
* If there are entries found in the monitor table,
* initiate a crash, else zero out the in_crash variable.
*/
if (found) {
mutex_lock(&crash_lock);
die = 1;
/* Signal sm_try() thread if sleeping. */
cond_signal(&retrywait);
mutex_unlock(&crash_lock);
rw_wrlock(&thr_rwlock);
sm_crash();
rw_unlock(&thr_rwlock);
} else {
mutex_lock(&crash_lock);
in_crash = 0;
mutex_unlock(&crash_lock);
}
}
/* ARGSUSED */
void
nsmaddrproc1_reg(reg1args *regargs, reg1res *regresp)
{
nsm_addr_res status;
name_addr_entry_t *entry;
char *tmp_n_bytes;
addr_entry_t *addr;
rw_rdlock(&thr_rwlock);
if (debug) {
int i;
(void) printf("nap1_reg: fam= %d, name= %s, len= %d\n",
regargs->family, regargs->name, regargs->address.n_len);
(void) printf("address is: ");
for (i = 0; i < regargs->address.n_len; i++) {
(void) printf("%d.",
(unsigned char)regargs->address.n_bytes[i]);
}
(void) printf("\n");
}
/*
* Locate the entry with the name in the NSM_ADDR_REG request if
* it exists. If it doesn't, create a new entry to hold this name.
* The first time through this code, name_addr starts out as NULL.
*/
mutex_lock(&name_addrlock);
for (entry = name_addr; entry; entry = entry->next) {
if (strcmp(regargs->name, entry->name) == 0) {
if (debug) {
(void) printf("nap1_reg: matched name %s\n",
entry->name);
}
break;
}
}
if (entry == NULL) {
entry = (name_addr_entry_t *)malloc(sizeof (*entry));
if (entry == NULL) {
if (debug) {
(void) printf(
"nsmaddrproc1_reg: no memory for entry\n");
}
status = nsm_addr_fail;
goto done;
}
entry->name = strdup(regargs->name);
if (entry->name == NULL) {
if (debug) {
(void) printf(
"nsmaddrproc1_reg: no memory for name\n");
}
free(entry);
status = nsm_addr_fail;
goto done;
}
entry->addresses = NULL;
/*
* Link the new entry onto the *head* of the name_addr
* table.
*
* Note: there is code below in the address maintenance
* section that assumes this behavior.
*/
entry->next = name_addr;
name_addr = entry;
}
/*
* Try to match the address in the request; if it doesn't match,
* add it to the entry's address list.
*/
for (addr = entry->addresses; addr; addr = addr->next) {
if (addr->family == (sa_family_t)regargs->family &&
addr->ah.n_len == regargs->address.n_len &&
memcmp(addr->ah.n_bytes, regargs->address.n_bytes,
addr->ah.n_len) == 0) {
if (debug) {
int i;
(void) printf("nap1_reg: matched addr ");
for (i = 0; i < addr->ah.n_len; i++) {
(void) printf("%d.",
(unsigned char)addr->ah.n_bytes[i]);
}
(void) printf(" family %d for name %s\n",
addr->family, entry->name);
}
break;
}
}
if (addr == NULL) {
addr = (addr_entry_t *)malloc(sizeof (*addr));
tmp_n_bytes = (char *)malloc(regargs->address.n_len);
if (addr == NULL || tmp_n_bytes == NULL) {
if (debug) {
(void) printf("nap1_reg: no memory for addr\n");
}
/*
* If this name entry was just newly made in the
* table, back it out now that we can't register
* an address with it anyway.
*
* Note: we are making an assumption about how
* names are added to (the head of) name_addr here.
*/
if (entry == name_addr && entry->addresses == NULL) {
name_addr = name_addr->next;
free(entry->name);
free(entry);
if (tmp_n_bytes)
free(tmp_n_bytes);
if (addr)
free(addr);
status = nsm_addr_fail;
goto done;
}
}
/*
* Note: this check for address family assumes that we
* will get something different here someday for
* other supported address types, such as IPv6.
*/
addr->ah.n_len = regargs->address.n_len;
addr->ah.n_bytes = tmp_n_bytes;
addr->family = regargs->family;
if (debug) {
if ((addr->family != AF_INET) &&
(addr->family != AF_INET6)) {
(void) printf(
"nap1_reg: unknown addr family %d\n",
addr->family);
}
}
(void) memcpy(addr->ah.n_bytes, regargs->address.n_bytes,
addr->ah.n_len);
addr->next = entry->addresses;
entry->addresses = addr;
}
status = nsm_addr_succ;
done:
regresp->status = status;
if (debug) {
pr_name_addr(name_addr);
}
mutex_unlock(&name_addrlock);
rw_unlock(&thr_rwlock);
}
/*
* Insert an entry into the monitor_q. Space for the entry is allocated
* here. It is then filled in from the information passed in.
*/
static void
insert_mon(mon *monp)
{
mon_entry *new, *found;
my_id *my_idp, *nl_idp;
mon_entry *monitor_q;
unsigned int hash;
name_addr_entry_t *entry;
addr_entry_t *addr;
/* Allocate entry for new */
if ((new = (mon_entry *) malloc(sizeof (mon_entry))) == 0) {
syslog(LOG_ERR,
"statd: insert_mon: malloc error on mon %s (id=%d)\n",
monp->mon_id.mon_name, *((int *)monp->priv));
return;
}
/* Initialize and copy contents of monp to new */
(void) memset(new, 0, sizeof (mon_entry));
(void) memcpy(&new->id, monp, sizeof (mon));
/* Allocate entry for new mon_name */
if ((new->id.mon_id.mon_name = strdup(monp->mon_id.mon_name)) == 0) {
syslog(LOG_ERR,
"statd: insert_mon: malloc error on mon %s (id=%d)\n",
monp->mon_id.mon_name, *((int *)monp->priv));
free(new);
return;
}
/* Allocate entry for new my_name */
if ((new->id.mon_id.my_id.my_name =
strdup(monp->mon_id.my_id.my_name)) == 0) {
syslog(LOG_ERR,
"statd: insert_mon: malloc error on mon %s (id=%d)\n",
monp->mon_id.mon_name, *((int *)monp->priv));
free(new->id.mon_id.mon_name);
free(new);
return;
}
if (debug)
(void) printf("add_mon(%x) %s (id=%d)\n",
(int)new, new->id.mon_id.mon_name, *((int *)new->id.priv));
/*
* Record the name, and all addresses which have been registered
* for this name, in the filesystem name space.
*/
record_name(new->id.mon_id.mon_name, 1);
if (regfiles_only == 0) {
mutex_lock(&name_addrlock);
for (entry = name_addr; entry; entry = entry->next) {
if (strcmp(new->id.mon_id.mon_name, entry->name) != 0) {
continue;
}
for (addr = entry->addresses; addr; addr = addr->next) {
record_addr(new->id.mon_id.mon_name,
addr->family, &addr->ah);
}
break;
}
mutex_unlock(&name_addrlock);
}
SMHASH(new->id.mon_id.mon_name, hash);
mutex_lock(&mon_table[hash].lock);
monitor_q = mon_table[hash].sm_monhdp;
/* If mon_table hash list is empty. */
if (monitor_q == NULL) {
if (debug)
(void) printf("\nAdding to monitor_q hash %d\n", hash);
new->nxt = new->prev = NULL;
mon_table[hash].sm_monhdp = new;
mutex_unlock(&mon_table[hash].lock);
return;
} else {
found = 0;
my_idp = &new->id.mon_id.my_id;
while (monitor_q != NULL) {
/*
* This list is searched sequentially for the
* tuple (hostname, prog, vers, proc). The tuples
* are inserted in the beginning of the monitor_q,
* if the hostname is not already present in the list.
* If the hostname is found in the list, the incoming
* tuple is inserted just after all the tuples with the
* same hostname. However, if the tuple matches exactly
* with an entry in the list, space allocated for the
* new entry is released and nothing is inserted in the
* list.
*/
if (str_cmp_unqual_hostname(
monitor_q->id.mon_id.mon_name,
new->id.mon_id.mon_name) == 0) {
/* found */
nl_idp = &monitor_q->id.mon_id.my_id;
if ((str_cmp_unqual_hostname(my_idp->my_name,
nl_idp->my_name) == 0) &&
my_idp->my_prog == nl_idp->my_prog &&
my_idp->my_vers == nl_idp->my_vers &&
my_idp->my_proc == nl_idp->my_proc) {
/*
* already exists an identical one,
* release the space allocated for the
* mon_entry
*/
free(new->id.mon_id.mon_name);
free(new->id.mon_id.my_id.my_name);
free(new);
mutex_unlock(&mon_table[hash].lock);
return;
} else {
/*
* mark the last callback that is
* not matching; new is inserted
* after this
*/
found = monitor_q;
}
} else if (found)
break;
monitor_q = monitor_q->nxt;
}
if (found) {
/*
* insert just after the entry having matching tuple.
*/
new->nxt = found->nxt;
new->prev = found;
if (found->nxt != NULL)
found->nxt->prev = new;
found->nxt = new;
} else {
/*
* not found, insert in front of list.
*/
new->nxt = mon_table[hash].sm_monhdp;
new->prev = (mon_entry *) NULL;
if (new->nxt != (mon_entry *) NULL)
new->nxt->prev = new;
mon_table[hash].sm_monhdp = new;
}
mutex_unlock(&mon_table[hash].lock);
return;
}
}
/*
* Deletes a specific monitor name or deletes all monitors with same id
* in hash table.
*/
static void
delete_mon(char *mon_name, my_id *my_idp)
{
unsigned int hash;
if (mon_name != NULL) {
record_name(mon_name, 0);
SMHASH(mon_name, hash);
mutex_lock(&mon_table[hash].lock);
delete_onemon(mon_name, my_idp, &mon_table[hash].sm_monhdp);
mutex_unlock(&mon_table[hash].lock);
} else {
for (hash = 0; hash < MAX_HASHSIZE; hash++) {
mutex_lock(&mon_table[hash].lock);
delete_onemon(mon_name, my_idp,
&mon_table[hash].sm_monhdp);
mutex_unlock(&mon_table[hash].lock);
}
}
}
/*
* Deletes a monitor in list.
* IF mon_name is NULL, delete all mon_names that have the same id,
* else delete specific monitor.
*/
void
delete_onemon(char *mon_name, my_id *my_idp, mon_entry **monitor_q)
{
mon_entry *next, *nl;
my_id *nl_idp;
next = *monitor_q;
while ((nl = next) != NULL) {
next = next->nxt;
if (mon_name == NULL || (mon_name != NULL &&
str_cmp_unqual_hostname(nl->id.mon_id.mon_name,
mon_name) == 0)) {
nl_idp = &nl->id.mon_id.my_id;
if ((str_cmp_unqual_hostname(my_idp->my_name,
nl_idp->my_name) == 0) &&
my_idp->my_prog == nl_idp->my_prog &&
my_idp->my_vers == nl_idp->my_vers &&
my_idp->my_proc == nl_idp->my_proc) {
/* found */
if (debug)
(void) printf("delete_mon(%x): %s\n",
(int)nl, mon_name ?
mon_name : "<NULL>");
/*
* Remove the monitor name from the
* record_q, if id matches.
*/
record_name(nl->id.mon_id.mon_name, 0);
/* if nl is not the first entry on list */
if (nl->prev != NULL)
nl->prev->nxt = nl->nxt;
else {
*monitor_q = nl->nxt;
}
if (nl->nxt != NULL)
nl->nxt->prev = nl->prev;
free(nl->id.mon_id.mon_name);
free(nl_idp->my_name);
free(nl);
}
} /* end of if mon */
}
}
/*
* Notify lockd of host specified by mon_name that the specified state
* has changed.
*/
static void
send_notice(char *mon_name, int state)
{
struct mon_entry *next;
mon_entry *monitor_q;
unsigned int hash;
moninfo_t *minfop;
mon *monp;
SMHASH(mon_name, hash);
mutex_lock(&mon_table[hash].lock);
monitor_q = mon_table[hash].sm_monhdp;
next = monitor_q;
while (next != NULL) {
if (hostname_eq(next->id.mon_id.mon_name, mon_name)) {
monp = &next->id;
/*
* Prepare the minfop structure to pass to
* thr_create(). This structure is a copy of
* mon info and state.
*/
if ((minfop =
(moninfo_t *)xmalloc(sizeof (moninfo_t))) != NULL) {
(void) memcpy(&minfop->id, monp, sizeof (mon));
/* Allocate entry for mon_name */
if ((minfop->id.mon_id.mon_name =
strdup(monp->mon_id.mon_name)) == 0) {
syslog(LOG_ERR, "statd: send_notice: "
"malloc error on mon %s (id=%d)\n",
monp->mon_id.mon_name,
*((int *)monp->priv));
free(minfop);
continue;
}
/* Allocate entry for my_name */
if ((minfop->id.mon_id.my_id.my_name =
strdup(monp->mon_id.my_id.my_name)) == 0) {
syslog(LOG_ERR, "statd: send_notice: "
"malloc error on mon %s (id=%d)\n",
monp->mon_id.mon_name,
*((int *)monp->priv));
free(minfop->id.mon_id.mon_name);
free(minfop);
continue;
}
minfop->state = state;
/*
* Create detached threads to process each host
* to notify. If error, print out msg, free
* resources and continue.
*/
if (thr_create(NULL, NULL, thr_send_notice,
minfop, THR_DETACHED, NULL)) {
syslog(LOG_ERR, "statd: unable to "
"create thread to send_notice to "
"%s.\n", mon_name);
free(minfop->id.mon_id.mon_name);
free(minfop->id.mon_id.my_id.my_name);
free(minfop);
continue;
}
}
}
next = next->nxt;
}
mutex_unlock(&mon_table[hash].lock);
}
/*
* Work thread created to do the actual statd_call_lockd
*/
static void *
thr_send_notice(void *arg)
{
moninfo_t *minfop;
minfop = (moninfo_t *)arg;
if (statd_call_lockd(&minfop->id, minfop->state) == -1) {
if (debug && minfop->id.mon_id.mon_name)
(void) printf("problem with notifying %s failure, "
"give up\n", minfop->id.mon_id.mon_name);
} else {
if (debug)
(void) printf("send_notice: %s, %d notified.\n",
minfop->id.mon_id.mon_name, minfop->state);
}
free(minfop->id.mon_id.mon_name);
free(minfop->id.mon_id.my_id.my_name);
free(minfop);
thr_exit((void *) 0);
#ifdef lint
/*NOTREACHED*/
return ((void *)0);
#endif
}
/*
* Contact lockd specified by monp.
*/
static int
statd_call_lockd(mon *monp, int state)
{
enum clnt_stat clnt_stat;
struct timeval tottimeout;
struct sm_status stat;
my_id *my_idp;
char *mon_name;
int i;
int rc = 0;
CLIENT *clnt;
mon_name = monp->mon_id.mon_name;
my_idp = &monp->mon_id.my_id;
(void) memset(&stat, 0, sizeof (stat));
stat.mon_name = mon_name;
stat.state = state;
for (i = 0; i < 16; i++) {
stat.priv[i] = monp->priv[i];
}
if (debug)
(void) printf("statd_call_lockd: %s state = %d\n",
stat.mon_name, stat.state);
tottimeout.tv_sec = SM_RPC_TIMEOUT;
tottimeout.tv_usec = 0;
clnt = create_client(my_idp->my_name, my_idp->my_prog, my_idp->my_vers,
"ticotsord", &tottimeout);
if (clnt == NULL) {
return (-1);
}
clnt_stat = clnt_call(clnt, my_idp->my_proc, xdr_sm_status,
(char *)&stat, xdr_void, NULL, tottimeout);
if (debug) {
(void) printf("clnt_stat=%s(%d)\n",
clnt_sperrno(clnt_stat), clnt_stat);
}
if (clnt_stat != (int)RPC_SUCCESS) {
syslog(LOG_WARNING,
"statd: cannot talk to lockd at %s, %s(%d)\n",
my_idp->my_name, clnt_sperrno(clnt_stat), clnt_stat);
rc = -1;
}
clnt_destroy(clnt);
return (rc);
}
/*
* Client handle created.
*/
CLIENT *
create_client(char *host, int prognum, int versnum, char *netid,
struct timeval *utimeout)
{
int fd;
struct timeval timeout;
CLIENT *client;
struct t_info tinfo;
if (netid == NULL) {
client = clnt_create_timed(host, prognum, versnum,
"netpath", utimeout);
} else {
struct netconfig *nconf;
nconf = getnetconfigent(netid);
if (nconf == NULL) {
return (NULL);
}
client = clnt_tp_create_timed(host, prognum, versnum, nconf,
utimeout);
freenetconfigent(nconf);
}
if (client == NULL) {
return (NULL);
}
(void) CLNT_CONTROL(client, CLGET_FD, (caddr_t)&fd);
if (t_getinfo(fd, &tinfo) != -1) {
if (tinfo.servtype == T_CLTS) {
/*
* Set time outs for connectionless case
*/
timeout.tv_usec = 0;
timeout.tv_sec = SM_CLTS_TIMEOUT;
(void) CLNT_CONTROL(client,
CLSET_RETRY_TIMEOUT, (caddr_t)&timeout);
}
} else
return (NULL);
return (client);
}
/*
* ONLY for debugging.
* Debug messages which prints out the monitor table information.
* If name is specified, just print out the hash list corresponding
* to name, otherwise print out the entire monitor table.
*/
static void
pr_mon(char *name)
{
mon_entry *nl;
int hash;
if (!debug)
return;
/* print all */
if (name == NULL) {
for (hash = 0; hash < MAX_HASHSIZE; hash++) {
mutex_lock(&mon_table[hash].lock);
nl = mon_table[hash].sm_monhdp;
if (nl == NULL) {
(void) printf(
"*****monitor_q = NULL hash %d\n", hash);
mutex_unlock(&mon_table[hash].lock);
continue;
}
(void) printf("*****monitor_q:\n ");
while (nl != NULL) {
(void) printf("%s:(%x), ",
nl->id.mon_id.mon_name, (int)nl);
nl = nl->nxt;
}
mutex_unlock(&mon_table[hash].lock);
(void) printf("\n");
}
} else { /* print one hash list */
SMHASH(name, hash);
mutex_lock(&mon_table[hash].lock);
nl = mon_table[hash].sm_monhdp;
if (nl == NULL) {
(void) printf("*****monitor_q = NULL hash %d\n", hash);
} else {
(void) printf("*****monitor_q:\n ");
while (nl != NULL) {
(void) printf("%s:(%x), ",
nl->id.mon_id.mon_name, (int)nl);
nl = nl->nxt;
}
(void) printf("\n");
}
mutex_unlock(&mon_table[hash].lock);
}
}
/*
* Only for debugging.
* Dump the host name-to-address translation table passed in `name_addr'.
*/
static void
pr_name_addr(name_addr_entry_t *name_addr)
{
name_addr_entry_t *entry;
addr_entry_t *addr;
struct in_addr ipv4_addr;
char *ipv6_addr;
char abuf[INET6_ADDRSTRLEN];
assert(MUTEX_HELD(&name_addrlock));
(void) printf("name-to-address translation table:\n");
for (entry = name_addr; entry != NULL; entry = entry->next) {
(void) printf("\t%s: ",
(entry->name ? entry->name : "(null)"));
for (addr = entry->addresses; addr; addr = addr->next) {
switch (addr->family) {
case AF_INET:
ipv4_addr = *(struct in_addr *)addr->ah.n_bytes;
(void) printf(" %s (fam %d)",
inet_ntoa(ipv4_addr), addr->family);
break;
case AF_INET6:
ipv6_addr = (char *)addr->ah.n_bytes;
(void) printf(" %s (fam %d)",
inet_ntop(addr->family, ipv6_addr, abuf,
sizeof (abuf)), addr->family);
break;
default:
return;
}
}
printf("\n");
}
}
/*
* First, try to compare the hostnames as strings. If the hostnames does not
* match we might deal with the hostname aliases. In this case two different
* aliases for the same machine don't match each other when using strcmp. To
* deal with this, the hostnames must be translated into some sort of universal
* identifier. These identifiers can be compared. Universal network addresses
* are currently used for this identifier because it is general and easy to do.
* Other schemes are possible and this routine could be converted if required.
*
* If it can't find an address for some reason, 0 is returned.
*/
static int
hostname_eq(char *host1, char *host2)
{
char *sysid1;
char *sysid2;
int rv;
/* Compare hostnames as strings */
if (host1 != NULL && host2 != NULL && strcmp(host1, host2) == 0)
return (1);
/* Try harder if hostnames do not match */
sysid1 = get_system_id(host1);
sysid2 = get_system_id(host2);
if ((sysid1 == NULL) || (sysid2 == NULL))
rv = 0;
else
rv = (strcmp(sysid1, sysid2) == 0);
free(sysid1);
free(sysid2);
return (rv);
}
/*
* Convert a hostname character string into its network address.
* A network address is found by searching through all the entries
* in /etc/netconfig and doing a netdir_getbyname() for each inet
* entry found. The netbuf structure returned is converted into
* a universal address format.
*
* If a NULL hostname is given, then the name of the current host
* is used. If the hostname doesn't map to an address, a NULL
* pointer is returned.
*
* N.B. the character string returned is allocated in taddr2uaddr()
* and should be freed by the caller using free().
*/
static char *
get_system_id(char *hostname)
{
void *hp;
struct netconfig *ncp;
struct nd_hostserv service;
struct nd_addrlist *addrs;
char *uaddr;
int rv;
if (hostname == NULL)
service.h_host = HOST_SELF;
else
service.h_host = hostname;
service.h_serv = NULL;
hp = setnetconfig();
if (hp == (void *) NULL) {
return (NULL);
}
while ((ncp = getnetconfig(hp)) != NULL) {
if ((strcmp(ncp->nc_protofmly, NC_INET) == 0) ||
(strcmp(ncp->nc_protofmly, NC_INET6) == 0)) {
addrs = NULL;
rv = netdir_getbyname(ncp, &service, &addrs);
if (rv != 0) {
continue;
}
if (addrs) {
uaddr = taddr2uaddr(ncp, addrs->n_addrs);
netdir_free(addrs, ND_ADDRLIST);
endnetconfig(hp);
return (uaddr);
}
}
else
continue;
}
endnetconfig(hp);
return (NULL);
}
void
merge_hosts(void)
{
struct lifconf *lifc = NULL;
int sock = -1;
struct lifreq *lifrp;
struct lifreq lifr;
int n;
struct sockaddr_in *sin;
struct sockaddr_in6 *sin6;
struct sockaddr_storage *sa;
int af;
struct hostent *phost;
char *addr;
size_t alen;
int errnum;
/*
* This function will enumerate all the interfaces for
* this platform, then get the hostent for each i/f.
* With the hostent structure, we can get all of the
* aliases for the i/f. Then we'll merge all the aliases
* with the existing host_name[] list to come up with
* all of the known names for each interface. This solves
* the problem of a multi-homed host not knowing which
* name to publish when statd is started. All the aliases
* will be stored in the array, host_name.
*
* NOTE: Even though we will use all of the aliases we
* can get from the i/f hostent, the receiving statd
* will still need to handle aliases with hostname_eq.
* This is because the sender's aliases may not match
* those of the receiver.
*/
lifc = getmyaddrs();
if (lifc == NULL) {
goto finish;
}
lifrp = lifc->lifc_req;
for (n = lifc->lifc_len / sizeof (struct lifreq); n > 0; n--, lifrp++) {
(void) strncpy(lifr.lifr_name, lifrp->lifr_name,
sizeof (lifr.lifr_name));
af = lifrp->lifr_addr.ss_family;
sock = socket(af, SOCK_DGRAM, 0);
if (sock == -1) {
syslog(LOG_ERR, "statd: socket failed\n");
goto finish;
}
/* If it's the loopback interface, ignore */
if (ioctl(sock, SIOCGLIFFLAGS, (caddr_t)&lifr) < 0) {
syslog(LOG_ERR,
"statd: SIOCGLIFFLAGS failed, error: %m\n");
goto finish;
}
if (lifr.lifr_flags & IFF_LOOPBACK)
continue;
if (ioctl(sock, SIOCGLIFADDR, (caddr_t)&lifr) < 0) {
syslog(LOG_ERR,
"statd: SIOCGLIFADDR failed, error: %m\n");
goto finish;
}
sa = (struct sockaddr_storage *)&(lifr.lifr_addr);
if (sa->ss_family == AF_INET) {
sin = (struct sockaddr_in *)&lifr.lifr_addr;
addr = (char *)(&sin->sin_addr);
alen = sizeof (struct in_addr);
} else if (sa->ss_family == AF_INET6) {
sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr;
addr = (char *)(&sin6->sin6_addr);
alen = sizeof (struct in6_addr);
} else {
syslog(LOG_WARNING,
"unexpected address family (%d)",
sa->ss_family);
continue;
}
phost = getipnodebyaddr(addr, alen, sa->ss_family, &errnum);
if (phost)
add_aliases(phost);
}
/*
* Now, just in case we didn't get them all byaddr,
* let's look by name.
*/
phost = getipnodebyname(hostname, AF_INET6, AI_ALL, &errnum);
if (phost)
add_aliases(phost);
finish:
if (sock != -1)
(void) close(sock);
if (lifc) {
free(lifc->lifc_buf);
free(lifc);
}
}
/*
* add_aliases traverses a hostent alias list, compares
* the aliases to the contents of host_name, and if an
* alias is not already present, adds it to host_name[].
*/
static void
add_aliases(struct hostent *phost)
{
char **aliases;
if (!in_host_array(phost->h_name)) {
add_to_host_array(phost->h_name);
}
if (phost->h_aliases == NULL)
return; /* no aliases to register */
for (aliases = phost->h_aliases; *aliases != NULL; aliases++) {
if (!in_host_array(*aliases)) {
add_to_host_array(*aliases);
}
}
}
/*
* in_host_array checks if the given hostname exists in the host_name
* array. Returns 0 if the host doesn't exist, and 1 if it does exist
*/
static int
in_host_array(char *host)
{
int i;
if (debug)
(void) printf("%s ", host);
if ((strcmp(hostname, host) == 0) || (strcmp(LOGHOST, host) == 0))
return (1);
for (i = 0; i < addrix; i++) {
if (strcmp(host_name[i], host) == 0)
return (1);
}
return (0);
}
/*
* add_to_host_array adds a hostname to the host_name array. But if
* the array is already full, then it first reallocates the array with
* HOST_NAME_INCR extra elements. If the realloc fails, then it does
* nothing and leaves host_name the way it was previous to the call.
*/
static void
add_to_host_array(char *host) {
void *new_block = NULL;
/* Make sure we don't overrun host_name. */
if (addrix >= host_name_count) {
host_name_count += HOST_NAME_INCR;
new_block = realloc((void *)host_name,
host_name_count*sizeof (char *));
if (new_block != NULL)
host_name = new_block;
else {
host_name_count -= HOST_NAME_INCR;
return;
}
}
if ((host_name[addrix] = strdup(host)) != NULL)
addrix++;
}
/*
* Compares the unqualified hostnames for hosts. Returns 0 if the
* names match, and 1 if the names fail to match.
*/
int
str_cmp_unqual_hostname(char *rawname1, char *rawname2)
{
size_t unq_len1, unq_len2;
char *domain;
if (debug) {
(void) printf("str_cmp_unqual: rawname1= %s, rawname2= %s\n",
rawname1, rawname2);
}
unq_len1 = strcspn(rawname1, ".");
unq_len2 = strcspn(rawname2, ".");
domain = strchr(rawname1, '.');
if (domain != NULL) {
if ((strncmp(rawname1, SM_ADDR_IPV4, unq_len1) == 0) ||
(strncmp(rawname1, SM_ADDR_IPV6, unq_len1) == 0))
return (1);
}
if ((unq_len1 == unq_len2) &&
(strncmp(rawname1, rawname2, unq_len1) == 0)) {
return (0);
}
return (1);
}
/*
* Compares <family>.<address-specifier> ASCII names for hosts. Returns
* 0 if the addresses match, and 1 if the addresses fail to match.
* If the args are indeed specifiers, they should look like this:
*
* ipv4.192.9.200.1 or ipv6.::C009:C801
*/
int
str_cmp_address_specifier(char *specifier1, char *specifier2)
{
size_t unq_len1, unq_len2;
char *rawaddr1, *rawaddr2;
int af1, af2, len;
if (debug) {
(void) printf("str_cmp_addr: specifier1= %s, specifier2= %s\n",
specifier1, specifier2);
}
/*
* Verify that:
* 1. The family tokens match;
* 2. The IP addresses following the `.' are legal; and
* 3. These addresses match.
*/
unq_len1 = strcspn(specifier1, ".");
unq_len2 = strcspn(specifier2, ".");
rawaddr1 = strchr(specifier1, '.');
rawaddr2 = strchr(specifier2, '.');
if (strncmp(specifier1, SM_ADDR_IPV4, unq_len1) == 0) {
af1 = AF_INET;
len = 4;
} else if (strncmp(specifier1, SM_ADDR_IPV6, unq_len1) == 0) {
af1 = AF_INET6;
len = 16;
}
else
return (1);
if (strncmp(specifier2, SM_ADDR_IPV4, unq_len2) == 0)
af2 = AF_INET;
else if (strncmp(specifier2, SM_ADDR_IPV6, unq_len2) == 0)
af2 = AF_INET6;
else
return (1);
if (af1 != af2)
return (1);
if (rawaddr1 != NULL && rawaddr2 != NULL) {
char dst1[16];
char dst2[16];
++rawaddr1;
++rawaddr2;
if (inet_pton(af1, rawaddr1, dst1) == 1 &&
inet_pton(af2, rawaddr1, dst2) == 1 &&
memcmp(dst1, dst2, len) == 0) {
return (0);
}
}
return (1);
}
/*
* Add IP address strings to the host_name list.
*/
void
merge_ips(void)
{
struct ifaddrs *ifap, *cifap;
int error;
error = getifaddrs(&ifap);
if (error) {
syslog(LOG_WARNING, "getifaddrs error: '%s'",
strerror(errno));
return;
}
for (cifap = ifap; cifap != NULL; cifap = cifap->ifa_next) {
struct sockaddr *sa = cifap->ifa_addr;
char addr_str[INET6_ADDRSTRLEN];
void *addr = NULL;
switch (sa->sa_family) {
case AF_INET: {
struct sockaddr_in *sin = (struct sockaddr_in *)sa;
/* Skip loopback addresses. */
if (sin->sin_addr.s_addr == htonl(INADDR_LOOPBACK)) {
continue;
}
addr = &sin->sin_addr;
break;
}
case AF_INET6: {
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;
/* Skip loopback addresses. */
if (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr)) {
continue;
}
addr = &sin6->sin6_addr;
break;
}
default:
syslog(LOG_WARNING, "Unknown address family %d for "
"interface %s", sa->sa_family, cifap->ifa_name);
continue;
}
if (inet_ntop(sa->sa_family, addr, addr_str, sizeof (addr_str))
== NULL) {
syslog(LOG_WARNING, "Failed to convert address into "
"string representation for interface '%s' "
"address family %d", cifap->ifa_name,
sa->sa_family);
continue;
}
if (!in_host_array(addr_str)) {
add_to_host_array(addr_str);
}
}
freeifaddrs(ifap);
}