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