/*
* Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* "Portions Copyright (c) 1999 Apple Computer, Inc. All Rights
* Reserved. This file contains Original Code and/or Modifications of
* Original Code as defined in and that are subject to the Apple Public
* Source License Version 1.0 (the 'License'). You may not use this file
* except in compliance with the License. Please obtain a copy of the
* License at http://www.apple.com/publicsource and read it before using
* this file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the
* License for the specific language governing rights and limitations
* under the License."
*
* @APPLE_LICENSE_HEADER_END@
*/
/*
* Server-side procedure implementation
* Copyright (C) 1989 by NeXT, Inc.
*/
#include <NetInfo/config.h>
#include <NetInfo/project_version.h>
#include "ni_server.h"
#include <NetInfo/mm.h>
#include <NetInfo/system.h>
#include <NetInfo/system_log.h>
#include <NetInfo/network.h>
#include <NetInfo/systhread.h>
#include "ni_globals.h"
#include "ni_notify.h"
#include "ni_dir.h"
#include <NetInfo/socket_lock.h>
#include "alert.h"
#include "getstuff.h"
#include "proxy_pids.h"
#include <sys/socket.h>
#include "sanitycheck.h"
#include "multi_call.h"
#include "psauth.h"
#include "shauth.h"
#include <sys/time.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/dirent.h>
#include <sys/param.h>
#include <sys/signal.h>
#include <sys/stat.h>
#include <rpc/rpc.h>
#include <rpc/pmap_clnt.h>
#include <rpc/pmap_prot.h>
#ifdef _OS_NEXT_
/* Support the old rpcgen, which doesn't append the _svc suffix
* to server side stubs.
*/
#define _ni_ping_2_svc _ni_ping_2
#define _ni_statistics_2_svc _ni_statistics_2
#define _ni_root_2_svc _ni_root_2
#define _ni_self_2_svc _ni_self_2
#define _ni_parent_2_svc _ni_parent_2
#define _ni_children_2_svc _ni_children_2
#define _ni_create_2_svc _ni_create_2
#define _ni_destroy_2_svc _ni_destroy_2
#define _ni_read_2_svc _ni_read_2
#define _ni_write_2_svc _ni_write_2
#define _ni_lookup_2_svc _ni_lookup_2
#define _ni_lookupread_2_svc _ni_lookupread_2
#define _ni_list_2_svc _ni_list_2
#define _ni_listall_2_svc _ni_listall_2
#define _ni_readprop_2_svc _ni_readprop_2
#define _ni_writeprop_2_svc _ni_writeprop_2
#define _ni_listprops_2_svc _ni_listprops_2
#define _ni_createprop_2_svc _ni_createprop_2
#define _ni_destroyprop_2_svc _ni_destroyprop_2
#define _ni_renameprop_2_svc _ni_renameprop_2
#define _ni_createname_2_svc _ni_createname_2
#define _ni_writename_2_svc _ni_writename_2
#define _ni_readname_2_svc _ni_readname_2
#define _ni_destroyname_2_svc _ni_destroyname_2
#define _ni_bind_2_svc _ni_bind_2
#define _ni_rparent_2_svc _ni_rparent_2
#define _ni_crashed_2_svc _ni_crashed_2
#define _ni_readall_2_svc _ni_readall_2
#define _ni_resync_2_svc _ni_resync_2
#endif _OS_NEXT_
CLIENT *svctcp_getclnt(SVCXPRT *xprt);
#define MAXINTSTRSIZE sizeof("4294967296") /* size of largest integer */
#define NI_SEPARATOR '/' /* separator for netinfo values */
/*
* These definitions are used when doing the search for the parent. When
* a timeout occurs because we are unable to find the parent server, we
* sleep for 10 seconds, checking once a second to see if the user decided
* to abort the search. After 30 seconds, the search is continued again.
*/
#ifdef _OS_APPLE_
#define PARENT_NINTERVALS 0
#else
#define PARENT_NINTERVALS 10
#endif
#define PARENT_SLEEPTIME 1
/*
* How long to sleep after catching an rparent reply.
* This reduces a flood of ICMP port unreachable messages.
*/
#define RPARENT_CATCH_SLEEP_SECONDS 2
/*
* Ping timeout is 1/2 second per try, with 3 tries. Total timeout is 2 seconds.
*/
#define PING_TIMEOUT_SEC 0
#define PING_TIMEOUT_USEC 500000
#define PING_CALL_SEC 2
#define PING_CALL_USEC 0
/* Number of minutes to force between attempts to see if we are still root */
#define NETROOT_TIMEOUT_MINS 10
/* Number of times to retry binding after catching a sighup */
#define MAX_REBIND_ATTEMPTS 2
extern bool_t readall();
static int ni_ping(u_long, ni_name);
extern void setproctitle(char *fmt, ...);
extern int have_notifications_pending(void);
extern int ni_svc_connections;
extern int ni_svc_topconnections;
extern int ni_svc_maxconnections;
extern int ni_svc_lruclosed;
/* for getting language */
#define NAME_LOCALCONFIG "localconfig"
#define NAME_LANGUAGE "language"
#define KEY_PARENT_SERVER_ADDR "ServerAddresses"
#define KEY_PARENT_SERVER_TAG "ServerTags"
#define forever for(;;)
typedef enum
{
TokenNULL = 0,
TokenWord = 1,
TokenXMLArrayStart = 2,
TokenXMLArrayEnd = 3,
TokenXMLDictStart = 4,
TokenXMLDictEnd = 5,
TokenXMLKeyStart = 6,
TokenXMLKeyEnd = 7
} token_type_t;
typedef struct
{
token_type_t type;
char *value;
} token_t;
/*
* Is this call an update from the master?
* XXX: the method used is to look for a privileged port from the master
* server. There is no way to distinguish this from a call from a root
* user on the master (versus the master server process).
*/
static int
isupdate(struct svc_req *req)
{
struct sockaddr_in *sin = svc_getcaller(req->rq_xprt);
/*
* XXX: Do not allow the client library as root to look
* like an update. Since the client library always sets AUTH_UNIX,
* and updates never do, we can safely test for AUTH_UNIX.
*/
if (req->rq_cred.oa_flavor == AUTH_UNIX) {
return (FALSE);
}
/*
* XXX: Master could have multiple addresses: should check all of
* them.
*/
return (sin->sin_addr.s_addr == master_addr &&
ntohs(sin->sin_port) < IPPORT_RESERVED);
}
static bool_t
is_admin(void *ni, char *user)
{
ni_namelist nl;
ni_status status;
ni_id id;
ni_idlist idl;
int i;
if (ni == NULL) return FALSE;
if (user == NULL) return FALSE;
/* Find /groups/admin */
status = ni_root(ni, &id);
if (status != NI_OK) return FALSE;
NI_INIT(&idl);
status = ni_lookup(ni, &id, NAME_NAME, NAME_GROUPS, &idl);
if (status != NI_OK) return FALSE;
id.nii_object = idl.ni_idlist_val[0];
ni_idlist_free(&idl);
NI_INIT(&idl);
status = ni_lookup(ni, &id, NAME_NAME, NAME_ADMIN, &idl);
if (status != NI_OK) return FALSE;
id.nii_object = idl.ni_idlist_val[0];
ni_idlist_free(&idl);
/* Check if named user is a member */
NI_INIT(&nl);
status = ni_lookupprop(ni, &id, NAME_USERS, &nl);
if (status != NI_OK) return FALSE;
for (i = 0; i < nl.ni_namelist_len; i++)
{
if (!strcmp(user, nl.ni_namelist_val[i]))
{
ni_namelist_free(&nl);
return TRUE;
}
}
ni_namelist_free(&nl);
return FALSE;
}
/*
* Authenticate a NetInfo call. Only required for write operations.
* NetInfo uses passwords for authentications, but does not send them
* in the clear. Instead, a trivial authentication system is used to
* defeat packet browsers.
*
* UNIX-style RPC authentication is used, with a gross hack that the encrypted
* password is placed in the machine-name field.
*
* XXX: design a better authentication system (non-trivial!)
*/
static ni_status
authenticate(void *ni, struct svc_req *req)
{
struct sockaddr_in *sin = svc_getcaller(req->rq_xprt);
struct authunix_parms *aup;
char *p;
ni_namelist nl, name_nl, nl2;
ni_status status;
ni_id id;
ni_idlist idl;
char uidstr[MAXINTSTRSIZE];
int u, found, try_crypt, hash_check;
/*
* Root on the local machine can do anything
*/
if (sys_is_my_address(&(sin->sin_addr)) && ntohs(sin->sin_port) < IPPORT_RESERVED)
{
ni_setuser(ni, ACCESS_USER_SUPER);
return NI_OK;
}
if (req->rq_cred.oa_flavor != AUTH_UNIX)
{
ni_setuser(ni, NULL);
return NI_OK;
}
aup = (struct authunix_parms *)req->rq_clntcred;
/*
* Pull user-supplied password out of RPC message.
* Our trivial encryption scheme just inverts the bits
*/
for (p = aup->aup_machname; *p; p++) *p = ~(*p);
status = ni_root(ni, &id);
if (status != NI_OK) return status;
/*
* Get /users directory
*/
NI_INIT(&idl);
status = ni_lookup(ni, &id, NAME_NAME, NAME_USERS, &idl);
if (status != NI_OK)
{
system_log(LOG_ERR,
"Cannot authenticate user %d from %s:%hu - no /%s "
"directory: %s", aup->aup_uid,
inet_ntoa(sin->sin_addr), ntohs(sin->sin_port),
NAME_USERS,
ni_error(status));
auth_count[BAD]++;
return ((status == NI_NODIR) ? NI_NOUSER : status);
}
id.nii_object = idl.ni_idlist_val[0];
ni_idlist_free(&idl);
/*
* Find all users with this uid
*/
sprintf(uidstr, "%d", aup->aup_uid);
NI_INIT(&idl);
status = ni_lookup(ni, &id, NAME_UID, uidstr, &idl);
if (status != NI_OK)
{
system_log(LOG_ERR, "Cannot find user %d from %s:%hu: %s",
aup->aup_uid,
inet_ntoa(sin->sin_addr), ntohs(sin->sin_port),
ni_error(status));
auth_count[BAD]++;
return ((status == NI_NODIR) ? NI_NOUSER : status);
}
/*
* Try authenticating each user
*/
found = 0;
for (u = 0; u < idl.ni_idlist_len; u++)
{
id.nii_object = idl.ni_idlist_val[u];
try_crypt = 0;
/* Check for an authentication authority */
NI_INIT(&nl);
status = ni_lookupprop(ni, &id, NAME_AUTHENTICATION_AUTHORITY, &nl);
if ((status == NI_OK) && (nl.ni_namelist_len > 0))
{
if (CheckAuthType(nl.ni_namelist_val[0], BASIC_AUTH_TYPE))
{
try_crypt = 1;
}
else
{
NI_INIT(&name_nl);
status = ni_lookupprop(ni, &id, NAME_NAME, &name_nl);
if ((status == NI_OK) && (name_nl.ni_namelist_len > 0))
{
if (CheckAuthType(nl.ni_namelist_val[0], SHADOWHASH_AUTH_TYPE))
{
NI_INIT(&nl2);
status = ni_lookupprop(ni, &id, NAME_GUID, &nl2);
if ((status == NI_OK) && (nl2.ni_namelist_len > 0))
{
hash_check = DoSHAuth(name_nl.ni_namelist_val[0], aup->aup_machname, nl2.ni_namelist_val[0]);
ni_namelist_free(&nl2);
}
else
{
hash_check = DoSHAuth(name_nl.ni_namelist_val[0], aup->aup_machname, NULL);
}
if (hash_check == 0)
{
found = 1;
ni_namelist_free(&name_nl);
ni_namelist_free(&nl);
break;
}
ni_namelist_free(&name_nl);
}
else
{
if (DoPSAuth(name_nl.ni_namelist_val[0], aup->aup_machname, nl.ni_namelist_val[0]) == 0)
{
found = 1;
ni_namelist_free(&name_nl);
ni_namelist_free(&nl);
break;
}
ni_namelist_free(&name_nl);
}
}
}
ni_namelist_free(&nl);
}
else
{
try_crypt = 1;
}
if (try_crypt == 1)
{
NI_INIT(&nl);
status = ni_lookupprop(ni, &id, NAME_PASSWD, &nl);
if (status == NI_OK)
{
if ((nl.ni_namelist_len == 0) || (nl.ni_namelist_val[0][0] == '\0'))
{
/*
* Free Parking: user has no password
*/
found = 1;
ni_namelist_free(&nl);
break;
}
if (!strcmp(nl.ni_namelist_val[0], crypt(aup->aup_machname, nl.ni_namelist_val[0])) != 0)
{
/*
* Password match
*/
found = 1;
ni_namelist_free(&nl);
break;
}
}
ni_namelist_free(&nl);
}
}
ni_idlist_free(&idl);
if (!found)
{
/*
* No user with this uid with no password or a matching password
*/
system_log(LOG_ERR, "Authentication error for user "
"%d from %s:%hu",
aup->aup_uid,
inet_ntoa(sin->sin_addr), ntohs(sin->sin_port),
ni_error(status));
auth_count[BAD]++;
return NI_AUTHERROR;
}
NI_INIT(&nl);
status = ni_lookupprop(ni, &id, NAME_NAME, &nl);
if (status != NI_OK)
{
system_log(LOG_ERR,
"User %d from %s:%hu - name prop not found during "
"authentication",
aup->aup_uid,
inet_ntoa(sin->sin_addr), ntohs(sin->sin_port),
ni_error(status));
auth_count[BAD]++;
return ((status == NI_NOPROP) ? NI_NOUSER : status);
}
if (nl.ni_namelist_len == 0)
{
system_log(LOG_ERR,
"User %d from %s:%hu - name value not found during "
"authentication",
aup->aup_uid,
inet_ntoa(sin->sin_addr), ntohs(sin->sin_port));
auth_count[BAD]++;
return NI_NOUSER;
}
/*
* If the user has uid 0, allow root access.
* Also allow root access if we promote admin users, and this user
* is an admin (member of the "admin" group).
*/
promote_admins = get_promote_admins(ni);
if (aup->aup_uid == 0)
{
ni_setuser(ni, ACCESS_USER_SUPER);
}
else if (promote_admins && is_admin(ni, nl.ni_namelist_val[0]))
{
ni_setuser(ni, ACCESS_USER_SUPER);
}
else
{
ni_setuser(ni, nl.ni_namelist_val[0]);
}
auth_count[GOOD]++;
system_log(LOG_NOTICE,
"Authenticated user %s [%d] from %s:%hu",
nl.ni_namelist_val[0], aup->aup_uid,
inet_ntoa(sin->sin_addr), ntohs(sin->sin_port));
ni_namelist_free(&nl);
return NI_OK;
}
/*
* Validate that a write-call is allowed. A write call is allowed
* to the master if the user is correctly authenticated and permission
* is allowed. To the clone, the write is only allowed if it comes
* from the master (an update).
*/
static ni_status
validate_write(struct svc_req *req)
{
ni_status status;
status = NI_OK;
ni_setuser(db_ni, NULL);
if (i_am_clone)
{
if (!isupdate(req)) status = NI_RDONLY;
else ni_setuser(db_ni, ACCESS_USER_SUPER);
}
else
{
status = authenticate(db_ni, req);
}
/*
* Do master side of readall in separate process.
* We need to lock out modifications during a readall, to
* avoid just needing another one far too soon. Since all
* the modifcations come through here, this is a good place
* to enforce things.
*/
if ((sending_all > 0) || db_lockup) {
status = NI_MASTERBUSY;
}
return (status);
}
static void
add_peer_servers(ni_entry entry, ni_namelist *sl)
{
ni_name sep;
ni_namelist nl;
ni_index i, j, taglen;
ni_id id;
char *s;
if (entry.names == NULL) return;
if (sl == NULL) return;
id.nii_object = entry.id;
for (i = 0; i < entry.names->ni_namelist_len; i++)
{
if (strncmp(entry.names->ni_namelist_val[i], "./", 2)) continue;
sep = entry.names->ni_namelist_val[i] + 1;
taglen = strlen(sep);
if (taglen == 0) continue;
NI_INIT(&nl);
if (ni_lookupprop(db_ni, &id, NAME_IP_ADDRESS, &nl) != NI_OK) continue;
for (j = 0; j < nl.ni_namelist_len; j++)
{
s = malloc(strlen(nl.ni_namelist_val[j]) + taglen + 1);
sprintf(s, "%s%s", nl.ni_namelist_val[j], sep);
ni_namelist_insert(sl, s, NI_INDEX_NULL);
free(s);
}
ni_namelist_free(&nl);
}
}
static ni_status
get_servers_from_db(void *ni, ni_namelist *sl)
{
ni_id id;
ni_idlist ids;
ni_entrylist entries;
int i;
ni_status status;
status = ni_root(ni, &id);
if (status != NI_OK) return status;
NI_INIT(&ids);
status = ni_lookup(ni, &id, NAME_NAME, NAME_MACHINES, &ids);
if (status != NI_OK) return status;
/* list "serves" properties in /machines */
id.nii_object = ids.ni_idlist_val[0];
ni_idlist_free(&ids);
NI_INIT(&entries);
status = ni_list_const(ni, &id, NAME_SERVES, &entries);
if (status != NI_OK) return status;
for (i = 0; i < entries.ni_entrylist_len; i++)
{
add_peer_servers(entries.ni_entrylist_val[i], sl);
}
ni_list_const_free(ni);
return NI_OK;
}
/*
* The NetInfo PING procedure
*/
void *
_ni_ping_2_svc(
void *arg,
struct svc_req *req
)
{
if (req == NULL) return ((void *)~0);
// system_log(LOG_DEBUG, "ni_ping on connection %u", req->rq_xprt->xp_sock);
return ((void *)~0);
}
/*
* The NetInfo statistics procedure
*/
ni_proplist *
_ni_statistics_2_svc(
void *arg,
struct svc_req *req
)
{
/*
* Statistics are updated in ni_prog_2_svc() in ni_prot_svc.c,
* which lives in SYMS/netinfod_syms.
*
* XXX This definition really belongs in ni.x, but we're not
* going to muck with the protocol definition file at this time.
* This definition must be shared with ni_prot_svc.c
*/
extern struct ni_stats
{
unsigned long ncalls;
unsigned long time;
} netinfod_stats[];
#define STATS_PROCNUM 0
#define STATS_NCALLS 1
#define STATS_TIME 2
#define N_STATS_VALS (STATS_TIME+1)
int total_calls = 0; /* We'll total things here */
interface_list_t *ifaces;
struct timeval now;
extern char *procname(int);
/*
* Sizes of values, excluding call stats
* Note: for properties with multiple values the size should reflect the
* max space required for any one value.
*/
static int props_sizes[] =
{
MAXINTSTRSIZE, /* checksum: */
128, /* server_version: */
NI_NAME_MAXLEN+1, /* tag: max(NI_NAME_MAXLEN+1, 7, MAXINTSTRSIZE) */
16, /* ip_address(es): "xxx.yyy.zzz.www" */
MAXHOSTNAMELEN+1, /* hostname: */
16+1+MAXNAMLEN+1, /* servers: "xxx.yyy.zzz.www/tag" */
MAXINTSTRSIZE+17, /* write_locked: strlen(SENDING_ALLn_STG)+MAXINTSTRSIZE */
MAXINTSTRSIZE, /* notify_threads: 3 of MAXINTSTRSIZE */
MAXINTSTRSIZE, /* notifications_pending: */
MAXINTSTRSIZE, /* authentications: 4 of MAXINTRSTRSIZE */
MAXINTSTRSIZE, /* readall_proxies: max(MAXINTSTRSIZE, "strict"|"loose}) */
MAXINTSTRSIZE, /* cleanup_wait: */
MAXINTSTRSIZE, /* total_calls: */
16+1+MAXNAMLEN+1, /* binding: "xxx.yyy.zzz.www/tag" */
MAXINTSTRSIZE /* connections: 4 of MAXINTSTRSIZE */
};
#define wProps(i, fmt, val) wPropsN(i,0, fmt, val)
#define wPropsN(i, j, fmt, val) sprintf(props[i].nip_val.ni_namelist_val[j], fmt, val)
static ni_property props[] =
{
{"checksum", {1, NULL}},
#define P_CHECKSUM 0
{"server_version", {1, NULL}},
#define P_VERSION 1
/*
* tag can have two or three values. If master, there'll
* be 3; if clone, 2. We'll allocate space for 3, to
* simplify the code.
*/
{"tag", {3, NULL}}, /* tag; master & #clones, or clone */
#define P_TAG 2
#define STATS_TAG 0
#define STATS_MASTER 1
#define STATS_NCLONES 2
{"ip_address", {16, NULL}},
#define P_ADDR 3
{"hostname", {1, NULL}},
#define P_HOST 4
{"domain_servers", {0, NULL}},
#define P_SERVERS 5
{"write_locked", {1, NULL}},
#define P_LOCKED 6
#define SENDING_ALL1_STG "Yes (a readall)"
#define SENDING_ALLn_STG "Yes (%u readalls)"
#define READING_ALL_STG "clone%s"
{"notify_threads", {3, NULL}}, /* current, max, latency */
#define P_THREADS 7
#define STATS_THREADS_USED 0
#define STATS_THREADS_MAX 1
#define STATS_THREADS_LATENCY 2
{"notifications_pending", {1, NULL}},
#define P_PENDING 8
{"authentications", {4, NULL}}, /* {user,dir}:{good,bad} */
#define P_AUTHS 9
{"readall_proxies", {2, NULL}}, /* max, strict */
#define P_PROXIES 10
#define STATS_PROXIES 0
#define STATS_PROXIES_STRICT 1
{"cleanup_wait", {2, NULL}}, /* minutes (!), remaining */
#define P_CLEANUP 11
#define STATS_CLEANUPTIME 0
#define STATS_CLEANUP_TOGO 1
{"total_calls", {1, NULL}},
#define P_CALLS 12
{"binding", {1, NULL}}, /* unknown, notResponding, addr/tag, root, forcedRoot */
#define P_BINDING 13
{"connections", {4, NULL}}, /* current, top, max, lru-closed */
#define P_CONNECTIONS 14
#define STATS_CONNECTIONS_USED 0
#define STATS_CONNECTIONS_TOP 1
#define STATS_CONNECTIONS_MAX 2
#define STATS_CONNECTIONS_CLOSED 3
/*
* 3 values of following properties:
* procnum, ncalls, time (usec)
*/
#define PROC_STATS_START 15
{NULL, {N_STATS_VALS, NULL}}, /* 0: ping */
{NULL, {N_STATS_VALS, NULL}},
{NULL, {N_STATS_VALS, NULL}},
{NULL, {N_STATS_VALS, NULL}},
{NULL, {N_STATS_VALS, NULL}},
{NULL, {N_STATS_VALS, NULL}},
{NULL, {N_STATS_VALS, NULL}},
{NULL, {N_STATS_VALS, NULL}},
{NULL, {N_STATS_VALS, NULL}},
{NULL, {N_STATS_VALS, NULL}},
{NULL, {N_STATS_VALS, NULL}}, /* 10: lookup */
{NULL, {N_STATS_VALS, NULL}},
{NULL, {N_STATS_VALS, NULL}},
{NULL, {N_STATS_VALS, NULL}},
{NULL, {N_STATS_VALS, NULL}},
{NULL, {N_STATS_VALS, NULL}},
{NULL, {N_STATS_VALS, NULL}},
{NULL, {N_STATS_VALS, NULL}},
{NULL, {N_STATS_VALS, NULL}},
{NULL, {N_STATS_VALS, NULL}},
{NULL, {N_STATS_VALS, NULL}}, /* 20: readname */
{NULL, {N_STATS_VALS, NULL}},
{NULL, {N_STATS_VALS, NULL}},
{NULL, {N_STATS_VALS, NULL}},
{NULL, {N_STATS_VALS, NULL}},
{NULL, {N_STATS_VALS, NULL}},
{NULL, {N_STATS_VALS, NULL}},
{NULL, {N_STATS_VALS, NULL}},
{NULL, {N_STATS_VALS, NULL}} /* 28: lookupread */
};
int i, j;
unsigned current_checksum;
static unsigned char first_time = TRUE;
static unsigned last_checksum = 0;
static ni_proplist res;
if (req == NULL) return NULL;
// system_log(LOG_DEBUG, "ni_statistics on connection %u", req->rq_xprt->xp_sock);
if (first_time)
{
/* Save some typing (and static memory)... */
first_time = FALSE;
for (i = 0; i < PROC_STATS_START; i++)
{
/* Initialize the props other than call stats and server list */
if (i == P_SERVERS) continue;
props[i].nip_val.ni_namelist_val = (ni_name *)malloc(props[i].nip_val.ni_namelist_len * sizeof(ni_name));
if (props[i].nip_val.ni_namelist_val == NULL)
{
system_log(LOG_ALERT, "Couldn't allocate memory for statistics");
system_log(LOG_ALERT, "Aborting!");
abort();
}
for (j = 0; j < props[i].nip_val.ni_namelist_len; j++)
{
props[i].nip_val.ni_namelist_val[j] = (ni_name)malloc(props_sizes[i]);
if (props[i].nip_val.ni_namelist_val[j] == NULL)
{
system_log(LOG_ALERT, "Couldn't allocate memory for statistics");
system_log(LOG_ALERT, "Aborting!");
abort();
}
}
}
for (i = PROC_STATS_START; i <= PROC_STATS_START+_NI_LOOKUPREAD; i++)
{
/* Initialize the call stats */
props[i].nip_name = procname(i - PROC_STATS_START);
props[i].nip_val.ni_namelist_val = (ni_name *)malloc(sizeof(ni_name) * N_STATS_VALS);
if (props[i].nip_val.ni_namelist_val == NULL)
{
system_log(LOG_ALERT, "Couldn't allocate memory for statistics");
system_log(LOG_ALERT, "Aborting!");
abort();
}
for (j = 0; j < N_STATS_VALS; j++)
{
props[i].nip_val.ni_namelist_val[j] = (ni_name)malloc(MAXINTSTRSIZE+1);
if (props[i].nip_val.ni_namelist_val[j] == NULL)
{
system_log(LOG_ALERT, "Couldn't allocate memory for statistics");
system_log(LOG_ALERT, "Aborting!");
abort();
}
}
/* Set up the (static) procedure number */
wPropsN(i, STATS_PROCNUM, "%u", i - PROC_STATS_START);
}
/* generate static information */
/* server_version */
wProps(P_VERSION, "%s", _PROJECT_VERSION_);
/* tag: tag, master (w/# clones) or clone */
wPropsN(P_TAG, STATS_TAG, "%s", db_tag);
if (!i_am_clone)
{
wPropsN(P_TAG, STATS_MASTER, "%s", "master");
props[P_TAG].nip_val.ni_namelist_len = 3;
}
else
{
wPropsN(P_TAG, STATS_MASTER, "%s", "clone");
props[P_TAG].nip_val.ni_namelist_len = 2;
}
/* hostname */
wProps(P_HOST, "%s", sys_hostname());
}
/* checksum */
current_checksum = ni_getchecksum(db_ni);
wProps(P_CHECKSUM, "%u", current_checksum);
/* identify whether the extended statistics should be returned */
if (req->rq_cred.oa_flavor == AUTH_UNIX)
{
struct authunix_parms *aup;
char *p;
aup = (struct authunix_parms *)req->rq_clntcred;
/*
* Pull the user-supplied password out of RPC message
* and undo the (trivial) encryption scheme.
*/
for (p = aup->aup_machname; *p; p++) *p = ~(*p);
if (strcmp(aup->aup_machname, "checksum") == 0)
{
res.ni_proplist_len = 1; /* assumes "checksum" is first property */
res.ni_proplist_val = props;
return (&res);
}
}
/* tag: tag, master (w/# clones) or clone */
if (!i_am_clone)
{
wPropsN(P_TAG, STATS_NCLONES, "%d", count_clones());
}
/* ip_address(es) */
ifaces = sys_interfaces();
for (i = 0, j = 0; ifaces && (i < ifaces->count) && (j < 16); i++)
{
if ((ifaces->interface[i].flags & IFF_UP) == 0) continue;
if (ifaces->interface[i].flags & IFF_LOOPBACK) continue;
wPropsN(P_ADDR, j++, "%s", inet_ntoa(ifaces->interface[i].addr));
}
sys_interfaces_release(ifaces);
if (j == 0)
{
props[P_ADDR].nip_val.ni_namelist_len = 1;
wPropsN(P_ADDR, j++, "%s", "0.0.0.0");
}
props[P_ADDR].nip_val.ni_namelist_len = j;
/* server list is refreshed if the checksum has changed */
if (current_checksum != last_checksum)
{
ni_namelist_free(&(props[P_SERVERS].nip_val));
get_servers_from_db(db_ni, &(props[P_SERVERS].nip_val));
last_checksum = current_checksum;
}
/* write_locked */
if (i_am_clone)
{
wProps(P_LOCKED, READING_ALL_STG, reading_all ? " (reading all)" : "");
}
else if (sending_all > 0)
{
wProps(P_LOCKED, 1 == sending_all ? SENDING_ALL1_STG : SENDING_ALLn_STG, sending_all);
}
else if (db_lockup)
{
/* If this is longer than SENDING_ALLn_STG, change props_sizes */
wProps(P_LOCKED, "%s", "Yes (due to SIGINT)");
}
else
{
wProps(P_LOCKED, "%s", "No");
}
/* notify_threads: current, max, latency */
wPropsN(P_THREADS, STATS_THREADS_USED, "%u", count_notify_subthreads());
wPropsN(P_THREADS, STATS_THREADS_MAX, "%u", max_subthreads);
wPropsN(P_THREADS, STATS_THREADS_LATENCY, "%u", update_latency_secs);
/* notifications_pending */
wProps(P_PENDING, "%u", notifications_pending());
/* Authentications, good and bad */
wPropsN(P_AUTHS, GOOD, "%u", auth_count[GOOD]);
wPropsN(P_AUTHS, BAD, "%u", auth_count[BAD]);
wPropsN(P_AUTHS, WGOOD, "%u", auth_count[WGOOD]);
wPropsN(P_AUTHS, WBAD, "%u", auth_count[WBAD]);
/* readall_proxies: max, strict */
wPropsN(P_PROXIES, STATS_PROXIES, "%d", max_readall_proxies);
wPropsN(P_PROXIES, STATS_PROXIES_STRICT, "%s", strict_proxies ? "strict" : "loose");
/* cleanup_wait (in seconds internally, in minutes externally!) */
gettimeofday(&now, NULL);
wPropsN(P_CLEANUP, STATS_CLEANUPTIME, "%d", cleanupwait/60);
#ifdef _OS_NEXT_
wPropsN(P_CLEANUP, STATS_CLEANUP_TOGO, "%ld", cleanupwait < 0 ? -1 : (cleanuptime - now.tv_sec)/60);
#else
wPropsN(P_CLEANUP, STATS_CLEANUP_TOGO, "%ld", cleanupwait < 0 ? -1 : (cleanuptime - now.tv_sec)/60);
#endif
/* current binding status */
if (forcedIsRoot)
{
wProps(P_BINDING, "%s", "forcedRoot");
}
else
{
switch (get_binding_status())
{
case NI_NORESPONSE:
wProps(P_BINDING, "%s", "notResponding");
break;
case NI_NETROOT:
wProps(P_BINDING, "%s", "root");
break;
case NI_OK:
wProps(P_BINDING, "%s", latestParentInfo);
break;
case NI_NOTMASTER:
default:
wProps(P_BINDING, "%s", "unknown");
break;
}
}
/* connections: current, max, lru-closed */
wPropsN(P_CONNECTIONS, STATS_CONNECTIONS_USED, "%d", ni_svc_connections);
wPropsN(P_CONNECTIONS, STATS_CONNECTIONS_TOP, "%d", ni_svc_topconnections);
wPropsN(P_CONNECTIONS, STATS_CONNECTIONS_MAX, "%d", ni_svc_maxconnections);
wPropsN(P_CONNECTIONS, STATS_CONNECTIONS_CLOSED, "%d", ni_svc_lruclosed);
/* We'll return to total_calls later */
/* Loop through the call stats. */
for (i = PROC_STATS_START; i <= PROC_STATS_START + _NI_LOOKUPREAD; i++)
{
wPropsN(i, STATS_NCALLS, "%lu", netinfod_stats[i - PROC_STATS_START].ncalls);
wPropsN(i, STATS_TIME, "%lu", netinfod_stats[i - PROC_STATS_START].time);
total_calls += netinfod_stats[i - PROC_STATS_START].ncalls;
}
wProps(P_CALLS, "%u", total_calls);
res.ni_proplist_len = PROC_STATS_START+(_NI_LOOKUPREAD+1);
res.ni_proplist_val = props;
return (&res);
}
/*
* The NetInfo ROOT procedure
*/
ni_id_res *
_ni_root_2_svc(
void *arg,
struct svc_req *req
)
{
static ni_id_res res;
if (req == NULL) {
return (NULL);
}
res.status = ni_root(db_ni, &res.ni_id_res_u.id);
return (&res);
}
/*
* The NetInfo SELF procedure
*/
ni_id_res *
_ni_self_2_svc(
ni_id *arg,
struct svc_req *req
)
{
static ni_id_res res;
if (req == NULL) {
return (NULL);
}
res.ni_id_res_u.id = *arg;
res.status = ni_self(db_ni, &res.ni_id_res_u.id);
return (&res);
}
/*
* The NetInfo PARENT procedure
*/
ni_parent_res *
_ni_parent_2_svc(
ni_id *arg,
struct svc_req *req
)
{
static ni_parent_res res;
if (req == NULL) {
return (NULL);
}
res.ni_parent_res_u.stuff.self_id = *arg;
res.status = ni_parent(db_ni, &res.ni_parent_res_u.stuff.self_id,
&res.ni_parent_res_u.stuff.object_id);
return (&res);
}
/*
* The NetInfo CHILDREN procedure
*/
ni_children_res *
_ni_children_2_svc(
ni_id *arg,
struct svc_req *req
)
{
static ni_children_res res;
ni_idlist_free(&res.ni_children_res_u.stuff.children);
if (req == NULL) {
return (NULL);
}
res.ni_children_res_u.stuff.self_id = *arg;
res.status = ni_children(db_ni, &res.ni_children_res_u.stuff.self_id,
&res.ni_children_res_u.stuff.children);
return (&res);
}
/*
* The NetInfo CREATE procedure
*/
ni_create_res *
_ni_create_2_svc(
ni_create_args *arg,
struct svc_req *req
)
{
static ni_create_res res;
if (req == NULL) {
return (NULL);
}
res.status = validate_write(req);
if (res.status != NI_OK) {
return (&res);
}
if (arg->target_id != NULL) {
res.ni_create_res_u.stuff.id = *arg->target_id;
} else {
res.ni_create_res_u.stuff.id.nii_object = NI_INDEX_NULL;
}
res.ni_create_res_u.stuff.self_id = arg->id;
res.status = ni_create(db_ni, &res.ni_create_res_u.stuff.self_id,
arg->props, &res.ni_create_res_u.stuff.id,
arg->where);
if (res.status == NI_OK) {
if (!i_am_clone) {
if (arg->target_id == NULL) {
MM_ALLOC(arg->target_id);
*arg->target_id = res.ni_create_res_u.stuff.id;
}
notify_clients(_NI_CREATE, arg);
}
} else if (i_am_clone) {
dir_clonecheck();
}
return (&res);
}
/*
* The NetInfo DESTROY procedure
*/
ni_id_res *
_ni_destroy_2_svc(
ni_destroy_args *arg,
struct svc_req *req
)
{
static ni_id_res res;
if (req == NULL) {
return (NULL);
}
res.status = validate_write(req);
if (res.status != NI_OK) {
return (&res);
}
res.ni_id_res_u.id = arg->parent_id;
res.status = ni_destroy(db_ni, &res.ni_id_res_u.id, arg->self_id);
if (res.status == NI_OK) {
if (!i_am_clone) {
notify_clients(_NI_DESTROY, arg);
}
} else if (i_am_clone) {
dir_clonecheck();
}
return (&res);
}
/*
* The NetInfo READ procedure
*/
ni_proplist_res *
_ni_read_2_svc(
ni_id *arg,
struct svc_req *req
)
{
static ni_proplist_res res;
ni_proplist_free(&res.ni_proplist_res_u.stuff.props);
if (req == NULL) {
return (NULL);
}
res.ni_proplist_res_u.stuff.id = *arg;
res.status = ni_read(db_ni, &res.ni_proplist_res_u.stuff.id,
&res.ni_proplist_res_u.stuff.props);
return (&res);
}
/*
* The NetInfo WRITE procedure
*/
ni_id_res *
_ni_write_2_svc(
ni_proplist_stuff *arg,
struct svc_req *req
)
{
static ni_id_res res;
if (req == NULL) {
return (NULL);
}
res.status = validate_write(req);
if (res.status != NI_OK) {
return (&res);
}
res.ni_id_res_u.id = arg->id;
res.status = ni_write(db_ni, &res.ni_id_res_u.id, arg->props);
if (res.status == NI_OK) {
if (!i_am_clone) {
notify_clients(_NI_WRITE, arg);
}
} else if (i_am_clone) {
dir_clonecheck();
}
return (&res);
}
/*
* The NetInfo LOOKUP procedure
*/
ni_lookup_res *
_ni_lookup_2_svc(
ni_lookup_args *arg,
struct svc_req *req
)
{
static ni_lookup_res res;
ni_idlist_free(&res.ni_lookup_res_u.stuff.idlist);
if (req == NULL) {
return (NULL);
}
res.ni_lookup_res_u.stuff.self_id = arg->id;
NI_INIT(&res.ni_lookup_res_u.stuff.idlist);
res.status = ni_lookup(db_ni, &res.ni_lookup_res_u.stuff.self_id,
arg->key, arg->value,
&res.ni_lookup_res_u.stuff.idlist);
return (&res);
}
/*
* The NetInfo LOOKUPREAD procedure
*/
ni_proplist_res *
_ni_lookupread_2_svc(
ni_lookup_args *arg,
struct svc_req *req
)
{
static ni_proplist_res res;
ni_proplist_free(&res.ni_proplist_res_u.stuff.props);
if (req == NULL) {
return (NULL);
}
res.ni_proplist_res_u.stuff.id = arg->id;
res.status = ni_lookupread(db_ni,
&res.ni_proplist_res_u.stuff.id,
arg->key, arg->value,
&res.ni_proplist_res_u.stuff.props);
return (&res);
}
/*
* The NetInfo LIST procedure
*/
ni_list_res *
_ni_list_2_svc(
ni_name_args *arg,
struct svc_req *req
)
{
static ni_list_res res;
if (req == NULL) {
ni_list_const_free(db_ni);
return (NULL);
}
res.ni_list_res_u.stuff.self_id = arg->id;
res.status = ni_list_const(db_ni, &res.ni_list_res_u.stuff.self_id,
arg->name,
&res.ni_list_res_u.stuff.entries);
return (&res);
}
/*
* WARNING: this function is dangerous and may be removed in future
* implementations of the protocol.
* While it is easier on the network, it eats up too much time on the
* server and interrupts service for others.
*
* PLEASE DO NOT CALL IT!!!
*/
ni_listall_res *
_ni_listall_2_svc(
ni_id *id,
struct svc_req *req
)
{
static ni_listall_res res;
ni_proplist_list_free(&res.ni_listall_res_u.stuff.entries);
if (req == NULL) {
return (NULL);
}
res.ni_listall_res_u.stuff.self_id = *id;
res.status = ni_listall(db_ni, &res.ni_listall_res_u.stuff.self_id,
&res.ni_listall_res_u.stuff.entries);
return (&res);
}
/*
* The NetInfo READPROP procedure
*/
ni_namelist_res *
_ni_readprop_2_svc(
ni_prop_args *arg,
struct svc_req *req
)
{
static ni_namelist_res res;
ni_namelist_free(&res.ni_namelist_res_u.stuff.values);
if (req == NULL) {
return (NULL);
}
res.ni_namelist_res_u.stuff.self_id = arg->id;
res.status = ni_readprop(db_ni, &res.ni_namelist_res_u.stuff.self_id,
arg->prop_index,
&res.ni_namelist_res_u.stuff.values);
return (&res);
}
/*
* The NetInfo WRITEPROP procedure
*/
ni_id_res *
_ni_writeprop_2_svc(
ni_writeprop_args *arg,
struct svc_req *req
)
{
static ni_id_res res;
if (req == NULL) {
return (NULL);
}
res.status = validate_write(req);
if (res.status != NI_OK) {
return (&res);
}
res.ni_id_res_u.id = arg->id;
res.status = ni_writeprop(db_ni, &res.ni_id_res_u.id,
arg->prop_index,
arg->values);
if (res.status == NI_OK) {
if (!i_am_clone) {
notify_clients(_NI_WRITEPROP, arg);
}
} else if (i_am_clone) {
dir_clonecheck();
}
return (&res);
}
/*
* The NetInfo LISTPROPS procedure
*/
ni_namelist_res *
_ni_listprops_2_svc(
ni_id *arg,
struct svc_req *req
)
{
static ni_namelist_res res;
ni_namelist_free(&res.ni_namelist_res_u.stuff.values);
if (req == NULL) {
return (NULL);
}
res.ni_namelist_res_u.stuff.self_id = *arg;
res.status = ni_listprops(db_ni, &res.ni_namelist_res_u.stuff.self_id,
&res.ni_namelist_res_u.stuff.values);
return (&res);
}
/*
* The NetInfo CREATEPROP procedure
*/
ni_id_res *
_ni_createprop_2_svc(
ni_createprop_args *arg,
struct svc_req *req
)
{
static ni_id_res res;
if (req == NULL) {
return (NULL);
}
res.status = validate_write(req);
if (res.status != NI_OK) {
return (&res);
}
res.ni_id_res_u.id = arg->id;
res.status = ni_createprop(db_ni, &res.ni_id_res_u.id, arg->prop, arg->where);
if (res.status == NI_OK) {
if (!i_am_clone) {
notify_clients(_NI_CREATEPROP, arg);
}
} else if (i_am_clone) {
dir_clonecheck();
}
return (&res);
}
/*
* The NetInfo DESTROYPROP procedure
*/
ni_id_res *
_ni_destroyprop_2_svc(
ni_prop_args *arg,
struct svc_req *req
)
{
static ni_id_res res;
if (req == NULL) {
return (NULL);
}
res.status = validate_write(req);
if (res.status != NI_OK) {
return (&res);
}
res.ni_id_res_u.id = arg->id;
res.status = ni_destroyprop(db_ni, &res.ni_id_res_u.id, arg->prop_index);
if (res.status == NI_OK) {
if (!i_am_clone) {
notify_clients(_NI_DESTROYPROP, arg);
}
} else if (i_am_clone) {
dir_clonecheck();
}
return (&res);
}
/*
* The NetInfo RENAMEPROP procedure
*/
ni_id_res *
_ni_renameprop_2_svc(
ni_propname_args *arg,
struct svc_req *req
)
{
static ni_id_res res;
if (req == NULL) {
return (NULL);
}
res.status = validate_write(req);
if (res.status != NI_OK) {
return (&res);
}
res.ni_id_res_u.id = arg->id;
res.status = ni_renameprop(db_ni, &res.ni_id_res_u.id,
arg->prop_index, arg->name);
if (res.status == NI_OK) {
if (!i_am_clone) {
notify_clients(_NI_RENAMEPROP, arg);
}
} else if (i_am_clone) {
dir_clonecheck();
}
return (&res);
}
/*
* The NetInfo CREATENAME procedure
*/
ni_id_res *
_ni_createname_2_svc(
ni_createname_args *arg,
struct svc_req *req
)
{
static ni_id_res res;
if (req == NULL) {
return (NULL);
}
res.status = validate_write(req);
if (res.status != NI_OK) {
return (&res);
}
res.ni_id_res_u.id = arg->id;
res.status = ni_createname(db_ni, &res.ni_id_res_u.id, arg->prop_index, arg->name,
arg->where);
if (res.status == NI_OK) {
if (!i_am_clone) {
notify_clients(_NI_CREATENAME, arg);
}
} else if (i_am_clone) {
dir_clonecheck();
}
return (&res);
}
/*
* The NetInfo WRITENAME procedure
*/
ni_id_res *
_ni_writename_2_svc(
ni_writename_args *arg,
struct svc_req *req
)
{
static ni_id_res res;
if (req == NULL) {
return (NULL);
}
res.status = validate_write(req);
if (res.status != NI_OK) {
return (&res);
}
res.ni_id_res_u.id = arg->id;
res.status = ni_writename(db_ni, &res.ni_id_res_u.id,
arg->prop_index, arg->name_index,
arg->name);
if (res.status == NI_OK) {
if (!i_am_clone) {
notify_clients(_NI_WRITENAME, arg);
}
} else if (i_am_clone) {
dir_clonecheck();
}
return (&res);
}
/*
* The NetInfo READNAME procedure
*/
ni_readname_res *
_ni_readname_2_svc(
ni_nameindex_args *arg,
struct svc_req *req
)
{
static ni_readname_res res;
ni_name_free(&res.ni_readname_res_u.stuff.name);
if (req == NULL) {
return (NULL);
}
res.ni_readname_res_u.stuff.id = arg->id;
res.status = ni_readname(db_ni, &res.ni_readname_res_u.stuff.id,
arg->prop_index, arg->name_index,
&res.ni_readname_res_u.stuff.name);
return (&res);
}
/*
* The NetInfo DESTROYNAME procedure
*/
ni_id_res *
_ni_destroyname_2_svc(
ni_nameindex_args *arg,
struct svc_req *req
)
{
static ni_id_res res;
if (req == NULL) {
return (NULL);
}
res.status = validate_write(req);
if (res.status != NI_OK) {
return (&res);
}
res.ni_id_res_u.id = arg->id;
res.status = ni_destroyname(db_ni, &res.ni_id_res_u.id,
arg->prop_index, arg->name_index);
if (res.status == NI_OK) {
if (!i_am_clone) {
notify_clients(_NI_DESTROYNAME, arg);
}
} else if (i_am_clone) {
dir_clonecheck();
}
return (&res);
}
/*
* Given a NetInfo "serves" value (contains a slash character), does
* the given tag match the field on the right of the value?
*/
static int
tag_match(
ni_name slashtag,
ni_name tag
)
{
ni_name sep;
int len;
sep = index(slashtag, NI_SEPARATOR);
if (sep == NULL) {
return (0);
}
if (!ni_name_match(sep + 1, tag)) {
return (0);
}
/*
* Ignore special values "." and ".."
*/
len = sep - slashtag;
if (ni_name_match_seg(NAME_DOT, slashtag, len) ||
ni_name_match_seg(NAME_DOTDOT, slashtag, len)) {
return (0);
}
return (1);
}
/*
* The NetInfo BIND procedure.
*
* Only reply if served to avoid creating unnecessary net traffic. This
* is implemented by returned ~NULL if served, NULL otherwise (an rpcgen
* convention).
*/
void *
_ni_bind_2_svc(
ni_binding *binding,
struct svc_req *req
)
{
ni_id id;
ni_idlist ids;
ni_namelist nl;
ni_index i;
ni_name address;
struct in_addr inaddr;
ni_status status;
if (req == NULL) return (NULL);
if (db_ni == NULL)
{
/* Report an RPC error so the caller doesn't need to time out */
svcerr_systemerr(req->rq_xprt);
return (NULL);
}
if (ni_root(db_ni, &id) != NI_OK)
{
svcerr_systemerr(req->rq_xprt);
return (NULL);
}
NI_INIT(&ids);
status = ni_lookup(db_ni, &id, NAME_NAME, NAME_MACHINES, &ids);
if (status!= NI_OK)
{
svcerr_systemerr(req->rq_xprt);
return (NULL);
}
id.nii_object = ids.ni_idlist_val[0];
ni_idlist_free(&ids);
inaddr.s_addr = binding->addr;
address = inet_ntoa(inaddr);
NI_INIT(&ids);
status = ni_lookup(db_ni, &id, NAME_IP_ADDRESS, address, &ids);
if (status != NI_OK)
{
svcerr_systemerr(req->rq_xprt);
return (NULL);
}
id.nii_object = ids.ni_idlist_val[0];
ni_idlist_free(&ids);
NI_INIT(&nl);
status = ni_lookupprop(db_ni, &id, NAME_SERVES, &nl);
if (status != NI_OK)
{
svcerr_systemerr(req->rq_xprt);
return (NULL);
}
for (i = 0; i < nl.ni_namelist_len; i++)
{
if (tag_match(nl.ni_namelist_val[i], binding->tag))
{
ni_namelist_free(&nl);
return ((void *)~0);
}
}
ni_namelist_free(&nl);
svcerr_systemerr(req->rq_xprt);
return (NULL);
}
/*
* Data structure used to hold arguments and results needed for calling
* the BIND procedure which is broadcast and receives many replies.
*/
typedef struct ni_rparent_stuff {
nibind_bind_args *bindings; /* arguments to BIND */
ni_rparent_res *res; /* result from BIND */
} ni_rparent_stuff;
/*
* Catches a BIND reply
*/
static bool_t
catch(void *vstuff, struct sockaddr_in *raddr, int which)
{
ni_rparent_stuff *stuff = (ni_rparent_stuff *)vstuff;
ni_name_free(&stuff->res->ni_rparent_res_u.binding.tag);
stuff->res->ni_rparent_res_u.binding.tag = ni_name_dup(stuff->bindings[which].server_tag);
stuff->res->ni_rparent_res_u.binding.addr = raddr->sin_addr.s_addr;
stuff->res->status = NI_OK;
/*
* Wait just a moment here to decrease the number of ICMP
* Port Unreachable messages that flood the network.
*/
sleep(RPARENT_CATCH_SLEEP_SECONDS);
return (TRUE);
}
/*
* Determine if this entry serves ".." (i.e. it has a serves property of
* which one of the values looks like ../SOMETAG.
*/
static unsigned
servesdotdot(ni_entry entry, ni_name *tag)
{
ni_name name;
ni_name sep;
unsigned addr;
ni_namelist nl;
ni_index i;
ni_id id;
if (entry.names == NULL || forcedIsRoot) return (0);
id.nii_object = entry.id;
for (i = 0; i < entry.names->ni_namelist_len; i++)
{
name = entry.names->ni_namelist_val[i];
sep = index(name, NI_SEPARATOR);
if (sep == NULL) continue;
if (!ni_name_match_seg(NAME_DOTDOT, name, sep - name)) continue;
NI_INIT(&nl);
if (ni_lookupprop(db_ni, &id, NAME_IP_ADDRESS, &nl) != NI_OK) continue;
if (nl.ni_namelist_len == 0) continue;
addr = inet_addr(nl.ni_namelist_val[0]);
ni_namelist_free(&nl);
*tag = ni_name_dup(sep + 1);
return (addr);
}
return (0);
}
static void
add_binding_entry(struct in_addr sa, ni_name st, struct in_addr ca, ni_name ct,
struct in_addr **addrs, ni_rparent_stuff *stuff, unsigned int *naddrs)
{
char astr[32], cstr[32];
sprintf(astr, "%s", inet_ntoa(sa));
sprintf(cstr, "%s", inet_ntoa(ca));
system_log(LOG_DEBUG, "binding list added %s/%s (%s/%s)", astr, st, cstr, ct);
MM_GROW_ARRAY(*addrs, *naddrs);
(*addrs)[*naddrs].s_addr = sa.s_addr;
MM_GROW_ARRAY(stuff->bindings, *naddrs);
stuff->bindings[*naddrs].client_tag = ni_name_dup(ct);
/*
* Byte swap client address, since this gets sent out in an RPC packet,
* and XDR treats it as an unsigned long.
*/
stuff->bindings[*naddrs].client_addr = ntohl(ca.s_addr);
stuff->bindings[*naddrs].server_tag = ni_name_dup(st);
*naddrs = *naddrs + 1;
}
static void
add_broadcast_binding(ni_name server_tag, ni_name client_tag,
struct in_addr **addrs, ni_rparent_stuff *stuff, unsigned int *naddrs)
{
interface_list_t *l;
int i;
l = sys_interfaces();
if (l == NULL) return;
for (i = 0; i < l->count; i++)
{
if ((l->interface[i].flags & IFF_UP) == 0) continue;
if (l->interface[i].flags & IFF_LOOPBACK)
{
if ((l->count == 1) && (standalone == 0))
{
/* Special case: include loopback if NOT standalone and it is the only interface */
add_binding_entry(l->interface[i].addr, server_tag, l->interface[i].addr, client_tag, addrs, stuff, naddrs);
}
}
else
{
add_binding_entry(l->interface[i].bcast, server_tag, l->interface[i].addr, client_tag, addrs, stuff, naddrs);
}
}
sys_interfaces_release(l);
}
static void
add_hardwired_binding(struct in_addr server_addr, ni_name server_tag, ni_name client_tag, struct in_addr **addrs, ni_rparent_stuff *stuff, unsigned int *naddrs)
{
interface_list_t *l;
int i;
l = sys_interfaces();
if (l == NULL) return;
for (i = 0; i < l->count; i++)
{
if ((l->interface[i].flags & IFF_UP) == 0) continue;
if ((l->interface[i].flags & IFF_LOOPBACK) && (l->count > 1)) continue;
add_binding_entry(server_addr, server_tag, l->interface[i].addr, client_tag, addrs, stuff, naddrs);
}
sys_interfaces_release(l);
}
static void
freeToken(token_t *t)
{
if (t == NULL) return;
if (t->value != NULL) free(t->value);
t->value = NULL;
t->type = TokenNULL;
free(t);
}
/*
* XML tokens are any run enclosed by < and >.
* White space includes spaces, tabs, and newlines.
*/
static token_t *
get_token_1(FILE *fp, int xword)
{
token_t *t, *s;
char c;
static char x = EOF;
int i, run, len, xml;
t = (token_t *)malloc(sizeof(token_t));
t->type = TokenNULL;
t->value = NULL;
len = 0;
if (xword == 1)
{
run = 1;
while (run == 1)
{
c = x;
x = EOF;
if (c == EOF) c = getc(fp);
if (c == EOF) return t;
if (c == '<')
{
x = c;
break;
}
if (len == 0) t->value = malloc(1);
t->value[len++] = c;
t->value = realloc(t->value, len + 1);
t->value[len] = '\0';
}
if (t->value == NULL) return t;
s = (token_t *)malloc(sizeof(token_t));
s->type = TokenWord;
s->value = NULL;
len = 0;
for (i = 0; t->value[i] != '\0'; i++)
{
c = t->value[i];
if (c == '&')
{
if (!strncmp(t->value + i, "&", 5))
{
c = '&';
i += 4;
}
else if (!strncmp(t->value + i, "<", 4))
{
c = '<';
i += 3;
}
else if (!strncmp(t->value + i, ">", 4))
{
c = '>';
i += 3;
}
}
if (len == 0) s->value = malloc(1);
s->value[len++] = c;
s->value = realloc(s->value, len + 1);
s->value[len] = '\0';
}
freeToken(t);
return s;
}
/* Skip white space */
run = 1;
while (run == 1)
{
c = x;
x = EOF;
if (c == EOF) c = getc(fp);
if (c == EOF) return t;
if (c == ' ') continue;
if (c == '\t') continue;
if (c == '\n') continue;
run = 0;
}
xml = 0;
if (c == '<') xml = 1;
len = 1;
t->value = malloc(len + 1);
t->value[0] = c;
t->value[len] = '\0';
t->type = TokenWord;
run = 1;
while (run == 1)
{
c = getc(fp);
if (c == EOF) return t;
if (c == '<')
{
x = c;
return t;
}
if ((xml == 1) && (c == '>')) run = 0;
t->value[len++] = c;
t->value = realloc(t->value, len + 1);
t->value[len] = '\0';
}
return t;
}
static token_t *
pop_token(FILE *fp)
{
token_t *t, *s;
t = get_token_1(fp, 0);
if (t->type != TokenWord) return t;
if (!strcmp(t->value, "<string>"))
{
freeToken(t);
t = get_token_1(fp, 1);
s = get_token_1(fp, 0);
if (strcmp(s->value, "</string>"))
{
freeToken(t);
freeToken(s);
return NULL;
}
freeToken(s);
return t;
}
if (t->value[0] == '<')
{
if (!strcmp(t->value, "<array>")) t->type = TokenXMLArrayStart;
if (!strcmp(t->value, "</array>")) t->type = TokenXMLArrayEnd;
if (!strcmp(t->value, "<dict>")) t->type = TokenXMLDictStart;
if (!strcmp(t->value, "</dict>")) t->type = TokenXMLDictEnd;
if (!strcmp(t->value, "<key>")) t->type = TokenXMLKeyStart;
if (!strcmp(t->value, "</key>")) t->type = TokenXMLKeyEnd;
return t;
}
return t;
}
static ni_property *
pl_createprop(ni_proplist *l, char *n)
{
ni_property *p;
ni_index where;
p = (ni_property *)malloc(sizeof(ni_property));
NI_INIT(p);
p->nip_name = ni_name_dup(n);
p->nip_val.ni_namelist_len = 0;
p->nip_val.ni_namelist_val = NULL;
ni_proplist_insert(l, *p, NI_INDEX_NULL);
ni_prop_free(p);
free(p);
where = ni_proplist_match(*l, n, NULL);
return &(l->nipl_val[where]);
}
static ni_proplist *
fread_plist(FILE *fp)
{
ni_proplist *pl;
ni_property *p;
token_t *t;
t = pop_token(fp);
/* Ignore XML "headers" until we hit "<dict>" */
while (t->type != TokenXMLDictStart)
{
if (t->type == TokenNULL)
{
freeToken(t);
return NULL;
}
freeToken(t);
t = pop_token(fp);
}
freeToken(t);
pl = (ni_proplist *)malloc(sizeof(ni_proplist));
NI_INIT(pl);
forever
{
t = pop_token(fp);
if (t->type == TokenXMLDictEnd)
{
freeToken(t);
return pl;
}
if (t->type != TokenXMLKeyStart)
{
freeToken(t);
ni_proplist_free(pl);
return NULL;
}
freeToken(t);
t = pop_token(fp);
if (t->type != TokenWord)
{
freeToken(t);
ni_proplist_free(pl);
return NULL;
}
p = pl_createprop(pl, t->value);
freeToken(t);
t = pop_token(fp);
if (t->type != TokenXMLKeyEnd)
{
freeToken(t);
ni_proplist_free(pl);
return NULL;
}
freeToken(t);
t = pop_token(fp);
if (t->type != TokenXMLArrayStart)
{
freeToken(t);
ni_proplist_free(pl);
return NULL;
}
freeToken(t);
forever
{
t = pop_token(fp);
if (t->type == TokenXMLArrayEnd)
{
freeToken(t);
break;
}
if (t->type != TokenWord)
{
freeToken(t);
ni_proplist_free(pl);
return NULL;
}
ni_namelist_insert(&(p->nip_val), t->value, NI_INDEX_NULL);
freeToken(t);
}
}
}
static void
add_parent_server(struct in_addr server_addr, ni_name server_tag, ni_name client_tag, struct in_addr **addrs, ni_rparent_stuff *stuff, unsigned int *naddrs)
{
if (server_addr.s_addr == 0) return;
if ((server_tag == NULL) || (client_tag == NULL)) return;
/*
* Filter out self-binding.
*/
if ((!strcmp(server_tag, client_tag)) && (sys_is_my_address(&server_addr))) return;
/*
* If the address is a general broadcast address (0xffffffff)
* we substitute the network-specific broadcast addresss for
* each configured non-loopback interface.
*
* If the address is not a broadcast address, we ping the
* NetInfo server with the given address and tag. If the server
* is up, we cut short the search and "bind" to that server.
* This does not require the server to reply to a bind request,
* and even allows us to "bind" to a parent that doesn't know
* this server. This is called "Anonymous Binding".
*/
if (sys_is_general_broadcast(&server_addr))
{
/*
* Add network_specific broadcast for each
* interface (other than loopback).
*/
add_broadcast_binding(server_tag, client_tag, addrs, stuff, naddrs);
}
else
{
add_hardwired_binding(server_addr, server_tag, client_tag, addrs, stuff, naddrs);
}
}
static ni_status
get_parents_from_file(void *ni, struct in_addr **addrs, ni_rparent_stuff *stuff, unsigned int *naddrs)
{
FILE *fp;
char str[MAXPATHLEN + 1], tag[MAXPATHLEN + 1];
ni_name client_tag = NULL;
ni_proplist *pl;
ni_index where_addr, where_tag;
struct in_addr server_addr;
ni_name server_tag;
int i, i_tag;
client_tag = ni_tagname(ni);
strcpy(tag, client_tag);
ni_name_free(&client_tag);
sprintf(str, CONFIG_FILE_NAME, tag);
/* If the config file doesn't exist or is unreadable, we fail. */
fp = fopen(str, "r");
if (fp == NULL) return NI_FAILED;
pl = fread_plist(fp);
fclose(fp);
/* No proplist == do not bind */
if (pl == NULL) return NI_OK;
where_addr = ni_proplist_match(*pl, KEY_PARENT_SERVER_ADDR, NULL);
where_tag = ni_proplist_match(*pl, KEY_PARENT_SERVER_TAG, NULL);
if ((where_addr == NI_INDEX_NULL)
|| (where_tag == NI_INDEX_NULL)
|| (pl->ni_proplist_val[where_addr].nip_val.ni_namelist_len == 0)
|| (pl->ni_proplist_val[where_tag].nip_val.ni_namelist_len == 0))
{
ni_proplist_free(pl);
free(pl);
return NI_OK;
}
i_tag = 0;
for (i = 0; i < pl->ni_proplist_val[where_addr].nip_val.ni_namelist_len; i++)
{
server_addr.s_addr = inet_addr(pl->ni_proplist_val[where_addr].nip_val.ni_namelist_val[i]);
server_tag = pl->ni_proplist_val[where_tag].nip_val.ni_namelist_val[i_tag];
add_parent_server(server_addr, server_tag, tag, addrs, stuff, naddrs);
i_tag++;
/* If we run out of tags, stick on the last one */
if (i_tag == pl->ni_proplist_val[where_tag].nip_val.ni_namelist_len) i_tag--;
}
ni_proplist_free(pl);
free(pl);
return NI_OK;
}
static ni_status
get_parents_from_db(void *ni, struct in_addr **addrs, ni_rparent_stuff *stuff, unsigned int *naddrs)
{
ni_name client_tag = NULL;
ni_name server_tag = NULL;
struct in_addr server_addr;
ni_id id;
ni_idlist ids;
ni_entrylist entries;
int i;
ni_status status;
status = ni_root(ni, &id);
if (status != NI_OK) return status;
NI_INIT(&ids);
status = ni_lookup(ni, &id, NAME_NAME, NAME_MACHINES, &ids);
if (status != NI_OK) return status;
/* list "serves" properties in /machines */
id.nii_object = ids.ni_idlist_val[0];
ni_idlist_free(&ids);
NI_INIT(&entries);
status = ni_list_const(ni, &id, NAME_SERVES, &entries);
if (status != NI_OK) return status;
client_tag = ni_tagname(ni);
for (i = 0; i < entries.ni_entrylist_len; i++)
{
/*
* We walk through the list of "serves" values. If a value
* is "../tag", we add the host's address and the tag to a list
* of potential parent servers.
*/
server_addr.s_addr = servesdotdot(entries.ni_entrylist_val[i], &server_tag);
add_parent_server(server_addr, server_tag, client_tag, addrs, stuff, naddrs);
ni_name_free(&server_tag);
}
ni_name_free(&client_tag);
ni_list_const_free(ni);
return NI_OK;
}
static void
swap_addrs(struct in_addr *addrs, ni_rparent_stuff *stuff, int a, int b)
{
unsigned long tempaddr;
ni_name temptag;
if (a == b) return;
tempaddr = addrs[a].s_addr;
addrs[a].s_addr = addrs[b].s_addr;
addrs[b].s_addr = tempaddr;
temptag = stuff->bindings[a].server_tag;
stuff->bindings[a].server_tag = stuff->bindings[b].server_tag;
stuff->bindings[b].server_tag = temptag;
tempaddr = stuff->bindings[a].client_addr;
stuff->bindings[a].client_addr = stuff->bindings[b].client_addr;
stuff->bindings[b].client_addr = tempaddr;
temptag = stuff->bindings[a].client_tag;
stuff->bindings[a].client_tag = stuff->bindings[b].client_tag;
stuff->bindings[b].client_tag = temptag;
return;
}
static ni_status
bind_to_parent(struct in_addr *addrs, ni_rparent_stuff *stuff, unsigned int naddrs, ni_rparent_res *res)
{
int i, j;
unsigned int nlocal, nnetwork;
unsigned int nbcast;
enum clnt_stat stat;
static ni_rparent_res old_res = { NI_NOTMASTER };
static struct in_addr old_binding;
static struct in_addr new_binding;
int *shuffle, temp;
static int fix3408166 = NI_OK;
unsigned int rentry;
if (naddrs == 0) return NI_NETROOT;
/*
* Majka - 1994.04.27
* 1. shuffle the servers to distribute client load
* 2. re-order the servers so that:
* a. servers on the local host are first
* b. servers on the local network are next
* c. all other servers
*/
shuffle = (int *)malloc(naddrs * sizeof(int));
for (i = 0; i < naddrs; i++) shuffle[i] = i;
for (i = 0, j = naddrs; j > 0; i++, j--)
{
/* pick one of the remaining entries */
rentry = random() % j;
/* swap entries */
temp = shuffle[rentry];
shuffle[rentry] = shuffle[j-1];
shuffle[j-1] = temp;
/* and swap the actual NI addresses */
swap_addrs(addrs, stuff, rentry, j-1);
}
free(shuffle);
/* Abort if we got a SIGHUP (unbind) */
if (get_binding_status() == NI_FAILED)
{
system_log(LOG_DEBUG, "bind_to_parent aborted");
return NI_NORESPONSE;
}
/*
* move local servers to the head of the list
*/
nlocal = 0;
for (i = nlocal; i < naddrs; i++)
{
if (sys_is_my_address(&(addrs[i])))
{
swap_addrs(addrs, stuff, nlocal, i);
nlocal++;
}
}
/*
* move servers on this network to follow local servers
*/
nnetwork = nlocal;
nbcast = 0;
for (i = nnetwork; i < naddrs; i++)
{
if (sys_is_on_attached_network(&(addrs[i])))
{
if (sys_is_my_broadcast(&(addrs[i]))) nbcast++;
swap_addrs(addrs, stuff, nnetwork, i);
nnetwork++;
}
}
/*
* Anonymous Binding.
* Majka - 1999.08.25
*/
for (i = 0; i < naddrs; i++)
{
/* Abort if we got a SIGHUP (unbind) */
if (get_binding_status() == NI_FAILED)
{
system_log(LOG_DEBUG, "bind_to_parent aborted");
return NI_NORESPONSE;
}
if (sys_is_my_broadcast(&(addrs[i]))) continue;
stat = ni_ping(addrs[i].s_addr, stuff->bindings[i].server_tag);
if (stat == RPC_SUCCESS)
{
ni_name_free(&(res->ni_rparent_res_u.binding.tag));
res->ni_rparent_res_u.binding.tag = ni_name_dup(stuff->bindings[i].server_tag);
res->ni_rparent_res_u.binding.addr = addrs[i].s_addr;
system_log(LOG_INFO, "Anonymous binding to %s/%s", inet_ntoa(addrs[i]), stuff->bindings[i].server_tag);
res->status = NI_OK;
goto binding_done;
}
else if (stat == RPC_SYSTEMERROR) break;
}
/*
* Found the addresses and committed to multicalling now
*/
stuff->res = res;
/*
* Try binding to a server on this host,
* if that fails,
* try binding to a server on this host or on an attached subnet,
* if that fails and there are any non-broadcast addresses,
* try all servers.
*/
stat = RPC_TIMEDOUT; /* start with stat != RPC_SUCCESS */
if ((stat != RPC_SUCCESS) && (nlocal > 0))
{
/* Abort if we got a SIGHUP (unbind) */
if (get_binding_status() == NI_FAILED)
{
system_log(LOG_DEBUG, "bind_to_parent aborted");
return NI_NORESPONSE;
}
for (i = 0; i < nlocal; i++)
{
system_log(LOG_INFO, "local bind try %d: %s/%s",
i + 1, inet_ntoa(addrs[i]),
stuff->bindings[i].server_tag);
}
stat = ni_multi_call(nlocal, addrs, NIBIND_PROG, NIBIND_VERS, NIBIND_BIND, (void *)xdr_nibind_bind_args, stuff->bindings, sizeof(nibind_bind_args), (void *)xdr_void, stuff, catch, -1);
}
if (stat != RPC_SUCCESS && nnetwork > 0)
{
/* Abort if we got a SIGHUP (unbind) */
if (get_binding_status() == NI_FAILED)
{
system_log(LOG_DEBUG, "bind_to_parent aborted");
return NI_NORESPONSE;
}
for (i = 0; i < nnetwork; i++)
{
system_log(LOG_INFO, "network bind try %d: %s/%s",
i + 1, inet_ntoa(addrs[i]),
stuff->bindings[i].server_tag);
}
stat = ni_multi_call(nnetwork, addrs, NIBIND_PROG, NIBIND_VERS, NIBIND_BIND, (void *)xdr_nibind_bind_args, stuff->bindings, sizeof(nibind_bind_args), (void *)xdr_void, stuff, catch, -1);
}
if (stat != RPC_SUCCESS && ((naddrs - nbcast) > 0))
{
/* Abort if we got a SIGHUP (unbind) */
if (get_binding_status() == NI_FAILED)
{
system_log(LOG_DEBUG, "bind_to_parent aborted");
return NI_NORESPONSE;
}
for (i = 0; i < naddrs; i++)
{
system_log(LOG_INFO, "world bind try %d: %s/%s",
i + 1, inet_ntoa(addrs[i]),
stuff->bindings[i].server_tag);
}
stat = ni_multi_call(naddrs, addrs, NIBIND_PROG, NIBIND_VERS, NIBIND_BIND, (void *)xdr_nibind_bind_args, stuff->bindings, sizeof(nibind_bind_args), (void *)xdr_void, stuff, catch, -1);
}
binding_done:
/* Abort if we got a SIGHUP (unbind) */
if (get_binding_status() == NI_FAILED)
{
system_log(LOG_DEBUG, "bind_to_parent aborted");
return NI_NORESPONSE;
}
if (stat != RPC_SUCCESS)
{
alert_open("English");
res->status = NI_NORESPONSE;
}
else
{
alert_close();
res->status = NI_OK;
}
old_binding.s_addr = old_res.ni_rparent_res_u.binding.addr;
if (NI_OK == res->status)
{
/* Successfully bound */
fix3408166 = NI_OK;
new_binding.s_addr = res->ni_rparent_res_u.binding.addr;
switch (old_res.status)
{
case NI_NOTMASTER:
/* Initial binding */
system_log(LOG_NOTICE, "bound to %s/%s",
inet_ntoa(new_binding), res->ni_rparent_res_u.binding.tag);
old_res.ni_rparent_res_u.binding.tag =
(ni_name)malloc(NI_NAME_MAXLEN+1);
break;
case NI_NETROOT:
/* New ".." serves property, new binding */
system_log(LOG_NOTICE, "bound to (new parent) %s/%s",
inet_ntoa(new_binding), res->ni_rparent_res_u.binding.tag);
old_res.ni_rparent_res_u.binding.tag =
(ni_name)malloc(NI_NAME_MAXLEN+1);
break;
default:
/* We just rebound */
system_log(LOG_WARNING, "rebound to %s/%s (was to %s/%s)",
inet_ntoa(new_binding), res->ni_rparent_res_u.binding.tag,
inet_ntoa(old_binding), old_res.ni_rparent_res_u.binding.tag);
break;
}
old_res.status = res->status;
old_res.ni_rparent_res_u.binding.addr =
res->ni_rparent_res_u.binding.addr;
(void)strcpy(old_res.ni_rparent_res_u.binding.tag,
res->ni_rparent_res_u.binding.tag);
/* keep track of latest parent for statistics */
if (latestParentInfo == NULL)
latestParentInfo = malloc(sizeof("xxx.xxx.xxx.xxx") + 1 + NI_NAME_MAXLEN + 1);
sprintf(latestParentInfo, "%s/%s",
inet_ntoa(new_binding), res->ni_rparent_res_u.binding.tag);
}
else
{
/* Binding failed (!) */
if (fix3408166 == NI_OK)
{
switch (old_res.status)
{
case NI_NOTMASTER:
system_log(LOG_ERR, "unable to bind to parent - %s",
clnt_sperrno(stat));
break;
default:
system_log(LOG_ERR, "unable to rebind from %s/%s - %s",
inet_ntoa(old_binding), old_res.ni_rparent_res_u.binding.tag,
clnt_sperrno(stat));
break;
}
}
fix3408166 = old_res.status;
}
return res->status;
}
/*
* The NetInfo RPARENT procedure
*/
ni_rparent_res *
_ni_rparent_2_svc(void *arg, struct svc_req *req)
{
static ni_rparent_res res;
static long root_time = -1;
static unsigned long paddr = -1;
long now;
struct in_addr *addrs;
unsigned int i, naddrs, j;
ni_rparent_stuff stuff;
ni_status status, binding_status;
if (req == NULL) return NULL;
res.status = get_binding_status();
/*
* If standalone (i.e. no network attached), then stop here
*/
if (alert_aborted())
{
set_binding_status(NI_NETROOT, 0);
return &res;
}
if (sys_is_standalone())
{
set_binding_status(NI_NETROOT, 0);
return &res;
}
/*
* If already have the result, return it.
*
* Note: As long as the parent NetInfo server which we were
* previously bound to is still up and running there
* will be no re-binding calls which might detect that
* we are now the root domain.
*/
if (res.status == NI_OK)
{
if (ni_ping(paddr, res.ni_rparent_res_u.binding.tag) == RPC_SUCCESS)
{
/*
* The address is stuffed into an unsigned long,
* so XDR will byte swap it. Thus we must pass the
* result in host byte order.
*/
res.ni_rparent_res_u.binding.addr = ntohl(paddr);
return (&res);
}
}
/*
* If we were the network root before, we probably still are.
* Throttle back attempts to find the parent domain.
*/
if ((res.status == NI_NETROOT) && (root_time != -1))
{
time(&now);
if ((now - root_time) < (NETROOT_TIMEOUT_MINS * 60)) return (&res);
}
/*
* Attempt to bind.
*/
binding_status = NI_NORESPONSE;
for (j = 0; (j < MAX_REBIND_ATTEMPTS) && (binding_status == NI_NORESPONSE); j++)
{
addrs = NULL;
naddrs = 0;
stuff.bindings = NULL;
/*
* Set status to NI_NORESPONSE while we are trying to bind.
*/
set_binding_status(NI_NORESPONSE, 0);
/*
* If there is a binding file, get potential parent servers from it.
* Otherwise, look in the database.
*/
status = get_parents_from_file(db_ni, &addrs, &stuff, &naddrs);
if (status != NI_OK)
{
status = get_parents_from_db(db_ni, &addrs, &stuff, &naddrs);
}
if (status != NI_OK)
{
/* Can't get any potential parents! */
res.status = NI_NETROOT;
set_binding_status(NI_NETROOT, 0);
return &res;
}
system_log(LOG_DEBUG, "Rebinding");
binding_status = bind_to_parent(addrs, &stuff, naddrs, &res);
MM_FREE_ARRAY(addrs, naddrs);
for (i = 0; i < naddrs; i++)
{
ni_name_free(&stuff.bindings[i].client_tag);
ni_name_free(&stuff.bindings[i].server_tag);
}
MM_FREE_ARRAY(stuff.bindings, naddrs);
}
if (binding_status == NI_OK)
{
paddr = res.ni_rparent_res_u.binding.addr;
/* Byte-swap the result so XDR does the "right thing" */
res.ni_rparent_res_u.binding.addr = ntohl(paddr);
}
else
{
res.status = NI_NETROOT;
time(&root_time);
}
set_binding_status(res.status, 0);
return (&res);
}
/*
* Called at startup: try to locate a parent server, allowing for user
* to abort search if timeouts occur.
*/
void
waitforparent(void)
{
ni_rparent_res *res;
int i;
unsigned int a, n;
n = get_localbindattempts(db_ni);
if (n == 0) return;
a = 0;
alert_enable(1);
for (;;)
{
a++;
system_log(LOG_DEBUG, "waitforparent attempt %d", a);
res = _ni_rparent_2_svc(NULL, (struct svc_req *)(~0));
if (res->status != NI_NORESPONSE)
{
system_log(LOG_DEBUG, "waitforparent exiting for status %d", res->status);
break;
}
if (a >= n)
{
system_log(LOG_DEBUG, "waitforparent aborted after %d attempt%s", a, (a == 1) ? "" : "s");
break;
}
for (i = 0; i < PARENT_NINTERVALS; i++)
{
if (alert_aborted())
{
system_log(LOG_DEBUG, "waitforparent aborted (alert_aborted)");
alert_enable(0);
return;
}
sleep(PARENT_SLEEPTIME);
}
}
alert_enable(0);
return;
}
/*
* The NetInfo CRASHED procedure
* If master, do nothing. If clone, check that our database is up to date.
*/
void *
_ni_crashed_2_svc(unsigned *checksum, struct svc_req *req)
{
if (req == NULL) return NULL;
if (i_am_clone)
{
if (*checksum != ni_getchecksum(db_ni))
{
/*
* If we get a crashed, it means the master's coming
* up. If our database is out-of-sync, we should
* sync up, regardless of when we last got a new
* database.
*/
have_transferred = 0;
dir_clonecheck();
}
}
return ((void *)~0);
}
/*
* The NetInfo READALL procedure
*
* XXX: doing a readall takes a long time. We should really
* fork a thread to do this and disable writes until it is done.
* Since there are bunches of thread-safety issues (especially
* with the RPC libraries) we will do the master side in a
* separate process.
*/
void proxy_term(void);
ni_readall_res *
_ni_readall_2_svc(
unsigned *checksum,
struct svc_req *req
)
{
static ni_readall_res res;
unsigned db_checksum;
int didit;
struct stat stat_buf;
int i;
static int kpid;
if (req == NULL) return (NULL);
/*
* Allow a clone to answer a readall. This means that a
* program can be written which does a readall but can
* request the information from a clone (rather than
* always having to go to the master).
*/
if (i_am_clone && !cloneReadallResponseOK)
{
res.status = NI_NOTMASTER;
return (&res);
}
db_checksum = ni_getchecksum(db_ni);
if (*checksum == db_checksum)
{
res.status = NI_OK;
res.ni_readall_res_u.stuff.checksum = 0;
res.ni_readall_res_u.stuff.list = NULL;
return (&res);
}
/* if notifications are pending, then fail transfer. */
if (have_notifications_pending())
{
res.status = NI_MASTERBUSY;
return (&res);
}
system_log(LOG_WARNING, "readall %s {%u} to %s:%hu {%u}",
db_tag, db_checksum,
inet_ntoa(svc_getcaller(req->rq_xprt)->sin_addr),
ntohs(svc_getcaller(req->rq_xprt)->sin_port), *checksum);
/*
* Do master side of readall in separate process.
* We'll use a separate process to ensure we don't run
* into any RPC thread-safe problems. This will be controlled
* by a property in the domain's root directory, readall_proxies
* if > 0, it denotes the maximum number of readall subprocesses
* we'll have forked at once.
*/
syslock_lock(readall_syslock);
sending_all++; /* Turn off writing for duration of readall */
if (max_readall_proxies != 0)
{
/* Perhaps use a proxy */
if ((max_readall_proxies == -1) ||
(max_readall_proxies > readall_proxies))
{
/* We'll fork a new process to do this readall */
readall_proxies++; /* Assume things work; elim races? */
kpid = fork();
if (kpid == 0)
{
/* Child */
/* all readall proxies go in the same process group */
if (readall_proxies == 1)
process_group = getpid();
if (setpgrp(0, process_group) == -1) {
system_log(LOG_ERR, "proxy can't setpgrp - %m");
}
syslock_unlock(readall_syslock);
/* Right now, if we get a SIGUSR1, we'll just set the
* shutdown flag, which is examined at the top of the
* run loop in svc_run(). We really ought to clean up
* and disappear when we get a SIGUSR1 as a readall
* proxy.
*/
signal(SIGUSR1, (void *)proxy_term);
/*
* XXX FIX THIS! XXX
* sleeping does not fix race conditions!!!
*
* Possible race condition: It's possible for the
* readall proxy to finish sending the database before
* the parent process updates its list of proxy PIDs.
* Sleep 1 sec to decrease the chances of this.
*/
sleep(1);
i_am_proxy = TRUE;
/*
* Identify that we're responding to a readall in argv.
*
* N.B. This needs to be shorter than invocation string!
* As long as we're invoked with our full pathname as
* argv[0], that's fine; if we're invoked with a relative
* pathname, this will likely be truncated. Note that
* if we've a maximum size clone address (xxx.xxx.xxx.xxx),
* this string is as long as it can be!
*/
setproctitle("netinfod %s->%s", db_tag,
inet_ntoa(svc_getcaller(req->rq_xprt)->sin_addr));
/*
* To ensure we don't inappropriately keep a connection
* open that our parent wants to close, let's close all
* the FDs that are sockets, except the one we'll use to
* answer the clone.
*/
for (i = getdtablesize() - 1; i >= 0; i--)
{
/* Might not have std* */
if (i != req->rq_xprt->xp_sock)
{
/* Do we need to socket_lock()? We're child... */
/*
* According to the BUGS section of the fstat()
* man page, and according to the code in the
* kernel [bsd/netinet/tcp_usrreq.c, the PRU_SENSE
* case of tcp_usrreq()], the whole fstat buffer
* will be zero, except for st_blksize (which
* will be the socket's high water mark). So,
* in case this bug is ever fixed and the mode is
* really set properly, we check for either IFSOCK
* set in the mode, or the whole mode being zero
* (which it can't otherwise be, since ALL files
* have a type set in the high-order bits of the
* mode field, and type of 0 isn't defined).
*/
if (fstat(i, &stat_buf) == 0)
{
/* Successful; check it */
if ((S_IFSOCK == (S_IFMT & stat_buf.st_mode)) ||
!stat_buf.st_mode)
close(i);
}
else
{
switch(errno)
{
/* Error; report it */
case 0: /* Whoops; no error? */
system_log(LOG_WARNING,
"proxy: fstat(%d) return of 0, and errno of 0");
break;
case EBADF: /* Not an open fd; ignore */
break;
default: /* Real error: report it */
system_log(LOG_ERR, "proxy: fstat(%d) - %m", i);
break;
}
}
}
}
/*
* Normally, we'd surround this with a socket_lock() and
* socket_unlock() pair. But, we KNOW there weren't any
* notifications pending in the parent, and we've no
* other threads. In addition, there's a slim race
* condition found during testing which leaves the
* socket_syslock locked; with no other thread running
* to unlock it, we just sit forever, eating up bits
* of CPU time.
*/
didit = svc_sendreply(req->rq_xprt, (void *)readall, db_ni);
if (!didit)
{
system_log(LOG_ERR, "readall %s to %s:%hu failed",
db_tag, inet_ntoa(svc_getcaller(req->rq_xprt)->sin_addr),
ntohs(svc_getcaller(req->rq_xprt)->sin_port));
exit(NI_SYSTEMERR); /* Tell parent we failed */
}
else
{
system_log(LOG_NOTICE, "readall %s to %s:%hu complete",
db_tag, inet_ntoa(svc_getcaller(req->rq_xprt)->sin_addr),
ntohs(svc_getcaller(req->rq_xprt)->sin_port));
}
exit(0); /* Tell parent all's ok */
}
else if (kpid == -1)
{
/* Error in fork()*/
readall_proxies--;
syslock_unlock(readall_syslock);
system_log(LOG_ERR, "Can't fork for readall [%p], retaining");
/* Fall through so we do readall anyway, in this process */
}
else
{
/* Parent */
system_log(LOG_DEBUG, "readall proxy pid %d", kpid);
/* all readall proxies go in the same process group */
if (readall_proxies == 1)
process_group = kpid;
if (setpgrp(kpid, process_group) == -1) {
system_log(LOG_ERR, "proxy can't setpgrp - %m");
}
/* Retain the proxy's pid to kill it if we shutdown */
add_proxy(kpid, svc_getcaller(req->rq_xprt)-> sin_addr.s_addr);
syslock_unlock(readall_syslock);
/*
* Just in case (?!), close this here; child has it open.
* But, it's really destruction of the RPC transport. But,
* we surely can't do this, because ni_prot_svc does a
* svc_freeargs() right after we return even return(NULL),
* and this will just fail, and so we'll exit(1). Maybe
* we need to do what svctcp_destroy() does, but that
* grossly violates encapsulation.
* XXX Do we need to do something to the LRU cache, or the
* FDs interesting to svc_run's select()?...
*/
return(NULL); /* Just keep listening */
}
}
else
{
/* Out of proxies */
system_log(LOG_WARNING, "readall proxy limit (%d) reached; %s "
"request from %s:%hu", max_readall_proxies,
strict_proxies ? "proroguing" : "retaining",
inet_ntoa(svc_getcaller(req->rq_xprt)->sin_addr),
ntohs(svc_getcaller(req->rq_xprt)->sin_port));
if (strict_proxies)
{
/* Strict proxies says we can't do it */
sending_all--; /* We're not doing anything; decrement */
syslock_unlock(readall_syslock);
res.status = NI_MASTERBUSY;
return (&res);
}
syslock_unlock(readall_syslock);
}
/* If not strict proxies, retain and run in this process */
} else {
syslock_unlock(readall_syslock);
}
/*
* Either proxies disabled, or we're out and loose, or the fork()
* of the proxy failed. Do it here, regardless.
*/
socket_lock();
didit = svc_sendreply(req->rq_xprt, (void *)readall, db_ni);
socket_unlock();
syslock_lock(readall_syslock);
sending_all--; /* Let writing resume (well, once we're at 0) */
syslock_unlock(readall_syslock);
if (!didit)
{
system_log(LOG_ERR, "readall %s to %s:%hu failed", db_tag,
inet_ntoa(svc_getcaller(req->rq_xprt)->sin_addr),
ntohs(svc_getcaller(req->rq_xprt)->sin_port));
}
else
{
system_log(LOG_NOTICE, "readall %s to %s:%hu complete", db_tag,
inet_ntoa(svc_getcaller(req->rq_xprt)->sin_addr),
ntohs(svc_getcaller(req->rq_xprt)->sin_port));
}
return (NULL);
}
void
readall_catcher(void)
{
/*
* We can't just clean up the proxy's pid: remove_proxy() calls
* [HashTable removeKey:] which calls free(), which waits on the
* (global) malloc lock. So, just post that we need to clean up.
* And, make the posting as atomic as possible, so we don't ever
* lose anything, and we avoid possible race conditions.
*/
readall_done = TRUE;
}
void
readall_cleanup(void)
{
int p;
union wait wait_stat;
unsigned int addr;
/*
* A readall fork finished doing its job. Note the **ASSUMPTION**
* that the only child processes we fork are for readalls.
*/
syslock_lock(readall_syslock);
while (TRUE)
{
p = wait4(-1, (_WAIT_TYPE_ *)&wait_stat, WNOHANG, NULL);
switch (p)
{
case 0: /* All dead children reaped */
syslock_unlock(readall_syslock);
return;
break;
case -1: /* Error */
syslock_unlock(readall_syslock);
if (ECHILD != errno)
system_log(LOG_WARNING, "problem reaping proxy: %m");
return;
break;
default: /* Someone's done */
/* transfer was successful */
addr = get_proxy(p);
if (addr == 0)
{
system_log(LOG_WARNING,
"child process %d not a readall proxy", p);
}
else
{
if ((wait_stat.w_retcode == 0) && (wait_stat.w_termsig == 0))
{
notify_mark_clone(addr);
remove_proxy(p);
}
else
{
system_log(LOG_ERR, "readall proxy terminated with status %u, "
"signal %u", wait_stat.w_retcode, wait_stat.w_termsig);
}
if (sending_all > 0) sending_all--;
if ((readall_proxies > 0) && (0 == --readall_proxies))
readall_done = FALSE; /* No more readalls can be pending */
}
break;
}
}
syslock_unlock(readall_syslock);
return;
}
/*
* Allow SIGINT to turn off updates
*/
void
dblock_catcher(void)
{
if (i_am_clone)
{
system_log(LOG_ERR, "SIGINT to clone ignored");
return;
}
else if (i_am_proxy)
{
system_log(LOG_ERR, "SIGINT to readall proxy ignored");
return;
}
syslock_lock(lockup_syslock);
db_lockup = ! db_lockup;
system_log(LOG_WARNING, "Master database is now %s",
db_lockup ? "locked" : "unlocked");
syslock_unlock(lockup_syslock);
return;
}
/*
* The NetInfo RESYNC procedure
*/
ni_status *
_ni_resync_2_svc(
void *arg,
struct svc_req *req
)
{
static ni_status status;
if (req == NULL) return (NULL);
system_log(LOG_NOTICE, "got a resync from %s:%hu",
inet_ntoa(svc_getcaller(req->rq_xprt)->sin_addr),
ntohs(svc_getcaller(req->rq_xprt)->sin_port));
cleanupwait = get_cleanupwait(db_ni);
forcedIsRoot = get_forced_root(db_ni);
cloneReadallResponseOK = get_clone_readall(db_ni);
if (i_am_clone)
{
if (get_sanitycheck(db_ni)) sanitycheck(db_tag);
dir_clonecheck();
}
else
{
notify_resync();
}
status = NI_OK;
return (&status);
}
/*
* Find the mapped port for program,version.
* Calls the pmap service remotely to do the lookup.
* Returns 0 if no map exists.
*/
static u_short
ni_ping_pmap_getport(struct sockaddr_in *address, unsigned int program, unsigned int version, unsigned int protocol, int *err)
{
u_short port = 0;
int sock = -1;
int errno_saved;
CLIENT *client;
struct pmap parms;
struct timeval tv, total;
tv.tv_sec = PING_TIMEOUT_SEC;
tv.tv_usec = PING_TIMEOUT_USEC;
total.tv_sec = PING_CALL_SEC;
total.tv_usec = PING_CALL_USEC;
socket_lock();
sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
errno_saved = errno;
socket_unlock();
if (sock < 0)
{
syslog(LOG_DEBUG, "ni_ping_pmap_getport: couldn't get socket - %s", strerror(errno_saved));
*err = RPC_SYSTEMERROR;
return 0;
}
address->sin_port = htons(PMAPPORT);
client = clntudp_bufcreate(address, PMAPPROG, PMAPVERS, tv, &sock, RPCSMALLMSGSIZE, RPCSMALLMSGSIZE);
if (client == NULL)
{
syslog(LOG_DEBUG, "ni_ping_pmap_getport: %s", clnt_spcreateerror("clntudp_bufcreate"));
socket_lock();
close(sock);
socket_unlock();
*err = RPC_FAILED;
return 0;
}
parms.pm_prog = program;
parms.pm_vers = version;
parms.pm_prot = protocol;
parms.pm_port = 0; /* not needed or used */
*err = CLNT_CALL(client, PMAPPROC_GETPORT, (void *)xdr_pmap, &parms, (void *)xdr_u_short, &port, total);
if ((*err == RPC_SUCCESS) && (port == 0)) *err = RPC_PROGNOTREGISTERED;
socket_lock();
clnt_destroy(client);
close(sock);
socket_unlock();
address->sin_port = 0;
return port;
}
int
ni_ping_socket_open(struct sockaddr_in *raddr, int prog, int vers, int *err)
{
int sock;
int errno_saved;
u_short port;
/*
* If no port number given ask the pmap for one
*/
if (raddr->sin_port == 0)
{
port = ni_ping_pmap_getport(raddr, prog, vers, IPPROTO_UDP, err);
if (port == 0)
{
syslog(LOG_DEBUG, "ni_ping_socket_open: %s", clnt_spcreateerror("pmap_getport failed"));
return -1;
}
raddr->sin_port = htons(port);
}
socket_lock();
sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
errno_saved = errno;
socket_unlock();
if (sock < 0)
{
syslog(LOG_WARNING, "ni_ping_socket_open: couldn't get socket - %s", strerror(errno_saved));
*err = RPC_SYSTEMERROR;
return -1;
}
*err = RPC_SUCCESS;
return sock;
}
/*
* Ping the server at the given address/tag
*/
static int
ni_ping(u_long address, ni_name tag)
{
struct sockaddr_in sin;
struct timeval tv, total;
enum clnt_stat stat;
int sock, err;
nibind_getregister_res res;
CLIENT *cl;
sin.sin_family = AF_INET;
sin.sin_port = 0;
sin.sin_addr.s_addr = address;
bzero(sin.sin_zero, sizeof(sin.sin_zero));
err = RPC_SUCCESS;
sock = ni_ping_socket_open(&sin, NIBIND_PROG, NIBIND_VERS, &err);
if (sock < 0) return err;
tv.tv_sec = PING_TIMEOUT_SEC;
tv.tv_usec = PING_TIMEOUT_USEC;
total.tv_sec = PING_CALL_SEC;
total.tv_usec = PING_CALL_USEC;
cl = clntudp_create(&sin, NIBIND_PROG, NIBIND_VERS, tv, &sock);
if (cl == NULL)
{
syslog(LOG_DEBUG, "ni_ping: %s", clnt_spcreateerror("clntudp_create"));
socket_close(sock);
return RPC_FAILED;
}
stat = clnt_call(cl, NIBIND_GETREGISTER, (void *)xdr_ni_name, &tag, (void *)xdr_nibind_getregister_res, &res, total);
clnt_destroy(cl);
socket_close(sock);
if (stat != RPC_SUCCESS) return stat;
if (res.status != NI_OK) return RPC_PROGNOTREGISTERED;
/*
* Actually talk to parent during ni_ping
*/
sin.sin_port = htons(res.nibind_getregister_res_u.addrs.udp_port);
sock = socket_open(&sin, NI_PROG, NI_VERS);
if (sock < 0) return RPC_SYSTEMERROR;
cl = clntudp_create(&sin, NI_PROG, NI_VERS, tv, &sock);
if (cl == NULL)
{
syslog(LOG_DEBUG, "ni_ping: %s", clnt_spcreateerror("clntudp_create"));
socket_close(sock);
return RPC_FAILED;
}
stat = clnt_call(cl, _NI_PING, (void *)xdr_void, (void *)NULL, (void *)xdr_void, (void *)NULL, total);
clnt_destroy(cl);
socket_close(sock);
return stat;
}
syntax highlighted by Code2HTML, v. 0.9.1