xref: /illumos-gate/usr/src/lib/libnsl/rpc/netnamer.c (revision e8d80663e4f91871f843bb8ad9108dc0b76dfcf3)
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