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