/* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * This module contains the subroutines used by the server to manipulate * objects and names. */ #include "mt.h" #include #include #include #include #include #include #include #include #include #include #include #include /* Must be ahead of rpcb_clnt.h */ #include #include #include #include #include #include #include #include #include "nis_clnt.h" #include #include #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); /* LINTED pointer cast */ 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); }