/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 1991, 2011, Oracle and/or its affiliates. All rights reserved.
*/
/*
* This module contains the subroutines used by the server to manipulate
* objects and names.
*/
#include "mt.h"
#include <pwd.h>
#include <grp.h>
#include <syslog.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/fcntl.h>
#include <netinet/in.h>
#include <rpc/rpc.h> /* Must be ahead of rpcb_clnt.h */
#include <rpc/svc.h>
#include <tiuser.h>
#include <netconfig.h>
#include <netdir.h>
#include <rpc/rpcb_clnt.h>
#include <rpc/pmap_clnt.h>
#include <rpcsvc/nis.h>
#include <rpcsvc/nis_dhext.h>
#include "nis_clnt.h"
#include <sys/systeminfo.h>
#include <nsswitch.h>
#define MAXIPRINT (11) /* max length of printed integer */
/*
* send and receive buffer size used for clnt_tli_create if not specified.
* This is only used for "UDP" connection.
* This limit can be changed from the application if this value is too
* small for the application. To use the maximum value for the transport,
* set this value to 0.
*/
int __nisipbufsize = 8192;
/*
* Static function prototypes.
*/
static struct local_names *__get_local_names(void);
static char *__map_addr(struct netconfig *, char *, rpcprog_t, rpcvers_t);
/*
* nis_dir_cmp() -- the results can be read as:
* "Name 'n1' is a $result than name 'n2'"
*/
name_pos
nis_dir_cmp(
nis_name n1,
nis_name n2) /* See if these are the same domain */
{
size_t l1, l2;
name_pos result;
if ((n1 == NULL) || (n2 == NULL))
return (BAD_NAME);
l1 = strlen(n1);
l2 = strlen(n2);
/* In this routine we're lenient and don't require a trailing '.' */
/* so we need to ignore it if it does appear. */
/* ==== That's what the previous version did so this one does */
/* too, but why? Is this inconsistent with rest of system? */
if (l1 != 0 && n1[l1 - 1] == '.') {
--l1;
}
if (l2 != 0 && n2[l2 - 1] == '.') {
--l2;
}
if (l1 > l2) {
result = LOWER_NAME;
} else if (l1 == l2) {
result = SAME_NAME;
} else /* (l1 < l2); swap l1/l2 and n1/n2 */ {
nis_name ntmp;
size_t ltmp;
ntmp = n1; n1 = n2; n2 = ntmp;
ltmp = l1; l1 = l2; l2 = ltmp;
result = HIGHER_NAME;
}
/* Now l1 >= l2 in all cases */
if (l2 == 0) {
/* Special case for n2 == "." or "" */
return (result);
}
if (l1 > l2) {
n1 += l1 - l2;
if (n1[-1] != '.') {
return (NOT_SEQUENTIAL);
}
}
if (strncasecmp(n1, n2, l2) == 0) {
return (result);
}
return (NOT_SEQUENTIAL);
}
#define LN_BUFSIZE (size_t)1024
struct principal_list {
uid_t uid;
char principal[LN_BUFSIZE];
struct principal_list *next;
};
struct local_names {
char domain[LN_BUFSIZE];
char host[LN_BUFSIZE];
char *rpcdomain;
struct principal_list *principal_map;
char group[LN_BUFSIZE];
};
static mutex_t ln_lock = DEFAULTMUTEX; /* lock level 2 */
static struct local_names *ln = NULL;
static struct local_names *__get_local_names1();
static struct local_names *
__get_local_names(void)
{
struct local_names *names;
sig_mutex_lock(&ln_lock);
names = __get_local_names1();
sig_mutex_unlock(&ln_lock);
return (names);
}
static struct local_names *
__get_local_names1(void)
{
char *t;
if (ln != NULL) {
/* Second and subsequent calls go this way */
return (ln);
}
/* First call goes this way */
ln = calloc(1, sizeof (*ln));
if (ln == NULL) {
syslog(LOG_ERR, "__get_local_names: Out of heap.");
return (NULL);
}
ln->principal_map = NULL;
if (sysinfo(SI_SRPC_DOMAIN, ln->domain, LN_BUFSIZE) < 0)
return (ln);
/* If no dot exists, add one. */
if (ln->domain[strlen(ln->domain)-1] != '.')
(void) strcat(ln->domain, ".");
if (sysinfo(SI_HOSTNAME, ln->host, LN_BUFSIZE) < 0)
return (ln);
/*
* Check for fully qualified hostname. If it's a fully qualified
* hostname, strip off the domain part. We always use the local
* domainname for the host principal name.
*/
t = strchr(ln->host, '.');
if (t)
*t = 0;
if (ln->domain[0] != '.')
(void) strcat(ln->host, ".");
ln->rpcdomain = strdup(ln->domain);
(void) strcat(ln->host, ln->domain);
t = getenv("NIS_GROUP");
if (t == NULL) {
ln->group[0] = '\0';
} else {
size_t maxlen = LN_BUFSIZE-1; /* max chars to copy */
char *temp; /* temp marker */
/*
* Copy <= maximum characters from NIS_GROUP; strncpy()
* doesn't terminate, so we do that manually. #1223323
* Also check to see if it's "". If it's the null string,
* we return because we don't want to add ".domain".
*/
(void) strncpy(ln->group, t, maxlen);
if (strcmp(ln->group, "") == 0) {
return (ln);
}
ln->group[maxlen] = '\0';
/* Is the group name somewhat fully-qualified? */
temp = strrchr(ln->group, '.');
/* If not, we need to add ".domain" to the group */
if ((temp == NULL) || (temp[1] != '\0')) {
/* truncate to make room for ".domain" */
ln->group[maxlen - (strlen(ln->domain)+1)] = '\0';
/* concat '.' if domain doesn't already have it */
if (ln->domain[0] != '.') {
(void) strcat(ln->group, ".");
}
(void) strcat(ln->group, ln->domain);
}
}
return (ln);
}
/*
* nis_local_group()
*
* Return's the group name of the current user.
*/
nis_name
nis_local_group(void)
{
struct local_names *ln = __get_local_names();
/* LOCK NOTE: Warning, after initialization, "ln" is expected */
/* to stay constant, So no need to lock here. If this assumption */
/* is changed, this code must be protected. */
if (!ln)
return (NULL);
return (ln->group);
}
/*
* __nis_nextsep_of()
*
* This internal funtion will accept a pointer to a NIS name string and
* return a pointer to the next separator occurring in it (it will point
* just past the first label). It allows for labels to be "quoted" to
* prevent the the dot character within them to be interpreted as a
* separator, also the quote character itself can be quoted by using
* it twice. If the the name contains only one label and no trailing
* dot character, a pointer to the terminating NULL is returned.
*/
nis_name
__nis_nextsep_of(char *s)
{
char *d;
int in_quotes = FALSE, quote_quote = FALSE;
if (!s)
return (NULL);
for (d = s; (in_quotes && (*d != '\0')) ||
(!in_quotes && (*d != '.') && (*d != '\0')); d++) {
if (quote_quote && in_quotes && (*d != '"')) {
quote_quote = FALSE;
in_quotes = FALSE;
if (*d == '.')
break;
} else if (quote_quote && in_quotes && (*d == '"')) {
quote_quote = FALSE;
} else if (quote_quote && (*d != '"')) {
quote_quote = FALSE;
in_quotes = TRUE;
} else if (quote_quote && (*d == '"')) {
quote_quote = FALSE;
} else if (in_quotes && (*d == '"')) {
quote_quote = TRUE;
} else if (!in_quotes && (*d == '"')) {
quote_quote = TRUE;
}
}
if (quote_quote || in_quotes) {
syslog(LOG_DEBUG, "__nis_nextsep_of: "
"Mismatched quotes in %s", s);
}
return (d);
}
/*
* nis_domain_of()
*
* This internal funtion will accept a pointer to a NIS name string and
* return a pointer to the "domain" part of it.
*
* ==== We don't need nis_domain_of_r(), but should we provide one for
* uniformity?
*/
nis_name
nis_domain_of(char *s)
{
char *d;
d = __nis_nextsep_of(s);
if (d == NULL)
return (NULL);
if (*d == '.')
d++;
if (*d == '\0') /* Don't return a zero length string */
return ("."); /* return root domain instead */
return (d);
}
/*
* nis_leaf_of()
*
* Returns the first label of a name. (other half of __domain_of)
*/
nis_name
nis_leaf_of_r(
const nis_name s,
char *buf,
size_t bufsize)
{
size_t nchars;
const char *d = __nis_nextsep_of((char *)s);
if (d == 0) {
return (0);
}
nchars = d - s;
if (bufsize < nchars + 1) {
return (0);
}
(void) strncpy(buf, s, nchars);
buf[nchars] = '\0';
return (buf);
}
static pthread_key_t buf_key = PTHREAD_ONCE_KEY_NP;
static char buf_main[LN_BUFSIZE];
nis_name
nis_leaf_of(char *s)
{
char *buf = thr_main()? buf_main :
thr_get_storage(&buf_key, LN_BUFSIZE, free);
if (buf == NULL)
return (NULL);
return (nis_leaf_of_r(s, buf, LN_BUFSIZE));
}
/*
* nis_name_of()
* This internal function will remove from the NIS name, the domain
* name of the current server, this will leave the unique part in
* the name this becomes the "internal" version of the name. If this
* function returns NULL then the name we were given to resolve is
* bad somehow.
* NB: Uses static storage and this is a no-no with threads. XXX
*/
nis_name
nis_name_of_r(
char *s, /* string with the name in it. */
char *buf,
size_t bufsize)
{
char *d;
struct local_names *ln = __get_local_names();
size_t dl, sl;
name_pos p;
#ifdef lint
bufsize = bufsize;
#endif /* lint */
if ((!s) || (!ln))
return (NULL); /* No string, this can't continue */
d = &(ln->domain[0]);
dl = strlen(ln->domain); /* _always dot terminated_ */
sl = strlen(s);
if (sl >= bufsize || (s[sl-1] != '.' && sl >= bufsize-1))
return (NULL);
(void) strcpy(buf, s); /* Make a private copy of 's' */
if (buf[sl-1] != '.') { /* Add a dot if necessary. */
(void) strcat(buf, ".");
sl++;
}
if (dl == 1) { /* We're the '.' directory */
buf[sl-1] = '\0'; /* Lose the 'dot' */
return (buf);
}
p = nis_dir_cmp(buf, d);
/* 's' is above 'd' in the tree */
if ((p == HIGHER_NAME) || (p == NOT_SEQUENTIAL) || (p == SAME_NAME))
return (NULL);
/* Insert a NUL where the domain name starts in the string */
buf[(sl - dl) - 1] = '\0';
/* Don't return a zero length name */
if (buf[0] == '\0')
return (NULL);
return (buf);
}
nis_name
nis_name_of(
char *s) /* string with the name in it. */
{
char *buf = thr_main()? buf_main :
thr_get_storage(&buf_key, LN_BUFSIZE, free);
if (!buf)
return (NULL);
return (nis_name_of_r(s, buf, LN_BUFSIZE));
}
/*
* nis_local_directory()
*
* Return a pointer to a string with the local directory name in it.
*/
nis_name
nis_local_directory(void)
{
struct local_names *ln = __get_local_names();
/* LOCK NOTE: Warning, after initialization, "ln" is expected */
/* to stay constant, So no need to lock here. If this assumption */
/* is changed, this code must be protected. */
if (ln == NULL)
return (NULL);
return (ln->domain);
}
/*
* __nis_rpc_domain()
*
* Return a pointer to a string with the rpc domain name in it.
*/
nis_name
__nis_rpc_domain()
{
struct local_names *ln = __get_local_names();
/* LOCK NOTE: Warning, after initialization, "ln" is expected */
/* to stay constant, So no need to lock here. If this assumption */
/* is changed, this code must be protected. */
if (ln == NULL)
return (NULL);
return (ln->rpcdomain);
}
/*
* nis_local_host()
* Generate the principal name for this host, "hostname"+"domainname"
* unless the hostname already has "dots" in its name.
*/
nis_name
nis_local_host(void)
{
struct local_names *ln = __get_local_names();
/* LOCK NOTE: Warning, after initialization, "ln" is expected */
/* to stay constant, So no need to lock here. If this assumption */
/* is changed, this code must be protected. */
if (ln == NULL)
return (NULL);
return (ln->host);
}
/*
* nis_destroy_object()
* This function takes a pointer to a NIS object and deallocates it. This
* is the inverse of __clone_object below. It must be able to correctly
* deallocate partially allocated objects because __clone_object will call
* it if it runs out of memory and has to abort. Everything is freed,
* INCLUDING the pointer that is passed.
*/
void
nis_destroy_object(nis_object *obj) /* The object to clone */
{
if (obj == 0)
return;
xdr_free(xdr_nis_object, (char *)obj);
free(obj);
} /* nis_destroy_object */
static void
destroy_nis_sdata(void *p)
{
struct nis_sdata *ns = p;
if (ns->buf != 0)
free(ns->buf);
free(ns);
}
/* XXX Why are these static ? */
/* static XDR in_xdrs, out_xdrs; */
/*
* __clone_object_r()
* This function takes a pointer to a NIS object and clones it. This
* duplicate object is now available for use in the local context.
*/
nis_object *
nis_clone_object_r(
nis_object *obj, /* The object to clone */
nis_object *dest, /* Use this pointer if non-null */
struct nis_sdata *clone_buf_ptr)
{
nis_object *result; /* The clone itself */
int status; /* a counter variable */
XDR in_xdrs, out_xdrs;
if (!nis_get_static_storage(clone_buf_ptr, 1,
xdr_sizeof(xdr_nis_object, obj)))
return (NULL);
(void) memset(&in_xdrs, 0, sizeof (in_xdrs));
(void) memset(&out_xdrs, 0, sizeof (out_xdrs));
xdrmem_create(&in_xdrs, clone_buf_ptr->buf, clone_buf_ptr->size,
XDR_ENCODE);
xdrmem_create(&out_xdrs, clone_buf_ptr->buf, clone_buf_ptr->size,
XDR_DECODE);
/* Allocate a basic NIS object structure */
if (dest) {
(void) memset(dest, 0, sizeof (nis_object));
result = dest;
} else
result = calloc(1, sizeof (nis_object));
if (result == NULL)
return (NULL);
/* Encode our object into the clone buffer */
(void) xdr_setpos(&in_xdrs, 0);
status = xdr_nis_object(&in_xdrs, obj);
if (status == FALSE)
return (NULL);
/* Now decode the buffer into our result pointer ... */
(void) xdr_setpos(&out_xdrs, 0);
status = xdr_nis_object(&out_xdrs, result);
if (status == FALSE)
return (NULL);
/* presto changeo, a new object */
return (result);
} /* __clone_object_r */
nis_object *
nis_clone_object(
nis_object *obj, /* The object to clone */
nis_object *dest) /* Use this pointer if non-null */
{
static pthread_key_t clone_buf_key = PTHREAD_ONCE_KEY_NP;
static struct nis_sdata clone_buf_main;
struct nis_sdata *clone_buf_ptr;
clone_buf_ptr = thr_main()? &clone_buf_main :
thr_get_storage(&clone_buf_key, sizeof (struct nis_sdata),
destroy_nis_sdata);
return (nis_clone_object_r(obj, dest, clone_buf_ptr));
} /* __clone_object */
/* Various subroutines used by the server code */
nis_object *
nis_read_obj(char *f) /* name of the object to read */
{
FILE *rootfile;
int status; /* Status of the XDR decoding */
XDR xdrs; /* An xdr stream handle */
nis_object *res;
res = calloc(1, sizeof (nis_object));
if (!res)
return (NULL);
rootfile = fopen(f, "rF");
if (rootfile == NULL) {
/* This is ok if we are the root of roots. */
free(res);
return (NULL);
}
/* Now read in the object */
xdrstdio_create(&xdrs, rootfile, XDR_DECODE);
status = xdr_nis_object(&xdrs, res);
xdr_destroy(&xdrs);
(void) fclose(rootfile);
if (!status) {
syslog(LOG_ERR, "Object file %s is corrupt!", f);
xdr_free(xdr_nis_object, (char *)res);
free(res);
return (NULL);
}
return (res);
}
int
nis_write_obj(
char *f, /* name of the object to read */
nis_object *o) /* The object to write */
{
FILE *rootfile;
int status; /* Status of the XDR decoding */
XDR xdrs; /* An xdr stream handle */
rootfile = fopen(f, "wF");
if (rootfile == NULL) {
return (0);
}
/* Now encode the object */
xdrstdio_create(&xdrs, rootfile, XDR_ENCODE);
status = xdr_nis_object(&xdrs, o);
xdr_destroy(&xdrs);
(void) fclose(rootfile);
return (status);
}
/*
* Transport INDEPENDENT RPC code. This code assumes you
* are using the new RPC/tli code and will build
* a ping handle on top of a datagram transport.
*/
/*
* __map_addr()
*
* This is our internal function that replaces rpcb_getaddr(). We
* build our own to prevent calling netdir_getbyname() which could
* recurse to the nameservice.
*/
static char *
__map_addr(
struct netconfig *nc, /* Our transport */
char *uaddr, /* RPCBIND address */
rpcprog_t prog, /* Name service Prog */
rpcvers_t ver)
{
CLIENT *client;
RPCB parms; /* Parameters for RPC binder */
enum clnt_stat clnt_st; /* Result from the rpc call */
char *ua = NULL; /* Universal address of service */
char *res = NULL; /* Our result to the parent */
struct timeval tv; /* Timeout for our rpcb call */
int ilen, olen; /* buffer length for clnt_tli_create */
/*
* If using "udp", use __nisipbufsize if inbuf and outbuf are set to 0.
*/
if (strcmp(NC_UDP, nc->nc_proto) == 0) {
/* for udp only */
ilen = olen = __nisipbufsize;
} else {
ilen = olen = 0;
}
client = __nis_clnt_create(RPC_ANYFD, nc, uaddr, 0, 0,
RPCBPROG, RPCBVERS, ilen, olen);
if (!client)
return (NULL);
(void) clnt_control(client, CLSET_FD_CLOSE, NULL);
/*
* Now make the call to get the NIS service address.
* We set the retry timeout to 3 seconds so that we
* will retry a few times. Retries should be rare
* because we are usually only called when we know
* a server is available.
*/
tv.tv_sec = 3;
tv.tv_usec = 0;
(void) clnt_control(client, CLSET_RETRY_TIMEOUT, (char *)&tv);
tv.tv_sec = 10;
tv.tv_usec = 0;
parms.r_prog = prog;
parms.r_vers = ver;
parms.r_netid = nc->nc_netid; /* not needed */
parms.r_addr = ""; /* not needed; just for xdring */
parms.r_owner = ""; /* not needed; just for xdring */
clnt_st = clnt_call(client, RPCBPROC_GETADDR, xdr_rpcb, (char *)&parms,
xdr_wrapstring, (char *)&ua, tv);
if (clnt_st == RPC_SUCCESS) {
clnt_destroy(client);
if (*ua == '\0') {
free(ua);
return (NULL);
}
res = strdup(ua);
xdr_free(xdr_wrapstring, (char *)&ua);
return (res);
} else if (((clnt_st == RPC_PROGVERSMISMATCH) ||
(clnt_st == RPC_PROGUNAVAIL)) &&
(strcmp(nc->nc_protofmly, NC_INET) == 0)) {
/*
* version 3 not available. Try version 2
* The assumption here is that the netbuf
* is arranged in the sockaddr_in
* style for IP cases.
*
* Note: If the remote host doesn't support version 3,
* we assume it doesn't know IPv6 either.
*/
ushort_t port;
struct sockaddr_in *sa;
struct netbuf remote;
int protocol;
char buf[32];
(void) clnt_control(client, CLGET_SVC_ADDR, (char *)&remote);
sa = (struct sockaddr_in *)(remote.buf);
protocol = strcmp(nc->nc_proto, NC_TCP) ?
IPPROTO_UDP : IPPROTO_TCP;
port = (ushort_t)pmap_getport(sa, prog, ver, protocol);
if (port != 0) {
port = htons(port);
(void) sprintf(buf, "%d.%d.%d.%d.%d.%d",
(sa->sin_addr.s_addr >> 24) & 0xff,
(sa->sin_addr.s_addr >> 16) & 0xff,
(sa->sin_addr.s_addr >> 8) & 0xff,
(sa->sin_addr.s_addr) & 0xff,
(port >> 8) & 0xff,
port & 0xff);
res = strdup(buf);
} else
res = NULL;
clnt_destroy(client);
return (res);
}
if (clnt_st == RPC_TIMEDOUT)
syslog(LOG_ERR, "NIS+ server not responding");
else
syslog(LOG_ERR, "NIS+ server could not be contacted: %s",
clnt_sperrno(clnt_st));
clnt_destroy(client);
return (NULL);
}
#define MAX_EP (20)
extern int __can_use_af(sa_family_t af);
CLIENT *
__nis_clnt_create(int fd, struct netconfig *nc, char *uaddr,
struct netbuf *addr, int domapaddr,
int prog, int ver, int inbuf, int outbuf) {
char *svc_addr;
CLIENT *clnt;
int freeaddr = 0;
/* Sanity check */
if (nc == 0 || (addr == 0 && uaddr == 0)) {
return (0);
}
/*
* Check if we have a useable interface for this address family.
* This check properly belongs in RPC (or even further down),
* but until they provide it, we roll our own.
*/
if (__can_use_af((strcmp(nc->nc_protofmly, NC_INET6) == 0) ?
AF_INET6 : AF_INET) == 0) {
return (0);
}
if (domapaddr) {
svc_addr = __map_addr(nc, uaddr, prog, ver);
if (svc_addr == 0)
return (0);
addr = uaddr2taddr(nc, svc_addr);
freeaddr = 1;
free(svc_addr);
} else if (addr == 0) {
addr = uaddr2taddr(nc, uaddr);
freeaddr = 1;
}
if (addr == 0) {
return (0);
}
clnt = clnt_tli_create(fd, nc, addr, prog, ver, outbuf, inbuf);
if (clnt) {
if (clnt_control(clnt, CLGET_FD, (char *)&fd))
/* make it "close on exec" */
(void) fcntl(fd, F_SETFD, FD_CLOEXEC);
(void) clnt_control(clnt, CLSET_FD_CLOSE, NULL);
}
if (freeaddr)
netdir_free(addr, ND_ADDR);
return (clnt);
}
static mutex_t __nis_ss_used_lock = DEFAULTMUTEX; /* lock level 3 */
int __nis_ss_used = 0;
/*
* nis_get_static_storage()
*
* This function is used by various functions in their effort to minimize the
* hassles of memory management in an RPC daemon. Because the service doesn't
* implement any hard limits, this function allows people to get automatically
* growing buffers that meet their storage requirements. It returns the
* pointer in the nis_sdata structure.
*
*/
void *
nis_get_static_storage(
struct nis_sdata *bs, /* User buffer structure */
uint_t el, /* Sizeof elements */
uint_t nel) /* Number of elements */
{
uint_t sz;
sz = nel * el;
if (!bs)
return (NULL);
if (!bs->buf) {
bs->buf = malloc(sz);
if (!bs->buf)
return (NULL);
bs->size = sz;
sig_mutex_lock(&__nis_ss_used_lock);
__nis_ss_used += sz;
sig_mutex_unlock(&__nis_ss_used_lock);
} else if (bs->size < sz) {
int size_delta;
free(bs->buf);
size_delta = - (bs->size);
bs->buf = malloc(sz);
/* check the result of malloc() first */
/* then update the statistic. */
if (!bs->buf)
return (NULL);
bs->size = sz;
size_delta += sz;
sig_mutex_lock(&__nis_ss_used_lock);
__nis_ss_used += size_delta;
sig_mutex_unlock(&__nis_ss_used_lock);
}
(void) memset(bs->buf, 0, sz); /* SYSV version of bzero() */
return (bs->buf);
}