xref: /illumos-gate/usr/src/lib/passwdutil/switch_utils.c (revision 46b592853d0f4f11781b6b0a7533f267c6aee132)
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  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #include <sys/types.h>
27 #include <nsswitch.h>
28 #include <stdlib.h>
29 #include <stdio.h>
30 #include <string.h>
31 #include <syslog.h>
32 #include <stdlib.h>
33 #include <unistd.h>
34 
35 #include "ns_sldap.h"
36 #include <nss_dbdefs.h>
37 #include <nsswitch.h>
38 #include <pwd.h>
39 #include <shadow.h>
40 #include <rpcsvc/nis.h>
41 
42 #include "passwdutil.h"
43 
44 static struct passwd *nisplus_getpw_from_master(const char *, char *);
45 static struct spwd *nisplus_getsp_from_master(const char *, char *);
46 
47 /*
48  * name_to_int(rep)
49  *
50  * Translate the repository to a bitmask.
51  * if we don't recognise the repository name, we return REP_ERANGE
52  */
53 int
54 name_to_int(char *rep_name)
55 {
56 	int result = REP_ERANGE;
57 
58 	if (strcmp(rep_name, "files") == 0)
59 		result = REP_FILES;
60 	else if (strcmp(rep_name, "nis") == 0)
61 		result = REP_NIS;
62 	else if (strcmp(rep_name, "nisplus") == 0)
63 		result = REP_NISPLUS;
64 	else if (strcmp(rep_name, "ldap") == 0)
65 		result = REP_LDAP;
66 	else if (strcmp(rep_name, "compat") == 0) {
67 		struct __nsw_switchconfig *cfg;
68 		enum   __nsw_parse_err pserr;
69 
70 		cfg = __nsw_getconfig("passwd_compat", &pserr);
71 		if (cfg == NULL) {
72 			result = REP_FILES | REP_NIS;
73 		} else {
74 			if (strcmp(cfg->lookups->service_name, "nisplus") == 0)
75 				result = REP_FILES | REP_NISPLUS;
76 			else if (strcmp(cfg->lookups->service_name, "ldap") ==
77 			    0)
78 				result = REP_FILES | REP_LDAP;
79 			else
80 				result = REP_ERANGE;
81 			__nsw_freeconfig(cfg);
82 		}
83 	}
84 
85 	return (result);
86 }
87 
88 /*
89  * Figure out which repository we use in compat mode.
90  */
91 int
92 get_compat_mode(void)
93 {
94 	struct __nsw_switchconfig *cfg;
95 	enum   __nsw_parse_err pserr;
96 	int result = REP_COMPAT_NIS;
97 
98 	if ((cfg = __nsw_getconfig("passwd_compat", &pserr)) != NULL) {
99 		if (strcmp(cfg->lookups->service_name, "nisplus") == 0)
100 			result = REP_COMPAT_NISPLUS;
101 		else if (strcmp(cfg->lookups->service_name, "ldap") == 0)
102 			result = REP_COMPAT_LDAP;
103 	}
104 	__nsw_freeconfig(cfg);
105 
106 	return (result);
107 }
108 
109 /*
110  * get_ns(rep, accesstype)
111  *
112  * returns a bitmask of repositories to use based on either
113  *   1. the repository that is given as argument
114  *   2. the nsswitch.conf file
115  *   3. the type of access requested
116  *
117  * "accesstype" indicates whether we are reading from or writing to the
118  * repository. We need to know this since "compat" will translate into
119  * REP_NSS (the nss-switch) for READ access (needed to decode
120  * the black-magic '+' entries) but it translates into a bitmask
121  * on WRITE access.
122  *
123  * If we detect read-access in compat mode, we augment the result
124  * with one of REP_COMPAT_{NIS,NISPLUS,LDAP}. We need this in order to
125  * implement ATTR_REP_NAME in nss_getpwnam.
126  *
127  * A return value of REP_NOREP indicates an error.
128  */
129 int
130 get_ns(pwu_repository_t *rep, int accesstype)
131 {
132 	struct __nsw_switchconfig *conf = NULL;
133 	enum __nsw_parse_err pserr;
134 	struct __nsw_lookup *lkp;
135 	struct __nsw_lookup *lkp2;
136 	struct __nsw_lookup *lkp3;
137 	struct __nsw_lookup *lkpn;
138 	int result = REP_NOREP;
139 
140 	if (rep != PWU_DEFAULT_REP) {
141 		result = name_to_int(rep->type);
142 		return (result);
143 	}
144 
145 	conf = __nsw_getconfig("passwd", &pserr);
146 	if (conf == NULL) {
147 		/*
148 		 * No config found. The user didn't supply a repository,
149 		 * so we try to change the password in the default
150 		 * repositories (files and nis) even though we cannot
151 		 * find the name service switch entry. (Backward compat)
152 		 */
153 		syslog(LOG_ERR, "passwdutil.so: nameservice switch entry for "
154 		    "passwd not found.");
155 		result = REP_FILES | REP_NIS;
156 		return (result);
157 	}
158 
159 	lkp = conf->lookups;
160 
161 	/*
162 	 * Supported nsswitch.conf can have a maximum of 3 repositories.
163 	 * If we encounter an unsupported nsswitch.conf, we return REP_NSS
164 	 * to fall back to the nsswitch backend.
165 	 *
166 	 * Note that specifying 'ad' in the configuration is acceptable
167 	 * though changing AD users' passwords through passwd(1) is not.
168 	 * Therefore "ad" will be silently ignored.
169 	 */
170 	if (conf->num_lookups == 1) {
171 		/* files or compat */
172 
173 		if (strcmp(lkp->service_name, "files") == 0) {
174 			result = name_to_int(lkp->service_name);
175 		} else if (strcmp(lkp->service_name, "compat") == 0) {
176 			if (accesstype == PWU_READ)
177 				result = REP_NSS | get_compat_mode();
178 			else
179 				result = name_to_int(lkp->service_name);
180 		} else
181 			result = REP_NSS;
182 
183 	} else if (conf->num_lookups == 2) {
184 		lkp2 = lkp->next;
185 		if (strcmp(lkp->service_name, "files") == 0) {
186 			result = REP_FILES;
187 			if (strcmp(lkp2->service_name, "ldap") == 0)
188 				result |= REP_LDAP;
189 			else if (strcmp(lkp2->service_name, "nis") == 0)
190 				result |= REP_NIS;
191 			else if (strcmp(lkp2->service_name, "nisplus") == 0)
192 				result |= REP_NISPLUS;
193 			else if (strcmp(lkp2->service_name, "ad") != 0)
194 				result = REP_NSS;
195 			/* AD is ignored */
196 		} else {
197 			result = REP_NSS;
198 		}
199 	} else if (conf->num_lookups == 3) {
200 		/*
201 		 * Valid configurations with 3 repositories are:
202 		 *   files ad [nis | ldap | nisplus] OR
203 		 *   files [nis | ldap | nisplus] ad
204 		 */
205 		lkp2 = lkp->next;
206 		lkp3 = lkp2->next;
207 		if (strcmp(lkp2->service_name, "ad") == 0)
208 			lkpn = lkp3;
209 		else if (strcmp(lkp3->service_name, "ad") == 0)
210 			lkpn = lkp2;
211 		else
212 			lkpn = NULL;
213 		if (strcmp(lkp->service_name, "files") == 0 &&
214 		    lkpn != NULL) {
215 			result = REP_FILES;
216 			if (strcmp(lkpn->service_name, "ldap") == 0)
217 				result |= REP_LDAP;
218 			else if (strcmp(lkpn->service_name, "nis") == 0)
219 				result |= REP_NIS;
220 			else if (strcmp(lkpn->service_name, "nisplus") == 0)
221 				result |= REP_NISPLUS;
222 			else
223 				result = REP_NSS;
224 		} else {
225 			result = REP_NSS;
226 		}
227 	} else {
228 		result = REP_NSS;
229 	}
230 
231 	__nsw_freeconfig(conf);
232 	return (result);
233 }
234 
235 static void
236 nss_ldap_passwd(p)
237 	nss_db_params_t	*p;
238 {
239 	p->name = NSS_DBNAM_PASSWD;
240 	p->flags |= NSS_USE_DEFAULT_CONFIG;
241 	p->default_config = "ldap";
242 }
243 
244 static void
245 nss_ldap_shadow(p)
246 	nss_db_params_t	*p;
247 {
248 	p->name = NSS_DBNAM_SHADOW;
249 	p->config_name    = NSS_DBNAM_PASSWD;	/* Use config for "passwd" */
250 	p->flags |= NSS_USE_DEFAULT_CONFIG;
251 	p->default_config = "ldap";
252 }
253 
254 
255 #ifdef PAM_NIS
256 static void
257 nss_nis_passwd(p)
258 	nss_db_params_t	*p;
259 {
260 	p->name = NSS_DBNAM_PASSWD;
261 	p->flags |= NSS_USE_DEFAULT_CONFIG;
262 	p->default_config = "nis";
263 }
264 
265 static void
266 nss_nis_shadow(p)
267 	nss_db_params_t	*p;
268 {
269 	p->name = NSS_DBNAM_SHADOW;
270 	p->config_name    = NSS_DBNAM_PASSWD;	/* Use config for "passwd" */
271 	p->flags |= NSS_USE_DEFAULT_CONFIG;
272 	p->default_config = "nis";
273 }
274 #endif /* PAM_NIS */
275 
276 
277 static void
278 nss_nisplus_passwd(p)
279 	nss_db_params_t	*p;
280 {
281 	p->name = NSS_DBNAM_PASSWD;
282 	p->flags |= NSS_USE_DEFAULT_CONFIG;
283 	p->default_config = "nisplus";
284 }
285 
286 static void
287 nss_nisplus_shadow(p)
288 	nss_db_params_t	*p;
289 {
290 	p->name = NSS_DBNAM_SHADOW;
291 	p->config_name    = NSS_DBNAM_PASSWD;	/* Use config for "passwd" */
292 	p->flags |= NSS_USE_DEFAULT_CONFIG;
293 	p->default_config = "nisplus";
294 }
295 
296 
297 static char *
298 gettok(nextpp)
299 	char	**nextpp;
300 {
301 	char	*p = *nextpp;
302 	char	*q = p;
303 	char	c;
304 
305 	if (p == 0) {
306 		return (0);
307 	}
308 	while ((c = *q) != '\0' && c != ':') {
309 		q++;
310 	}
311 	if (c == '\0') {
312 		*nextpp = 0;
313 	} else {
314 		*q++ = '\0';
315 		*nextpp = q;
316 	}
317 	return (p);
318 }
319 
320 /*
321  * Return values: 0 = success, 1 = parse error, 2 = erange ...
322  * The structure pointer passed in is a structure in the caller's space
323  * wherein the field pointers would be set to areas in the buffer if
324  * need be. instring and buffer should be separate areas.
325  */
326 static int
327 str2passwd(const char *instr, int lenstr, void *ent, char *buffer, int buflen)
328 {
329 	struct passwd	*passwd	= (struct passwd *)ent;
330 	char		*p, *next;
331 	int		black_magic;	/* "+" or "-" entry */
332 
333 	if (lenstr + 1 > buflen) {
334 		return (NSS_STR_PARSE_ERANGE);
335 	}
336 	/*
337 	 * We copy the input string into the output buffer and
338 	 * operate on it in place.
339 	 */
340 	(void) memcpy(buffer, instr, lenstr);
341 	buffer[lenstr] = '\0';
342 
343 	next = buffer;
344 
345 	passwd->pw_name = p = gettok(&next);		/* username */
346 	if (*p == '\0') {
347 		/* Empty username;  not allowed */
348 		return (NSS_STR_PARSE_PARSE);
349 	}
350 	black_magic = (*p == '+' || *p == '-');
351 	if (black_magic) {
352 		passwd->pw_uid	= UID_NOBODY;
353 		passwd->pw_gid	= GID_NOBODY;
354 		/*
355 		 * pwconv tests pw_passwd and pw_age == NULL
356 		 */
357 		passwd->pw_passwd = "";
358 		passwd->pw_age	= "";
359 		/*
360 		 * the rest of the passwd entry is "optional"
361 		 */
362 		passwd->pw_comment = "";
363 		passwd->pw_gecos = "";
364 		passwd->pw_dir	= "";
365 		passwd->pw_shell = "";
366 	}
367 
368 	passwd->pw_passwd = p = gettok(&next);		/* password */
369 	if (p == 0) {
370 		if (black_magic)
371 			return (NSS_STR_PARSE_SUCCESS);
372 		else
373 			return (NSS_STR_PARSE_PARSE);
374 	}
375 	for (; *p != '\0'; p++) {			/* age */
376 		if (*p == ',') {
377 			*p++ = '\0';
378 			break;
379 		}
380 	}
381 	passwd->pw_age = p;
382 
383 	p = next;					/* uid */
384 	if (p == 0 || *p == '\0') {
385 		if (black_magic)
386 			return (NSS_STR_PARSE_SUCCESS);
387 		else
388 			return (NSS_STR_PARSE_PARSE);
389 	}
390 	if (!black_magic) {
391 		passwd->pw_uid = strtol(p, &next, 10);
392 		if (next == p) {
393 			/* uid field should be nonempty */
394 			return (NSS_STR_PARSE_PARSE);
395 		}
396 		/*
397 		 * The old code (in 2.0 thru 2.5) would check
398 		 * for the uid being negative, or being greater
399 		 * than 60001 (the rfs limit).  If it met either of
400 		 * these conditions, the uid was translated to 60001.
401 		 *
402 		 * Now we just check for ephemeral uids; anything else
403 		 * is administrative policy
404 		 */
405 		if (passwd->pw_uid > MAXUID)
406 			passwd->pw_uid = UID_NOBODY;
407 	}
408 	if (*next++ != ':') {
409 		if (black_magic)
410 			p = gettok(&next);
411 		else
412 			return (NSS_STR_PARSE_PARSE);
413 	}
414 	p = next;					/* gid */
415 	if (p == 0 || *p == '\0') {
416 		if (black_magic)
417 			return (NSS_STR_PARSE_SUCCESS);
418 		else
419 			return (NSS_STR_PARSE_PARSE);
420 	}
421 	if (!black_magic) {
422 		passwd->pw_gid = strtol(p, &next, 10);
423 		if (next == p) {
424 			/* gid field should be nonempty */
425 			return (NSS_STR_PARSE_PARSE);
426 		}
427 		/*
428 		 * gid should be non-negative; anything else
429 		 * is administrative policy.
430 		 */
431 		if (passwd->pw_gid > MAXUID)
432 			passwd->pw_gid = GID_NOBODY;
433 	}
434 	if (*next++ != ':') {
435 		if (black_magic)
436 			p = gettok(&next);
437 		else
438 			return (NSS_STR_PARSE_PARSE);
439 	}
440 
441 	passwd->pw_gecos = passwd->pw_comment = p = gettok(&next);
442 	if (p == 0) {
443 		if (black_magic)
444 			return (NSS_STR_PARSE_SUCCESS);
445 		else
446 			return (NSS_STR_PARSE_PARSE);
447 	}
448 
449 	passwd->pw_dir = p = gettok(&next);
450 	if (p == 0) {
451 		if (black_magic)
452 			return (NSS_STR_PARSE_SUCCESS);
453 		else
454 			return (NSS_STR_PARSE_PARSE);
455 	}
456 
457 	passwd->pw_shell = p = gettok(&next);
458 	if (p == 0) {
459 		if (black_magic)
460 			return (NSS_STR_PARSE_SUCCESS);
461 		else
462 			return (NSS_STR_PARSE_PARSE);
463 	}
464 
465 	/* Better not be any more fields... */
466 	if (next == 0) {
467 		/* Successfully parsed and stored */
468 		return (NSS_STR_PARSE_SUCCESS);
469 	}
470 	return (NSS_STR_PARSE_PARSE);
471 }
472 
473 typedef const char *constp;
474 
475 /*
476  * Return value 1 means success and more input, 0 means error or no more
477  */
478 static int
479 getfield(nextp, limit, uns, valp)
480 	constp		*nextp;
481 	constp		limit;
482 	int		uns;
483 	void		*valp;
484 {
485 	constp		p = *nextp;
486 	char		*endfield;
487 	char		numbuf[12];  /* Holds -2^31 and trailing ':' */
488 	int		len;
489 	long		x;
490 	unsigned long	ux;
491 
492 	if (p == 0 || p >= limit) {
493 		return (0);
494 	}
495 	if (*p == ':') {
496 		p++;
497 		*nextp = p;
498 		return (p < limit);
499 	}
500 	if ((len = limit - p) > sizeof (numbuf) - 1) {
501 		len = sizeof (numbuf) - 1;
502 	}
503 	/*
504 	 * We want to use strtol() and we have a readonly non-zero-terminated
505 	 *   string, so first we copy and terminate the interesting bit.
506 	 *   Ugh.  (It's convenient to terminate with a colon rather than \0).
507 	 */
508 	if ((endfield = memccpy(numbuf, p, ':', len)) == 0) {
509 		if (len != limit - p) {
510 			/* Error -- field is too big to be a legit number */
511 			return (0);
512 		}
513 		numbuf[len] = ':';
514 		p = limit;
515 	} else {
516 		p += (endfield - numbuf);
517 	}
518 	if (uns) {
519 		ux = strtoul(numbuf, &endfield, 10);
520 		if (*endfield != ':') {
521 			/* Error -- expected <integer><colon> */
522 			return (0);
523 		}
524 		*((unsigned int *)valp) = (unsigned int)ux;
525 	} else {
526 		x = strtol(numbuf, &endfield, 10);
527 		if (*endfield != ':') {
528 			/* Error -- expected <integer><colon> */
529 			return (0);
530 		}
531 		*((int *)valp) = (int)x;
532 	}
533 	*nextp = p;
534 	return (p < limit);
535 }
536 
537 /*
538  *  str2spwd() -- convert a string to a shadow passwd entry.  The parser is
539  *	more liberal than the passwd or group parsers;  since it's legitimate
540  *	for almost all the fields here to be blank, the parser lets one omit
541  *	any number of blank fields at the end of the entry.  The acceptable
542  *	forms for '+' and '-' entries are the same as those for normal entries.
543  *  === Is this likely to do more harm than good?
544  *
545  * Return values: 0 = success, 1 = parse error, 2 = erange ...
546  * The structure pointer passed in is a structure in the caller's space
547  * wherein the field pointers would be set to areas in the buffer if
548  * need be. instring and buffer should be separate areas.
549  */
550 int
551 str2spwd(instr, lenstr, ent, buffer, buflen)
552 	const char	*instr;
553 	int		lenstr;
554 	void	*ent; /* really (struct spwd *) */
555 	char	*buffer;
556 	int	buflen;
557 {
558 	struct spwd	*shadow	= (struct spwd *)ent;
559 	const char	*p = instr, *limit;
560 	char		*bufp;
561 	int	lencopy, black_magic;
562 
563 	limit = p + lenstr;
564 	if ((p = memchr(instr, ':', lenstr)) == 0 ||
565 		++p >= limit ||
566 		(p = memchr(p, ':', limit - p)) == 0) {
567 		lencopy = lenstr;
568 		p = 0;
569 	} else {
570 		lencopy = p - instr;
571 		p++;
572 	}
573 	if (lencopy + 1 > buflen) {
574 		return (NSS_STR_PARSE_ERANGE);
575 	}
576 	(void) memcpy(buffer, instr, lencopy);
577 	buffer[lencopy] = 0;
578 
579 	black_magic = (*instr == '+' || *instr == '-');
580 	shadow->sp_namp = bufp = buffer;
581 	shadow->sp_pwdp	= 0;
582 	shadow->sp_lstchg = -1;
583 	shadow->sp_min	= -1;
584 	shadow->sp_max	= -1;
585 	shadow->sp_warn	= -1;
586 	shadow->sp_inact = -1;
587 	shadow->sp_expire = -1;
588 	shadow->sp_flag	= 0;
589 
590 	if ((bufp = strchr(bufp, ':')) == 0) {
591 		if (black_magic)
592 			return (NSS_STR_PARSE_SUCCESS);
593 		else
594 			return (NSS_STR_PARSE_PARSE);
595 	}
596 	*bufp++ = '\0';
597 
598 	shadow->sp_pwdp = bufp;
599 	if (instr == 0) {
600 		if ((bufp = strchr(bufp, ':')) == 0) {
601 			if (black_magic)
602 				return (NSS_STR_PARSE_SUCCESS);
603 			else
604 				return (NSS_STR_PARSE_PARSE);
605 		}
606 		*bufp++ = '\0';
607 		p = bufp;
608 	} /* else p was set when we copied name and passwd into the buffer */
609 
610 	if (!getfield(&p, limit, 0, &shadow->sp_lstchg))
611 			return (NSS_STR_PARSE_SUCCESS);
612 	if (!getfield(&p, limit, 0, &shadow->sp_min))
613 			return (NSS_STR_PARSE_SUCCESS);
614 	if (!getfield(&p, limit, 0, &shadow->sp_max))
615 			return (NSS_STR_PARSE_SUCCESS);
616 	if (!getfield(&p, limit, 0, &shadow->sp_warn))
617 			return (NSS_STR_PARSE_SUCCESS);
618 	if (!getfield(&p, limit, 0, &shadow->sp_inact))
619 			return (NSS_STR_PARSE_SUCCESS);
620 	if (!getfield(&p, limit, 0, &shadow->sp_expire))
621 			return (NSS_STR_PARSE_SUCCESS);
622 	if (!getfield(&p, limit, 1, &shadow->sp_flag))
623 			return (NSS_STR_PARSE_SUCCESS);
624 	if (p != limit) {
625 		/* Syntax error -- garbage at end of line */
626 		return (NSS_STR_PARSE_PARSE);
627 	}
628 	return (NSS_STR_PARSE_SUCCESS);
629 }
630 
631 static nss_XbyY_buf_t *buffer;
632 static DEFINE_NSS_DB_ROOT(db_root);
633 
634 #define	GETBUF()	\
635 	NSS_XbyY_ALLOC(&buffer, sizeof (struct passwd), NSS_BUFLEN_PASSWD)
636 
637 #pragma fini(endutilpwent)
638 
639 static void
640 endutilpwent(void)
641 {
642 	NSS_XbyY_FREE(&buffer);
643 	nss_delete(&db_root);
644 }
645 
646 struct passwd *
647 getpwnam_from(const char *name, pwu_repository_t *rep, int reptype)
648 {
649 	nss_XbyY_buf_t  *b = GETBUF();
650 	nss_XbyY_args_t arg;
651 
652 	if (b == 0)
653 		return (0);
654 
655 	NSS_XbyY_INIT(&arg, b->result, b->buffer, b->buflen, str2passwd);
656 	arg.key.name = name;
657 
658 	switch (reptype) {
659 	case REP_LDAP:
660 		(void) nss_search(&db_root, nss_ldap_passwd,
661 		    NSS_DBOP_PASSWD_BYNAME, &arg);
662 		break;
663 	case REP_NISPLUS:
664 		if (rep && rep->scope)
665 			return (nisplus_getpw_from_master(name, rep->scope));
666 
667 		(void) nss_search(&db_root, nss_nisplus_passwd,
668 		    NSS_DBOP_PASSWD_BYNAME, &arg);
669 		break;
670 #ifdef PAM_NIS
671 	case REP_NIS:
672 		(void) nss_search(&db_root, nss_nis_passwd,
673 		    NSS_DBOP_PASSWD_BYNAME, &arg);
674 		break;
675 #endif
676 	default:
677 		return (NULL);
678 	}
679 
680 	return (struct passwd *)NSS_XbyY_FINI(&arg);
681 }
682 
683 /*ARGSUSED*/
684 struct passwd *
685 getpwuid_from(uid_t uid, pwu_repository_t *rep, int reptype)
686 {
687 	nss_XbyY_buf_t  *b = GETBUF();
688 	nss_XbyY_args_t arg;
689 
690 	if (b == 0)
691 		return (0);
692 
693 	NSS_XbyY_INIT(&arg, b->result, b->buffer, b->buflen, str2passwd);
694 	arg.key.uid = uid;
695 
696 	switch (reptype) {
697 	case REP_LDAP:
698 		(void) nss_search(&db_root, nss_ldap_passwd,
699 		    NSS_DBOP_PASSWD_BYUID, &arg);
700 		break;
701 	case REP_NISPLUS:
702 		(void) nss_search(&db_root, nss_nisplus_passwd,
703 		    NSS_DBOP_PASSWD_BYUID, &arg);
704 		break;
705 #ifdef PAM_NIS
706 	case REP_NIS:
707 		(void) nss_search(&db_root, nss_nis_passwd,
708 		    NSS_DBOP_PASSWD_BYUID, &arg);
709 		break;
710 #endif
711 	default:
712 		return (NULL);
713 	}
714 
715 	return (struct passwd *)NSS_XbyY_FINI(&arg);
716 }
717 
718 static nss_XbyY_buf_t *spbuf;
719 static DEFINE_NSS_DB_ROOT(spdb_root);
720 
721 #define	GETSPBUF()	\
722 	NSS_XbyY_ALLOC(&spbuf, sizeof (struct spwd), NSS_BUFLEN_SHADOW)
723 
724 #pragma fini(endutilspent)
725 
726 static void
727 endutilspent(void)
728 {
729 	NSS_XbyY_FREE(&spbuf);
730 	nss_delete(&spdb_root);
731 }
732 
733 struct spwd *
734 getspnam_from(const char *name, pwu_repository_t *rep, int reptype)
735 {
736 	nss_XbyY_buf_t  *b = GETSPBUF();
737 	nss_XbyY_args_t arg;
738 
739 	if (b == 0)
740 		return (0);
741 
742 	NSS_XbyY_INIT(&arg, b->result, b->buffer, b->buflen, str2spwd);
743 	arg.key.name = name;
744 	switch (reptype) {
745 	case REP_LDAP:
746 		(void) nss_search(&spdb_root, nss_ldap_shadow,
747 		    NSS_DBOP_SHADOW_BYNAME, &arg);
748 		break;
749 	case REP_NISPLUS:
750 		if (rep && rep->scope)
751 			return (nisplus_getsp_from_master(name, rep->scope));
752 
753 		(void) nss_search(&spdb_root, nss_nisplus_shadow,
754 		    NSS_DBOP_SHADOW_BYNAME, &arg);
755 		break;
756 #ifdef PAM_NIS
757 	case REP_NIS:
758 		(void) nss_search(&spdb_root, nss_nis_shadow,
759 		    NSS_DBOP_SHADOW_BYNAME, &arg);
760 		break;
761 #endif
762 	default:
763 		return (NULL);
764 	}
765 	return (struct spwd *)NSS_XbyY_FINI(&arg);
766 }
767 
768 
769 static nis_result *
770 nisplus_match(const char *name, char *domain, char *buf, int len)
771 {
772 	int n;
773 	int flags;
774 	nis_result *res;
775 	nis_object *object;
776 
777 	n = snprintf(buf, len, "[name=%s],passwd.org_dir.%s", name, domain);
778 	if (n >= len) {
779 		syslog(LOG_ERR, "nisplus_match: name too long");
780 		return (NULL);
781 	}
782 	if (buf[n-1] != '.') {
783 		if (n == len-1) {
784 			syslog(LOG_ERR, "nisplus_match: name too long");
785 			return (NULL);
786 		}
787 		buf[n++] = '.';
788 		buf[n] = '\0';
789 	}
790 
791 	flags = USE_DGRAM | FOLLOW_LINKS | FOLLOW_PATH | MASTER_ONLY;
792 
793 	res = nis_list(buf, flags, NULL, NULL);
794 
795 	if (res == NULL) {
796 		syslog(LOG_ERR, "nisplus_match: nis_list returned NULL");
797 		return (NULL);
798 	}
799 
800 	if (NIS_RES_STATUS(res) != NIS_SUCCESS || NIS_RES_NUMOBJ(res) != 1) {
801 		syslog(LOG_ERR, "nisplus_match: match failed: %s",
802 		    nis_sperrno(NIS_RES_STATUS(res)));
803 		nis_freeresult(res);
804 		return (NULL);
805 	}
806 
807 	object = NIS_RES_OBJECT(res);
808 
809 	if (object->EN_data.en_cols.en_cols_len < 8) {
810 		syslog(LOG_ERR, "nisplus_match: "
811 		    "not a valid passwd table entry for user %s", name);
812 		nis_freeresult(res);
813 		return (NULL);
814 	}
815 
816 	return (res);
817 }
818 
819 #define	SAFE_STRDUP(dst, idx) \
820 	if ((idx) <= 3 && ENTRY_VAL(nret, (idx)) == NULL) { \
821 		syslog(LOG_ERR, \
822 		    "passwdutil: missing field from password entry"); \
823 		goto error; \
824 	} \
825 	len = ENTRY_LEN(nret, (idx)); \
826 	(dst) = malloc(len+1); \
827 	if ((dst) == NULL) { \
828 		syslog(LOG_ERR, "passwdutil: out of memory"); \
829 		goto error; \
830 	} \
831 	(dst)[len] = '\0'; \
832 	(void) strncpy((dst), \
833 	    ENTRY_VAL(nret, (idx)) ? ENTRY_VAL(nret, (idx)) : "", \
834 	    len);
835 
836 
837 
838 static struct passwd *
839 nisplus_getpw_from_master(const char *name, char *domain)
840 {
841 	char lookup[NIS_MAXNAMELEN+1];
842 	nis_result *res;
843 	nis_object *nret;
844 	int len;
845 	char *p;
846 	struct passwd *pw;
847 
848 	if ((pw = calloc(1, sizeof (*pw))) == NULL)
849 		return (NULL);
850 
851 	res = nisplus_match(name, domain, lookup, sizeof (lookup));
852 
853 	if (res == NULL)
854 		return (NULL);
855 
856 	nret = NIS_RES_OBJECT(res);
857 
858 	SAFE_STRDUP(pw->pw_name, 0);
859 
860 	if ((pw->pw_passwd = strdup("x")) == NULL) {
861 		syslog(LOG_ERR, "passwdutil: out of memory");
862 		goto error;
863 	}
864 
865 	SAFE_STRDUP(p, 2);
866 	pw->pw_uid = atoi(p);
867 	free(p);
868 
869 	SAFE_STRDUP(p, 3);
870 	pw->pw_gid = atoi(p);
871 	free(p);
872 
873 	pw->pw_age = NULL;
874 	pw->pw_comment = NULL;
875 
876 /*CONSTANTCONDITION*/
877 	SAFE_STRDUP(pw->pw_gecos, 4);
878 
879 /*CONSTANTCONDITION*/
880 	SAFE_STRDUP(pw->pw_dir, 5);
881 
882 /*CONSTANTCONDITION*/
883 	SAFE_STRDUP(pw->pw_shell, 6);
884 
885 	nis_freeresult(res);
886 
887 	return (pw);
888 
889 error:
890 	nis_freeresult(res);
891 	if (pw->pw_name)
892 		free(pw->pw_name);
893 	if (pw->pw_passwd)
894 		free(pw->pw_passwd);
895 	if (pw->pw_gecos)
896 		free(pw->pw_gecos);
897 	if (pw->pw_dir)
898 		free(pw->pw_dir);
899 	if (pw->pw_shell)
900 		free(pw->pw_shell);
901 	free(pw);
902 
903 	return (NULL);
904 }
905 
906 /*
907  * struct spwd * nisplus_getsp_from_master()
908  *
909  * Get the shadow structure from a NIS+ master.
910  * This routine normally runs with EUID==0. This can cause trouble
911  * if the NIS+ tables are locked down so that only the owner can
912  * access the encrypted password. If we detect that scenario, we switch
913  * EUID to the owner of the record and refetch it.
914  */
915 static struct spwd *
916 nisplus_getsp_from_master(const char *name, char *domain)
917 {
918 	char lookup[NIS_MAXNAMELEN+1];
919 	nis_result *res = NULL;
920 	nis_object *nret = NULL;
921 	int len;
922 	struct spwd *spw;
923 	char *shadow = NULL;
924 	const char *p = NULL;
925 	const char *limit;
926 
927 	res = nisplus_match(name, domain, lookup, sizeof (lookup));
928 	if (res == NULL)
929 		return (NULL);
930 
931 	nret = NIS_RES_OBJECT(res);
932 
933 	/*CONSTANTCONDITION*/
934 	SAFE_STRDUP(shadow, 7);
935 
936 	/*
937 	 * If we got NOPWDRTR as password, try again with EUID set
938 	 * to the UID of the record-owner.
939 	 */
940 	if (strncmp(shadow, NOPWDRTR, 4) == 0) {
941 		char *p;
942 		uid_t owner_uid;
943 		uid_t euid = geteuid();
944 
945 		SAFE_STRDUP(p, 2);	/* record-owner field */
946 		owner_uid = atoi(p);
947 		free(p);
948 
949 		if (owner_uid != euid) {
950 			/* re-obtain entry using owners EUID */
951 			free(shadow);
952 			nis_freeresult(res);
953 
954 			(void) seteuid(owner_uid);
955 			res = nisplus_match(name, domain, lookup,
956 			    sizeof (lookup));
957 			(void) seteuid(euid);
958 
959 			if (res == NULL)
960 				return (NULL);
961 			nret = NIS_RES_OBJECT(res);
962 
963 			/*CONSTANTCONDITION*/
964 			SAFE_STRDUP(shadow, 7);
965 		}
966 	}
967 
968 	if ((spw = calloc(1, sizeof (*spw))) == NULL) {
969 		nis_freeresult(res);
970 		return (NULL);
971 	}
972 
973 	SAFE_STRDUP(spw->sp_namp, 0);
974 	SAFE_STRDUP(spw->sp_pwdp, 1);
975 
976 	nis_freeresult(res);
977 
978 	limit = shadow + strlen(shadow) + 1;
979 	p = shadow;
980 
981 	spw->sp_lstchg = -1;
982 	spw->sp_min	= -1;
983 	spw->sp_max	= -1;
984 	spw->sp_warn	= -1;
985 	spw->sp_inact = -1;
986 	spw->sp_expire = -1;
987 	spw->sp_flag	= 0;
988 
989 	if (!getfield(&p, limit, 0, &spw->sp_lstchg))
990 		goto out;
991 
992 	if (!getfield(&p, limit, 0, &spw->sp_min))
993 		goto out;
994 
995 	if (!getfield(&p, limit, 0, &spw->sp_max))
996 		goto out;
997 
998 	if (!getfield(&p, limit, 0, &spw->sp_warn))
999 		goto out;
1000 
1001 	if (!getfield(&p, limit, 0, &spw->sp_inact))
1002 		goto out;
1003 
1004 	if (!getfield(&p, limit, 0, &spw->sp_expire))
1005 		goto out;
1006 
1007 	if (!getfield(&p, limit, 1, &spw->sp_flag))
1008 		goto out;
1009 
1010 	if (p != limit) {
1011 		syslog(LOG_ERR, "passwdutil: garbage at end of record");
1012 		goto error;
1013 	}
1014 
1015 out:
1016 	free(shadow);
1017 	return (spw);
1018 
1019 error:
1020 	if (spw->sp_namp)
1021 		free(spw->sp_namp);
1022 	if (spw->sp_pwdp)
1023 		free(spw->sp_pwdp);
1024 	free(spw);
1025 	if (shadow)
1026 		free(shadow);
1027 	return (NULL);
1028 }
1029