Home | History | Annotate | Download | only in rpc
      1 /*
      2  * CDDL HEADER START
      3  *
      4  * The contents of this file are subject to the terms of the
      5  * Common Development and Distribution License (the "License").
      6  * You may not use this file except in compliance with the License.
      7  *
      8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
      9  * or http://www.opensolaris.org/os/licensing.
     10  * See the License for the specific language governing permissions
     11  * and limitations under the License.
     12  *
     13  * When distributing Covered Code, include this CDDL HEADER in each
     14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
     15  * If applicable, add the following below this CDDL HEADER, with the
     16  * fields enclosed by brackets "[]" replaced with your own identifying
     17  * information: Portions Copyright [yyyy] [name of copyright owner]
     18  *
     19  * CDDL HEADER END
     20  */
     21 
     22 /*
     23  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
     24  * Use is subject to license terms.
     25  */
     26 /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */
     27 /* All Rights Reserved */
     28 /*
     29  * Portions of this source code were derived from Berkeley
     30  * 4.3 BSD under license from the Regents of the University of
     31  * California.
     32  */
     33 /*
     34  * ==== hack-attack:  possibly MT-safe but definitely not MT-hot.
     35  * ==== turn this into a real switch frontend and backends
     36  *
     37  * Well, at least the API doesn't involve pointers-to-static.
     38  */
     39 
     40 /*
     41  * netname utility routines convert from netnames to unix names (uid, gid)
     42  *
     43  * This module is operating system dependent!
     44  * What we define here will work with any unix system that has adopted
     45  * the Sun NIS domain architecture.
     46  */
     47 
     48 #undef NIS
     49 #include "mt.h"
     50 #include "rpc_mt.h"
     51 #include <stdio.h>
     52 #include <stdlib.h>
     53 #include <unistd.h>
     54 #include <alloca.h>
     55 #include <sys/types.h>
     56 #include <ctype.h>
     57 #include <grp.h>
     58 #include <pwd.h>
     59 #include <string.h>
     60 #include <syslog.h>
     61 #include <sys/param.h>
     62 #include <nsswitch.h>
     63 #include <rpc/rpc.h>
     64 #include <rpcsvc/nis.h>
     65 #include <rpcsvc/ypclnt.h>
     66 #include <nss_dbdefs.h>
     67 
     68 static const char    OPSYS[]	= "unix";
     69 static const char    NETIDFILE[] = "/etc/netid";
     70 static const char    NETID[]	= "netid.byname";
     71 #define	OPSYS_LEN 4
     72 
     73 extern int _getgroupsbymember(const char *, gid_t[], int, int);
     74 
     75 /*
     76  * the value for NOBODY_UID is set by the SVID. The following define also
     77  * appears in netname.c
     78  */
     79 
     80 #define	NOBODY_UID 60001
     81 
     82 /*
     83  *	default publickey policy:
     84  *		publickey: nis [NOTFOUND = return] files
     85  */
     86 
     87 
     88 /*		NSW_NOTSUCCESS  NSW_NOTFOUND   NSW_UNAVAIL    NSW_TRYAGAIN */
     89 #define	DEF_ACTION {__NSW_RETURN, __NSW_RETURN, __NSW_CONTINUE, __NSW_CONTINUE}
     90 
     91 static struct __nsw_lookup lookup_files = {"files", DEF_ACTION, NULL, NULL},
     92 		lookup_nis = {"nis", DEF_ACTION, NULL, &lookup_files};
     93 static struct __nsw_switchconfig publickey_default =
     94 			{0, "publickey", 2, &lookup_nis};
     95 
     96 static mutex_t serialize_netname_r = DEFAULTMUTEX;
     97 
     98 struct netid_userdata {
     99 	uid_t	*uidp;
    100 	gid_t	*gidp;
    101 	int	*gidlenp;
    102 	gid_t	*gidlist;
    103 };
    104 
    105 static int
    106 parse_uid(char *s, struct netid_userdata *argp)
    107 {
    108 	uid_t	u;
    109 
    110 	if (!s || !isdigit(*s)) {
    111 		syslog(LOG_ERR,
    112 		    "netname2user: expecting uid '%s'", s);
    113 		return (__NSW_NOTFOUND); /* xxx need a better error */
    114 	}
    115 
    116 	/* Fetch the uid */
    117 	u = (uid_t)(atoi(s));
    118 
    119 	if (u == 0) {
    120 		syslog(LOG_ERR, "netname2user: should not have uid 0");
    121 		return (__NSW_NOTFOUND);
    122 	}
    123 	*(argp->uidp) = u;
    124 	return (__NSW_SUCCESS);
    125 }
    126 
    127 
    128 /* parse a comma separated gid list */
    129 static int
    130 parse_gidlist(char *p, struct netid_userdata *argp)
    131 {
    132 	int len;
    133 	gid_t	g;
    134 
    135 	if (!p || (!isdigit(*p))) {
    136 		syslog(LOG_ERR,
    137 		    "netname2user: missing group id list in '%s'.",
    138 		    p);
    139 		return (__NSW_NOTFOUND);
    140 	}
    141 
    142 	g = (gid_t)(atoi(p));
    143 	*(argp->gidp) = g;
    144 
    145 	len = 0;
    146 	while (p = strchr(p, ','))
    147 		argp->gidlist[len++] = (gid_t)atoi(++p);
    148 	*(argp->gidlenp) = len;
    149 	return (__NSW_SUCCESS);
    150 }
    151 
    152 
    153 /*
    154  * parse_netid_str()
    155  *
    156  * Parse uid and group information from the passed string.
    157  *
    158  * The format of the string passed is
    159  * 	uid:gid,grp,grp, ...
    160  *
    161  */
    162 static int
    163 parse_netid_str(char *s, struct netid_userdata *argp)
    164 {
    165 	char	*p;
    166 	int	err;
    167 
    168 	/* get uid */
    169 	err = parse_uid(s, argp);
    170 	if (err != __NSW_SUCCESS)
    171 		return (err);
    172 
    173 	/* Now get the group list */
    174 	p = strchr(s, ':');
    175 	if (!p) {
    176 		syslog(LOG_ERR,
    177 		    "netname2user: missing group id list in '%s'", s);
    178 		return (__NSW_NOTFOUND);
    179 	}
    180 	++p;			/* skip ':' */
    181 	err = parse_gidlist(p, argp);
    182 	return (err);
    183 }
    184 
    185 /*
    186  * netname2user_files()
    187  *
    188  * This routine fetches the netid information from the "files" nameservice.
    189  * ie /etc/netid.
    190  */
    191 static int
    192 netname2user_files(int *err, char *netname, struct netid_userdata *argp)
    193 {
    194 	char 	buf[512];	/* one line from the file */
    195 	char	*name;
    196 	char	*value;
    197 	char 	*res;
    198 	FILE	*fd;
    199 
    200 	fd = fopen(NETIDFILE, "rF");
    201 	if (fd == NULL) {
    202 		*err = __NSW_UNAVAIL;
    203 		return (0);
    204 	}
    205 	/*
    206 	 * for each line in the file parse it appropriately
    207 	 * file format is :
    208 	 *	netid	uid:grp,grp,grp # for users
    209 	 *	netid	0:hostname	# for hosts
    210 	 */
    211 	while (!feof(fd)) {
    212 		res = fgets(buf, 512, fd);
    213 		if (res == NULL)
    214 			break;
    215 
    216 		/* Skip comments and blank lines */
    217 		if ((*res == '#') || (*res == '\n'))
    218 			continue;
    219 
    220 		name = &(buf[0]);
    221 		while (isspace(*name))
    222 			name++;
    223 		if (*name == '\0')	/* blank line continue */
    224 			continue;
    225 		value = name;		/* will contain the value eventually */
    226 		while (!isspace(*value))
    227 			value++;
    228 		if (*value == '\0') {
    229 			syslog(LOG_WARNING,
    230 			    "netname2user: badly formatted line in %s.",
    231 			    NETIDFILE);
    232 			continue;
    233 		}
    234 		*value++ = '\0'; /* nul terminate the name */
    235 
    236 		if (strcasecmp(name, netname) == 0) {
    237 			(void) fclose(fd);
    238 			while (isspace(*value))
    239 				value++;
    240 			*err = parse_netid_str(value, argp);
    241 			return (*err == __NSW_SUCCESS);
    242 		}
    243 	}
    244 	(void) fclose(fd);
    245 	*err = __NSW_NOTFOUND;
    246 	return (0);
    247 }
    248 
    249 /*
    250  * netname2user_nis()
    251  *
    252  * This function reads the netid from the NIS (YP) nameservice.
    253  */
    254 static int
    255 netname2user_nis(int *err, char *netname, struct netid_userdata *argp)
    256 {
    257 	char *domain;
    258 	int yperr;
    259 	char *lookup;
    260 	int len;
    261 
    262 	domain = strchr(netname, '@');
    263 	if (!domain) {
    264 		*err = __NSW_UNAVAIL;
    265 		return (0);
    266 	}
    267 
    268 	/* Point past the '@' character */
    269 	domain++;
    270 	lookup = NULL;
    271 	yperr = yp_match(domain, (char *)NETID, netname, strlen(netname),
    272 	    &lookup, &len);
    273 	switch (yperr) {
    274 		case 0:
    275 			break; /* the successful case */
    276 
    277 		default :
    278 			/*
    279 			 *  XXX not sure about yp_match semantics.
    280 			 * should err be set to NOTFOUND here?
    281 			 */
    282 			*err = __NSW_UNAVAIL;
    283 			return (0);
    284 	}
    285 	if (lookup) {
    286 		lookup[len] = '\0';
    287 		*err = parse_netid_str(lookup, argp);
    288 		free(lookup);
    289 		return (*err == __NSW_SUCCESS);
    290 	}
    291 	*err = __NSW_NOTFOUND;
    292 	return (0);
    293 }
    294 
    295 /*
    296  * Build the uid and gid from the netname for users in LDAP.
    297  * There is no netid container in LDAP. For this we build
    298  * the netname to user data dynamically from the passwd and
    299  * group data. This works only for users in a single domain.
    300  * This function is an interim solution until we support a
    301  * netid container in LDAP which enables us to do netname2user
    302  * resolution for multiple domains.
    303  */
    304 static int
    305 netname2user_ldap(int *err, char *netname, struct netid_userdata *argp)
    306 {
    307 	char buf[NSS_LINELEN_PASSWD];
    308 	char *p2, *lasts;
    309 	struct passwd pw;
    310 	uid_t uidnu;
    311 	int ngroups = 0;
    312 	int count;
    313 	char pwbuf[NSS_LINELEN_PASSWD];
    314 	int maxgrp = sysconf(_SC_NGROUPS_MAX);
    315 	gid_t *groups = alloca(maxgrp * sizeof (gid_t));
    316 
    317 	if (strlcpy(buf, netname, NSS_LINELEN_PASSWD) >= NSS_LINELEN_PASSWD) {
    318 		*err = __NSW_UNAVAIL;
    319 		return (0);
    320 	}
    321 
    322 	/* get the uid from the netname */
    323 	if (strtok_r(buf, ".", &lasts) == NULL) {
    324 		*err = __NSW_UNAVAIL;
    325 		return (0);
    326 	}
    327 	if ((p2 = strtok_r(NULL, "@", &lasts)) == NULL) {
    328 		*err = __NSW_UNAVAIL;
    329 		return (0);
    330 	}
    331 	uidnu = atoi(p2);
    332 
    333 	/*
    334 	 * check out the primary group and crosscheck the uid
    335 	 * with the passwd data
    336 	 */
    337 	if ((getpwuid_r(uidnu, &pw, pwbuf, sizeof (pwbuf))) == NULL) {
    338 		*err = __NSW_UNAVAIL;
    339 		return (0);
    340 	}
    341 
    342 	*(argp->uidp) = pw.pw_uid;
    343 	*(argp->gidp) = pw.pw_gid;
    344 
    345 	/* search through all groups for membership */
    346 
    347 	groups[0] = pw.pw_gid;
    348 
    349 	ngroups = _getgroupsbymember(pw.pw_name, groups, maxgrp,
    350 	    (pw.pw_gid <= MAXUID) ? 1 : 0);
    351 
    352 	if (ngroups < 0) {
    353 		*err = __NSW_UNAVAIL;
    354 		return (0);
    355 	}
    356 
    357 	*(argp->gidlenp) = ngroups;
    358 
    359 	for (count = 0; count < ngroups; count++) {
    360 		(argp->gidlist[count]) = groups[count];
    361 	}
    362 
    363 	*err = __NSW_SUCCESS;
    364 	return (1);
    365 
    366 }
    367 
    368 /*
    369  * Convert network-name into unix credential
    370  */
    371 int
    372 netname2user(const char netname[MAXNETNAMELEN + 1], uid_t *uidp, gid_t *gidp,
    373 						int *gidlenp, gid_t *gidlist)
    374 {
    375 	struct __nsw_switchconfig *conf;
    376 	struct __nsw_lookup *look;
    377 	enum __nsw_parse_err perr;
    378 	int needfree = 1, res;
    379 	struct netid_userdata argp;
    380 	int err;
    381 
    382 	/*
    383 	 * Take care of the special case of nobody. Compare the netname
    384 	 * to the string "nobody". If they are equal, return the SVID
    385 	 * standard value for nobody.
    386 	 */
    387 
    388 	if (strcmp(netname, "nobody") == 0) {
    389 		*uidp = NOBODY_UID;
    390 		*gidp = NOBODY_UID;
    391 		*gidlenp = 0;
    392 		return (1);
    393 	}
    394 
    395 	/*
    396 	 * First we do some generic sanity checks on the name we were
    397 	 * passed. This lets us assume they are correct in the backends.
    398 	 *
    399 	 * NOTE: this code only recognizes names of the form :
    400 	 *		unix.UID@domainname
    401 	 */
    402 	if (strncmp(netname, OPSYS, OPSYS_LEN) != 0)
    403 		return (0);
    404 	if (!isdigit(netname[OPSYS_LEN+1]))	/* check for uid string */
    405 		return (0);
    406 
    407 	argp.uidp = uidp;
    408 	argp.gidp = gidp;
    409 	argp.gidlenp = gidlenp;
    410 	argp.gidlist = gidlist;
    411 	(void) mutex_lock(&serialize_netname_r);
    412 
    413 	conf = __nsw_getconfig("publickey", &perr);
    414 	if (!conf) {
    415 		conf = &publickey_default;
    416 		needfree = 0;
    417 	} else
    418 		needfree = 1; /* free the config structure */
    419 
    420 	for (look = conf->lookups; look; look = look->next) {
    421 		if (strcmp(look->service_name, "nis") == 0)
    422 			res = netname2user_nis(&err, (char *)netname, &argp);
    423 		else if (strcmp(look->service_name, "files") == 0)
    424 			res = netname2user_files(&err, (char *)netname, &argp);
    425 		else if (strcmp(look->service_name, "ldap") == 0)
    426 			res = netname2user_ldap(&err, (char *)netname, &argp);
    427 		else {
    428 			syslog(LOG_INFO,
    429 			    "netname2user: unknown nameservice for publickey"
    430 			    "info '%s'\n", look->service_name);
    431 			err = __NSW_UNAVAIL;
    432 		}
    433 		switch (look->actions[err]) {
    434 			case __NSW_CONTINUE :
    435 				break;
    436 			case __NSW_RETURN :
    437 				if (needfree)
    438 					(void) __nsw_freeconfig(conf);
    439 				(void) mutex_unlock(&serialize_netname_r);
    440 				return (res);
    441 			default :
    442 				syslog(LOG_ERR,
    443 				    "netname2user: Unknown action for "
    444 				    "nameservice '%s'", look->service_name);
    445 		}
    446 	}
    447 	if (needfree)
    448 		(void) __nsw_freeconfig(conf);
    449 	(void) mutex_unlock(&serialize_netname_r);
    450 	return (0);
    451 }
    452 
    453 /*
    454  * Convert network-name to hostname (fully qualified)
    455  * NOTE: this code only recognizes names of the form :
    456  *		unix.HOST@domainname
    457  *
    458  * This is very simple.  Since the netname is of the form:
    459  *	unix.host@domainname
    460  * We just construct the hostname using information from the domainname.
    461  */
    462 int
    463 netname2host(const char netname[MAXNETNAMELEN + 1], char *hostname,
    464 							const int hostlen)
    465 {
    466 	char *p, *domainname;
    467 	int len, dlen;
    468 
    469 	if (!netname) {
    470 		syslog(LOG_ERR, "netname2host: null netname");
    471 		goto bad_exit;
    472 	}
    473 
    474 	if (strncmp(netname, OPSYS, OPSYS_LEN) != 0)
    475 		goto bad_netname;
    476 	p = (char *)netname + OPSYS_LEN;	/* skip OPSYS part */
    477 	if (*p != '.')
    478 		goto bad_netname;
    479 	++p;				/* skip '.' */
    480 
    481 	domainname = strchr(p, '@');	/* get domain name */
    482 	if (domainname == 0)
    483 		goto bad_netname;
    484 
    485 	len = domainname - p;		/* host sits between '.' and '@' */
    486 	domainname++;			/* skip '@' sign */
    487 
    488 	if (len <= 0)
    489 		goto bad_netname;
    490 
    491 	if (hostlen < len) {
    492 		syslog(LOG_ERR,
    493 		    "netname2host: insufficient space for hostname");
    494 		goto bad_exit;
    495 	}
    496 
    497 	if (isdigit(*p))		/* don't want uid here */
    498 		goto bad_netname;
    499 
    500 	if (*p == '\0')			/* check for null hostname */
    501 		goto bad_netname;
    502 
    503 	(void) strncpy(hostname, p, len);
    504 
    505 	/* make into fully qualified hostname by concatenating domain part */
    506 	dlen = strlen(domainname);
    507 	if (hostlen < (len + dlen + 2)) {
    508 		syslog(LOG_ERR,
    509 		    "netname2host: insufficient space for hostname");
    510 		goto bad_exit;
    511 	}
    512 
    513 	hostname[len] = '.';
    514 	(void) strncpy(hostname+len+1, domainname, dlen);
    515 	hostname[len+dlen+1] = '\0';
    516 
    517 	return (1);
    518 
    519 bad_netname:
    520 	syslog(LOG_ERR, "netname2host: invalid host netname %s", netname);
    521 
    522 bad_exit:
    523 	hostname[0] = '\0';
    524 	return (0);
    525 }
    526