xref: /illumos-gate/usr/src/lib/libnsl/rpc/netname.c (revision e8031f0a)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 
23 /*
24  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
25  * Use is subject to license terms.
26  */
27 /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */
28 /* All Rights Reserved */
29 /*
30  * Portions of this source code were derived from Berkeley
31  * 4.3 BSD under license from the Regents of the University of
32  * California.
33  */
34 /*
35  * ==== hack-attack:  possibly MT-safe but definitely not MT-hot.
36  * ==== turn this into a real switch frontend and backends
37  *
38  * Well, at least the API doesn't involve pointers-to-static.
39  */
40 
41 #pragma ident	"%Z%%M%	%I%	%E% SMI"
42 
43 /*
44  * netname utility routines (getnetname, user2netname, host2netname).
45  *
46  * Convert from unix names (uid, gid) to network wide names.
47  * This module is operating system dependent!
48  * What we define here will work with any unix system that has adopted
49  * the Sun NIS domain architecture.
50  */
51 
52 #undef NIS
53 
54 #include "mt.h"
55 #include "rpc_mt.h"
56 #include <stdio.h>
57 #include <stdlib.h>
58 #include <unistd.h>
59 #include <sys/types.h>
60 #include <ctype.h>
61 #include <string.h>
62 #include <syslog.h>
63 #include <sys/param.h>
64 #include <rpc/rpc.h>
65 #include <rpcsvc/nis.h>
66 #include <rpcsvc/nis_dhext.h>
67 #include <nsswitch.h>
68 #include <syslog.h>
69 
70 #ifndef MAXHOSTNAMELEN
71 #define	MAXHOSTNAMELEN 256
72 #endif
73 #ifndef NGROUPS
74 #define	NGROUPS 16
75 #endif
76 
77 /*
78  * the value for NOBODY_UID is set by the SVID. The following define also
79  * appears in netnamer.c
80  */
81 
82 #define	NOBODY_UID 60001
83 
84 extern int __nis_principal();
85 extern int getdomainname();
86 extern int key_call();
87 #define	OPSYS_LEN 4
88 #define	PKTABLE_LEN 12
89 static const char *OPSYS = "unix";
90 static const char *PKTABLE  = "cred.org_dir";
91 
92 
93 /*
94  * default publickey policy:
95  *	publickey: nis [NOTFOUND = return] files
96  */
97 
98 
99 /*	NSW_NOTSUCCESS  NSW_NOTFOUND   NSW_UNAVAIL    NSW_TRYAGAIN */
100 #define	DEF_ACTION {__NSW_RETURN, __NSW_RETURN, __NSW_CONTINUE, __NSW_CONTINUE}
101 
102 static struct __nsw_lookup lookup_files = {"files", DEF_ACTION, NULL, NULL},
103 		lookup_nis = {"nis", DEF_ACTION, NULL, &lookup_files};
104 static struct __nsw_switchconfig publickey_default =
105 			{0, "publickey", 2, &lookup_nis};
106 
107 static mutex_t serialize_netname = DEFAULTMUTEX;
108 
109 /*
110  * Convert unix cred to network-name using nisplus
111  * nisplus cred table has the following format:
112  *
113  * cname	auth_type auth_name	public  private
114  * ----------------------------------------------------------
115  * nisname	DES	  netname	pubkey  private_key
116  * nisname	LOCAL	  uid		gidlist
117  *
118  * Obtain netname given <uid,domain>.
119  * 0.  If domain is NULL (indicating local domain), first try to get
120  *	netname from keyserv (keylogin sets this).
121  * 1.  Get the nisplus principal name from the LOCAL entry of the cred
122  *	table in the specified domain (the local domain if domain is NULL).
123  * 2.  Using the principal name, lookup the DES entry and extract netname.
124  */
125 
126 static int
127 user2netname_nisplus(int *err, char netname[MAXNETNAMELEN + 1], uid_t uid,
128 								char *domain)
129 {
130 	key_netstres kres;
131 	nis_result *nres;
132 	int len;
133 	uid_t my_uid;
134 	char principal[NIS_MAXNAMELEN+1];
135 	char buf[NIS_MAXNAMELEN+1];
136 	int status;
137 	mechanism_t **mechs;
138 	char auth_type[MECH_MAXATNAME+1];
139 
140 	my_uid = geteuid();
141 
142 	if (my_uid == uid && domain == NULL) {
143 		/*
144 		 * Look up the keyserv interface routines to see if
145 		 * netname is stored there.
146 		 */
147 		kres.key_netstres_u.knet.st_netname = NULL;
148 		if (key_call((rpcproc_t)KEY_NET_GET, xdr_void, NULL,
149 				xdr_key_netstres, (char *)&kres) &&
150 		    kres.status == KEY_SUCCESS) {
151 			len = strlen(kres.key_netstres_u.knet.st_netname);
152 			(void) strncpy(netname,
153 				kres.key_netstres_u.knet.st_netname,
154 				len +1);
155 			free(kres.key_netstres_u.knet.st_netname);
156 			*err = __NSW_SUCCESS;
157 			return (1);
158 		}
159 	}
160 
161 
162 	/*
163 	 * 1.  Determine user's nis+ principal name.
164 	 *
165 	 * If domain is specified, we want to look up the uid in the
166 	 * specified domain to determine the user's principal name.
167 	 * Otherwise, get principal name from local directory.
168 	 */
169 	if (domain == NULL)
170 		domain = nis_local_directory();
171 	/*
172 	 * Don't use nis_local_principal here because we want to
173 	 * catch the TRYAGAIN case so that we handle it properly.
174 	 */
175 	status = __nis_principal(principal, uid, domain);
176 
177 	if (status != NIS_SUCCESS && status != NIS_S_SUCCESS) {
178 		switch (status) {
179 		case NIS_NOTFOUND:
180 		case NIS_PARTIAL:
181 		case NIS_NOSUCHNAME:
182 		case NIS_NOSUCHTABLE:
183 			*err = __NSW_NOTFOUND;
184 			break;
185 		case NIS_S_NOTFOUND:
186 		case NIS_TRYAGAIN:
187 			*err = __NSW_TRYAGAIN;
188 			syslog(LOG_ERR,
189 				"user2netname: (nis+ lookup): %s\n",
190 				nis_sperrno(status));
191 			break;
192 		default:
193 			*err = __NSW_UNAVAIL;
194 			syslog(LOG_ERR,
195 				"user2netname: (nis+ lookup): %s\n",
196 				nis_sperrno(status));
197 		}
198 
199 		return (0);
200 	}
201 
202 	/*
203 	 * 2.  use nis+ principal name to get netname by getting a PK entry.
204 	 *
205 	 * (Use NOAUTH to prevent recursion.)
206 	 */
207 	domain = nis_domain_of(principal);
208 	if ((strlen(principal)+strlen(domain)+PKTABLE_LEN+ 28) >
209 		    (size_t)NIS_MAXNAMELEN) {
210 		*err = __NSW_UNAVAIL;
211 		return (0);
212 	}
213 
214 	if (mechs = __nis_get_mechanisms(FALSE)) {
215 		mechanism_t **mpp;
216 
217 		/*
218 		 * Loop thru mechanism types till we find one in the
219 		 * cred table for this user.
220 		 */
221 		for (mpp = mechs; *mpp; mpp++) {
222 			mechanism_t *mp = *mpp;
223 
224 			if (AUTH_DES_COMPAT_CHK(mp)) {
225 				__nis_release_mechanisms(mechs);
226 				goto try_auth_des;
227 			}
228 			if (!VALID_MECH_ENTRY(mp))
229 				continue;
230 
231 			if (!__nis_mechalias2authtype(mp->alias, auth_type,
232 							sizeof (auth_type)))
233 				continue;
234 
235 			(void) snprintf(buf, sizeof (buf),
236 				"[cname=\"%s\",auth_type=\"%s\"],%s.%s",
237 				principal, auth_type, PKTABLE, domain);
238 			if (buf[strlen(buf)-1] != '.')
239 				(void) strcat(buf, ".");
240 
241 			nres = nis_list(buf,
242 				USE_DGRAM+NO_AUTHINFO+FOLLOW_LINKS+FOLLOW_PATH,
243 				NULL, NULL);
244 
245 			/*
246 			 * If the entry is not found, let's try the next one,
247 			 * else it's success or a serious enough NIS+ err
248 			 * to bail on.
249 			 */
250 			if (nres->status != NIS_NOTFOUND)
251 				break;
252 		}
253 	} else {
254 	try_auth_des:
255 		/*
256 		 * No valid mechs exist or the AUTH_DES compat entry was
257 		 * found in the security cf.
258 		 */
259 		(void) snprintf(buf, sizeof (buf),
260 					"[cname=\"%s\",auth_type=DES],%s.%s",
261 			principal, PKTABLE, domain);
262 		if (buf[strlen(buf)-1] != '.')
263 			(void) strcat(buf, ".");
264 
265 		nres = nis_list(buf,
266 				USE_DGRAM+NO_AUTHINFO+FOLLOW_LINKS+FOLLOW_PATH,
267 				NULL, NULL);
268 	}
269 
270 	switch (nres->status) {
271 	case NIS_SUCCESS:
272 	case NIS_S_SUCCESS:
273 		break;   /* go and do something useful */
274 	case NIS_NOTFOUND:
275 	case NIS_PARTIAL:
276 	case NIS_NOSUCHNAME:
277 	case NIS_NOSUCHTABLE:
278 		*err = __NSW_NOTFOUND;
279 		nis_freeresult(nres);
280 		return (0);
281 	case NIS_S_NOTFOUND:
282 	case NIS_TRYAGAIN:
283 		*err = __NSW_TRYAGAIN;
284 		syslog(LOG_ERR,
285 			"user2netname: (nis+ lookup): %s\n",
286 			nis_sperrno(nres->status));
287 		nis_freeresult(nres);
288 		return (0);
289 	default:
290 		*err = __NSW_UNAVAIL;
291 		syslog(LOG_ERR, "user2netname: (nis+ lookup): %s\n",
292 			nis_sperrno(nres->status));
293 		nis_freeresult(nres);
294 		return (0);
295 	}
296 
297 	if (nres->objects.objects_len > 1) {
298 		/*
299 		 * Principal with more than one entry for this mech type?
300 		 * Something wrong with cred table. Should be unique.
301 		 * Warn user and continue.
302 		 */
303 		syslog(LOG_ALERT,
304 			"user2netname: %s entry for %s not unique",
305 			auth_type, principal);
306 	}
307 
308 	len = ENTRY_LEN(nres->objects.objects_val, 2);
309 	if (len > MAXNETNAMELEN) {
310 		*err = __NSW_UNAVAIL;
311 		syslog(LOG_ERR, "user2netname: netname of '%s' too long",
312 			principal);
313 		nis_freeresult(nres);
314 		return (0);
315 	}
316 	(void) strncpy(netname, ENTRY_VAL(nres->objects.objects_val, 2), len);
317 	netname[len] = '\0';
318 	nis_freeresult(nres);
319 	*err = __NSW_SUCCESS;
320 	return (1);
321 }
322 
323 #define	MAXIPRINT	(11)	/* max length of printed integer */
324 
325 /*
326  * Convert unix cred to network-name by concatenating the
327  * 3 pieces of information <opsys type> <uid> <domain>.
328  */
329 
330 static int
331 user2netname_nis(int *err, char netname[MAXNETNAMELEN + 1], uid_t uid,
332 								char *domain)
333 {
334 	int i;
335 	char *dfltdom;
336 	if (domain == NULL) {
337 		if (__rpc_get_default_domain(&dfltdom) != 0) {
338 			*err = __NSW_UNAVAIL;
339 			return (0);
340 		}
341 		domain = dfltdom;
342 	}
343 	if ((strlen(domain) + OPSYS_LEN + 3 + MAXIPRINT) >
344 						(size_t)MAXNETNAMELEN) {
345 		*err = __NSW_UNAVAIL;
346 		return (0);
347 	}
348 	(void) snprintf(netname, MAXNETNAMELEN + 1,
349 					"%s.%d@%s", OPSYS, (int)uid, domain);
350 	i = strlen(netname);
351 	if (netname[i-1] == '.')
352 		netname[i-1] = '\0';
353 	*err = __NSW_SUCCESS;
354 	return (1);
355 }
356 
357 /*
358  * Figure out my fully qualified network name
359  */
360 int
361 getnetname(char name[MAXNETNAMELEN + 1])
362 {
363 	uid_t uid;
364 
365 	uid = geteuid();
366 	if (uid == 0)
367 		return (host2netname(name, NULL, NULL));
368 	return (user2netname(name, uid, NULL));
369 }
370 
371 
372 /*
373  * Figure out the fully qualified network name for the given uid.
374  * This is a private interface.
375  */
376 int
377 __getnetnamebyuid(char name[MAXNETNAMELEN + 1], uid_t uid)
378 {
379 	if (uid == 0)
380 		return (host2netname(name, NULL, NULL));
381 	return (user2netname(name, uid, NULL));
382 }
383 
384 /*
385  * Convert unix cred to network-name
386  *
387  * It uses the publickey policy in the /etc/nsswitch.conf file
388  * (Unless the netname is "nobody", which is special cased).
389  * If there is no publickey policy in /etc/nsswitch.conf,
390  * the default publickey policy is used, which is
391  *	publickey: nis [NOTFOUND=return] files
392  * Note that for the non-nisplus case, there is no failover
393  * so only the first entry would be relevant for those cases.
394  */
395 int
396 user2netname(char netname[MAXNETNAMELEN + 1], const uid_t uid,
397 							const char *domain)
398 {
399 	struct __nsw_switchconfig *conf;
400 	struct __nsw_lookup *look;
401 	int needfree = 1, res = 0;
402 	enum __nsw_parse_err perr;
403 	int err;
404 
405 	/*
406 	 * Take care of the special case of "nobody". If the uid is
407 	 * the value assigned by the SVID for nobody, return the string
408 	 * "nobody".
409 	 */
410 
411 	if (uid == NOBODY_UID) {
412 		(void) strcpy(netname, "nobody");
413 		return (1);
414 	}
415 
416 	netname[0] = '\0';  /* make null first (no need for memset) */
417 
418 	(void) mutex_lock(&serialize_netname);
419 
420 	conf = __nsw_getconfig("publickey", &perr);
421 	if (!conf) {
422 		conf = &publickey_default;
423 		needfree = 0;
424 	}
425 
426 	for (look = conf->lookups; look; look = look->next) {
427 		if (strcmp(look->service_name, "nisplus") == 0)
428 			res = user2netname_nisplus(&err,
429 						netname, uid, (char *)domain);
430 		/* ldap, nis, and files all do the same thing. */
431 		else if (strcmp(look->service_name, "ldap") == 0 ||
432 			strcmp(look->service_name, "nis") == 0 ||
433 			strcmp(look->service_name, "files") == 0)
434 			res = user2netname_nis(&err,
435 				netname, uid, (char *)domain);
436 		else {
437 			syslog(LOG_INFO,
438 				"user2netname: unknown nameservice \
439 					for publickey info '%s'\n",
440 				look->service_name);
441 			err = __NSW_UNAVAIL;
442 		}
443 		switch (look->actions[err]) {
444 			case __NSW_CONTINUE :
445 				break;
446 			case __NSW_RETURN :
447 				if (needfree)
448 					__nsw_freeconfig(conf);
449 				(void) mutex_unlock(&serialize_netname);
450 				return (res);
451 			default :
452 				syslog(LOG_ERR,
453 			"user2netname: Unknown action for nameservice '%s'",
454 			look->service_name);
455 			}
456 	}
457 	if (needfree)
458 		__nsw_freeconfig(conf);
459 	(void) mutex_unlock(&serialize_netname);
460 	return (0);
461 }
462 
463 
464 /*
465  * Convert host to network-name
466  * This routine returns following netnames given the host and domain
467  * arguments defined below: (domainname=y.z)
468  *	  Arguments
469  *	host	domain		netname
470  *	----	------		-------
471  *	-	-		unix.m@y.z (hostname=m)
472  *	-	a.b		unix.m@a.b (hostname=m)
473  *	-	-		unix.m@y.z (hostname=m.w.x)
474  *	-	a.b		unix.m@a.b (hostname=m.w.x)
475  *	h	-		unix.h@y.z
476  *	h	a.b		unix.h@a.b
477  *	h.w.x	-		unix.h@w.x
478  *	h.w.x	a.b		unix.h@a.b
479  */
480 int
481 host2netname(char netname[MAXNETNAMELEN + 1], const char *host,
482 							const char *domain)
483 {
484 	char *p;
485 	char hostname[MAXHOSTNAMELEN + 1];
486 	char domainname[MAXHOSTNAMELEN + 1];
487 	char *dot_in_host;
488 	int i;
489 	size_t len;
490 
491 	netname[0] = '\0';  /* make null first (no need for memset) */
492 
493 	if (host == NULL) {
494 		(void) strncpy(hostname, nis_local_host(), sizeof (hostname));
495 		p = (char *)strchr(hostname, '.');
496 		if (p) {
497 			*p++ = '\0';
498 			/* if no domain passed, use tail of nis_local_host() */
499 			if (domain == NULL) {
500 				domain = p;
501 			}
502 		}
503 	} else {
504 		len = strlen(host);
505 		if (len >= sizeof (hostname)) {
506 			return (0);
507 		}
508 		(void) strcpy(hostname, host);
509 	}
510 
511 	dot_in_host = (char *)strchr(hostname, '.');
512 	if (domain == NULL) {
513 		p = dot_in_host;
514 		if (p) {
515 			p = (char *)nis_domain_of(hostname);
516 			len = strlen(p);
517 			if (len >= sizeof (domainname)) {
518 				return (0);
519 			}
520 			(void) strcpy(domainname, p);
521 		} else {
522 			domainname[0] = NULL;
523 			if (getdomainname(domainname, MAXHOSTNAMELEN) < 0)
524 				return (0);
525 		}
526 	} else {
527 		len = strlen(domain);
528 		if (len >= sizeof (domainname)) {
529 			return (0);
530 		}
531 		(void) strcpy(domainname, domain);
532 	}
533 
534 	i = strlen(domainname);
535 	if (i == 0)
536 		/* No domainname */
537 		return (0);
538 	if (domainname[i - 1] == '.')
539 		domainname[i - 1] = 0;
540 
541 	if (dot_in_host) {  /* strip off rest of name */
542 		*dot_in_host = '\0';
543 	}
544 
545 	if ((strlen(domainname) + strlen(hostname) + OPSYS_LEN + 3)
546 	    > (size_t)MAXNETNAMELEN) {
547 		return (0);
548 	}
549 
550 	(void) snprintf(netname, MAXNETNAMELEN + 1,
551 				"%s.%s@%s", OPSYS, hostname, domainname);
552 	return (1);
553 }
554