xref: /illumos-gate/usr/src/lib/libnisdb/yptol/shim_changepasswd.c (revision 132157d7fb25c120ae1deca2a65fa7c78e8fcfd0)
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 2015 Gary Mills
23  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*
28  * DESCRIPTION: This is the N2L equivalent of changepasswd.c. The traditional
29  *		version modifies the NIS source files and then initiates a
30  *		ypmake to make the maps and push them.
31  *
32  *		For N2L there are no source files and the policy is that the
33  *		definitive information is that contained in the DIT. Old
34  *		information is read from LDAP. Assuming	this authenticates, and
35  *		the change is acceptable, this information is modified and
36  *		written back to LDAP.
37  *
38  *		Related map entries are then found and 	updated finally
39  *		yppushes of the changed maps are initiated. Since the
40  *		definitive information has already correctly been updated the
41  *		code is tolerant of some errors during this operation.
42  *
43  *		What was previously in the maps is irrelevant.
44  *
45  *		Some less than perfect code (like inline constants for
46  *		return values and a few globals) is retained from the original.
47  */
48 
49 #include <sys/types.h>
50 #include <sys/stat.h>
51 #include <ctype.h>
52 #include <unistd.h>
53 #include <stdlib.h>
54 #include <string.h>
55 #include <stdio.h>
56 #include <errno.h>
57 #include <syslog.h>
58 #include <pwd.h>
59 #include <signal.h>
60 #include <crypt.h>
61 #include <rpc/rpc.h>
62 #include <rpcsvc/yppasswd.h>
63 #include <utmpx.h>
64 #include <shadow.h>
65 
66 #include <ndbm.h>
67 /* DO NOT INCLUDE SHIM_HOOKS.H */
68 #include "shim.h"
69 #include "yptol.h"
70 #include "../ldap_util.h"
71 
72 /*
73  * Undocumented external function in libnsl
74  */
75 extern int  getdomainname(char *, int);
76 
77 /* Constants */
78 #define	CRYPTPWSIZE CRYPT_MAXCIPHERTEXTLEN
79 #define	STRSIZE 100
80 #define	FINGERSIZE (4 * STRSIZE - 4)
81 #define	SHELLSIZE (STRSIZE - 2)
82 
83 #define	UTUSERLEN (sizeof (((struct utmpx *)0)->ut_user))
84 #define	COLON_CHAR ':'
85 
86 /*
87  * Path to DBM files. This is only required for N2L mode. Traditional mode
88  * works with the source files and uses the NIS Makefile to generate the maps.
89  * Seems to be hard coded in the rest of NIS so same is done here.
90  */
91 #define	YPDBPATH "/var/yp"
92 
93 /* Names of password and adjunct mappings. Used to access DIT */
94 #define	BYNAME ".byname"
95 #define	BYUID ".byuid"
96 #define	BYGID ".bygid"
97 #define	PASSWD_MAPPING "passwd" BYNAME
98 #define	PASSWD_ADJUNCT_MAPPING "passwd.adjunct" BYNAME
99 #define	AGEING_MAPPING "ageing" BYNAME
100 
101 /* Bitmasks used in list of fields to change */
102 #define	CNG_PASSWD 	0x0001
103 #define	CNG_SH		0x0002
104 #define	CNG_GECOS	0x0004
105 
106 /* Globals :-( */
107 extern int single, nogecos, noshell, nopw, mflag;
108 
109 /*
110  * Structure for containing the information is currently in the DIT. This is
111  * similar to the passwd structure defined in getpwent(3C) apart from.
112  *
113  * 1. Since GID and UID are never changed they are not converted to integers.
114  * 2. There are extra fields to hold adjunct information.
115  * 3. There are extra fields to hold widely used information.
116  */
117 struct passwd_entry {
118 	char 	*pw_name;
119 	char 	*pw_passwd;
120 	char	*pw_uid;
121 	char	*pw_gid;
122 	char	*pw_gecos;
123 	char	*pw_dir;
124 	char	*pw_shell;
125 	char	*adjunct_tail;	/* Tail of adjunct entry (opaque) */
126 	bool_t	adjunct;	/* Flag indicating if DIT has adjunct info */
127 	char	*pwd_str;	/* New password string */
128 	char	*adjunct_str;	/* New adjunct string */
129 };
130 
131 /* Prototypes */
132 extern bool_t validloginshell(char *sh, char *arg, int);
133 extern int    validstr(char *str, size_t size);
134 
135 suc_code write_shadow_info(char *, struct spwd *);
136 int put_new_info(struct passwd_entry *, char *);
137 char *create_pwd_str(struct passwd_entry *, bool_t);
138 int proc_domain(struct yppasswd *, bool_t, char *);
139 int proc_request(struct yppasswd *, struct passwd_entry *, bool_t, char *);
140 int modify_ent(struct yppasswd *, struct passwd_entry *t, bool_t, char *);
141 int get_change_list(struct yppasswd *, struct passwd_entry *);
142 struct passwd_entry *get_old_info(char *, char *);
143 static char *get_next_token(char *, char **, char *);
144 void free_pwd_entry(struct passwd_entry *);
145 struct spwd *get_old_shadow(char *, char *);
146 suc_code decode_shadow_entry(datum *, struct spwd *);
147 void free_shadow_entry(struct spwd *);
148 int proc_maps(char *, struct passwd_entry *);
149 int proc_map_list(char **, char *, struct passwd_entry *, bool_t);
150 int update_single_map(char *, struct passwd_entry *, bool_t);
151 bool_t strend(char *s1, char *s2);
152 
153 /*
154  * FUNCTION:	shim_changepasswd()
155  *
156  * DESCRIPTION:	N2L version of changepasswd(). When this is called 'useshadow'
157  *		etc. will have been set up but are meaningless. We work out
158  *		what to change based on information from the DIT.
159  *
160  * INPUTS:	Identical to changepasswd()
161  *
162  * OUTPUTS:	Identical to changepasswd()
163  */
164 void
165 shim_changepasswd(SVCXPRT *transp)
166 {
167 	struct yppasswd yppwd;
168 	bool_t	root_on_master = FALSE;
169 	char domain[MAXNETNAMELEN+1];
170 	char **domain_list;
171 	int dom_count, i;
172 
173 	int	ret, ans = 2;	/* Answer codes */
174 
175 	/* Clean out yppwd ... maybe we don't trust RPC */
176 	memset(&yppwd, 0, sizeof (struct yppasswd));
177 
178 	/* Get the RPC args */
179 	if (!svc_getargs(transp, xdr_yppasswd, (caddr_t)&yppwd)) {
180 		svcerr_decode(transp);
181 		return;
182 	}
183 
184 	/* Perform basic validation */
185 	if ((!validstr(yppwd.newpw.pw_passwd, CRYPTPWSIZE)) ||
186 		(!validstr(yppwd.newpw.pw_name, UTUSERLEN)) ||
187 		(!validstr(yppwd.newpw.pw_gecos, FINGERSIZE)) ||
188 		(!validstr(yppwd.newpw.pw_shell, SHELLSIZE))) {
189 		svcerr_decode(transp);
190 		return;
191 	}
192 
193 	/*
194 	 * Special case: root on the master server can change other
195 	 * users' passwords without first entering the old password.
196 	 * We need to ensure that this is indeed root on the master
197 	 * server. (bug 1253949)
198 	 */
199 	if (strcmp(transp->xp_netid, "ticlts") == 0) {
200 		svc_local_cred_t cred;
201 		if (!svc_get_local_cred(transp, &cred)) {
202 			logmsg(MSG_NOTIMECHECK, LOG_ERR,
203 					"Couldn't get local user credentials");
204 		} else if (cred.ruid == 0)
205 			root_on_master = TRUE;
206 	}
207 
208 	/*
209 	 * Get the domain name. This is tricky because a N2L server may be
210 	 * handling multiple domains. There is nothing in the request to
211 	 * indicate which one we are trying to change a passwd for. First
212 	 * we try to get a list of password related domains from the mapping
213 	 * file.
214 	 */
215 	if (0 !=
216 	    (dom_count = get_mapping_yppasswdd_domain_list(&domain_list))) {
217 		/* Got a domain list ... process all the domains */
218 		for (i = 0; i < dom_count; i ++) {
219 			ret = proc_domain(&yppwd, root_on_master,
220 								domain_list[i]);
221 
222 			/* If one has worked don't care if others fail */
223 			if (0 != ans)
224 				ans = ret;
225 		}
226 	}
227 	else
228 	{
229 		/*
230 		 * There was no domain list in the mapping file. The
231 		 * traditional version of this code calls ypmake which picks
232 		 * up the domain returned by getdomainname(). Fall back to the
233 		 * same mechanism.
234 		 */
235 		if (0 > getdomainname(domain, MAXNETNAMELEN+1)) {
236 			logmsg(MSG_NOTIMECHECK, LOG_ERR,
237 					"Could not get any domain info");
238 		} else {
239 			/* Got one domain ... process it. */
240 			ans = proc_domain(&yppwd, root_on_master, domain);
241 		}
242 	}
243 
244 	/* Send reply packet */
245 	if (!svc_sendreply(transp, xdr_int, (char *)&ans))
246 		logmsg(MSG_NOTIMECHECK, LOG_WARNING,
247 						"could not reply to RPC call");
248 }
249 
250 /*
251  * FUNCTION : 	proc_domain()
252  *
253  * DESCRIPTION:	Process a request for one domain
254  *
255  * GIVEN :	Pointer to the request.
256  *		Root on master flag
257  *		Domain
258  *
259  * OUTPUTS :	Answer code for reply
260  */
261 int
262 proc_domain(struct yppasswd *yppwd, bool_t root_on_master, char *domain)
263 {
264 	struct passwd_entry *old_pwd;
265 	char	*p;
266 	int ans = 2;
267 
268 	/* security hole fix from original source */
269 	for (p = yppwd->newpw.pw_name; (*p != '\0'); p++)
270 		if ((*p == ':') || !(isprint(*p)))
271 			*p = '$';	/* you lose buckwheat */
272 	for (p = yppwd->newpw.pw_passwd; (*p != '\0'); p++)
273 		if ((*p == ':') || !(isprint(*p)))
274 			*p = '$';	/* you lose buckwheat */
275 
276 	/* Get old info from DIT for this domain */
277 	old_pwd = get_old_info(yppwd->newpw.pw_name, domain);
278 	if (NULL ==  old_pwd) {
279 		logmsg(MSG_NOTIMECHECK, LOG_ERR,
280 				"Could not get old information for %s in "
281 				"domain %s", yppwd->newpw.pw_name, domain);
282 		return (ans);
283 	}
284 
285 	/* Have a request that can be replied to */
286 	ans = proc_request(yppwd, old_pwd, root_on_master, domain);
287 	free_pwd_entry(old_pwd);
288 
289 	return (ans);
290 }
291 
292 /*
293  * FUNCTION :	proc_request()
294  *
295  * DESCRIPTION:	Process a request
296  *
297  * GIVEN :	Pointer to the request.
298  *		Pointer to old information from LDAP
299  *		Root on master flag
300  *		Domain
301  *
302  * OUTPUTS :	Answer code for reply
303  */
304 int
305 proc_request(struct yppasswd *yppwd, struct passwd_entry *old_pwd,
306 					bool_t root_on_master, char *domain)
307 {
308 	struct sigaction sa, osa1, osa2, osa3;
309 	int	ans;
310 
311 	/* Authenticate */
312 	if ((0 != strcmp(crypt(yppwd->oldpass, old_pwd->pw_passwd),
313 				old_pwd->pw_passwd)) && !root_on_master) {
314 		logmsg(MSG_NOTIMECHECK, LOG_NOTICE, "Passwd incorrect %s",
315 						yppwd->newpw.pw_name);
316 		return (7);
317 	}
318 
319 	/* Work out what we have to change and change it */
320 	ans = modify_ent(yppwd, old_pwd, root_on_master, domain);
321 	if (0 != ans)
322 		return (ans);
323 
324 	/*
325 	 * Generate passwd and adjunct map entries. This creates extra
326 	 * malloced strings in old_pwd. These will be freed when
327 	 * free_pwd_entry() is called to free up the rest of the structure.
328 	 */
329 	old_pwd->pwd_str = create_pwd_str(old_pwd, FALSE);
330 	if (NULL == old_pwd->pwd_str) {
331 		logmsg(MSG_NOTIMECHECK, LOG_ERR,
332 					"Could not create passwd entry");
333 		return (2);
334 	}
335 	if (old_pwd->adjunct) {
336 		old_pwd->adjunct_str = create_pwd_str(old_pwd, TRUE);
337 		if (NULL == old_pwd->adjunct_str) {
338 			logmsg(MSG_NOTIMECHECK, LOG_ERR,
339 					"Could not create adjunct entry");
340 			return (2);
341 		}
342 	} else {
343 		old_pwd->adjunct_str = NULL;
344 	}
345 
346 	/* Put the information back to DIT */
347 	ans = put_new_info(old_pwd, domain);
348 	if (0 != ans) {
349 		return (ans);
350 	}
351 
352 	/* Are going to be forking pushes, set up signals */
353 	memset(&sa, 0, sizeof (struct sigaction));
354 	sa.sa_handler = SIG_IGN;
355 	sigaction(SIGTSTP, &sa, (struct sigaction *)0);
356 	sigaction(SIGHUP,  &sa, &osa1);
357 	sigaction(SIGINT,  &sa, &osa2);
358 	sigaction(SIGQUIT, &sa, &osa3);
359 
360 	/* Update and push all the maps */
361 	ans = proc_maps(domain, old_pwd);
362 
363 	/* Tidy up signals */
364 	sigaction(SIGHUP,  &osa1, (struct sigaction *)0);
365 	sigaction(SIGINT,  &osa2, (struct sigaction *)0);
366 	sigaction(SIGQUIT, &osa3, (struct sigaction *)0);
367 
368 	return (ans);
369 }
370 
371 /*
372  * FUNCTION:	proc_maps()
373  *
374  * DESCRIPTION: Gets all the map lists and processes them.
375  *
376  * INPUTS:	Domain name
377  *		New info to write into maps
378  *
379  * OUTPUT :	Answer code
380  */
381 int
382 proc_maps(char *domain, struct passwd_entry *pwd)
383 {
384 	char	**map_list; 	/* Array of passwd or adjunct maps */
385 	int	ans = 0;
386 
387 	/* Get list of passwd maps from mapping file */
388 	map_list = get_passwd_list(FALSE, domain);
389 	if (map_list != NULL) {
390 		/* Process list of passwd maps */
391 		ans = proc_map_list(map_list, domain, pwd, FALSE);
392 		free_passwd_list(map_list);
393 		if (0 != ans)
394 			return (ans);
395 	}
396 
397 	/*
398 	 * If we get here either there were no passwd maps or there were
399 	 * some and they were processed successfully. Either case is good
400 	 * continue and process passwd.adjunct maps.
401 	 */
402 
403 	/* Get list of adjunct maps from mapping file */
404 	map_list = get_passwd_list(TRUE, domain);
405 	if (map_list != NULL) {
406 		/*
407 		 * Process list of adjunct maps. If the required information
408 		 * is not present in LDAP then the updates attempts will log
409 		 * an error. No need to make the check here
410 		 */
411 		ans = proc_map_list(map_list, domain, pwd, TRUE);
412 		free_passwd_list(map_list);
413 	}
414 
415 	return (ans);
416 }
417 
418 /*
419  * FUNCTION:	proc_map_list()
420  *
421  * DESCRIPTION: Finds entries in one list of map that need to be updated.
422  *		updates them and writes them back.
423  *
424  * INPUTS:	Null terminated list of maps to process.
425  *		Domain name
426  *		Information to write (including user name)
427  *		Flag indicating if this is the adjunct list
428  *
429  * OUTPUTS:	An error code
430  */
431 int
432 proc_map_list(char **map_list, char *domain,
433 				struct passwd_entry *pwd, bool_t adjunct_flag)
434 {
435 	char 	*myself = "proc_map_list";
436 	char	*map_name;
437 	char	cmdbuf[BUFSIZ];
438 	int	map_name_len = 0;
439 	int	index, ans = 0;
440 
441 	/* If this is a adjunct list check LDAP had some adjunct info */
442 	if ((adjunct_flag) && (!pwd->adjunct)) {
443 		logmsg(MSG_NOTIMECHECK, LOG_INFO,
444 			"Have adjunct map list but no adjunct data in DIT");
445 		/* Not a disaster */
446 		return (0);
447 	}
448 
449 	/* Allocate enough buffer to take longest map name */
450 	for (index = 0; map_list[index] != NULL; index ++)
451 		if (map_name_len < strlen(map_list[index]))
452 			map_name_len = strlen(map_list[index]);
453 	map_name_len += strlen(YPDBPATH);
454 	map_name_len += strlen(NTOL_PREFIX);
455 	map_name_len += strlen(domain);
456 	map_name_len += 3;
457 	if (NULL == (map_name = am(myself, map_name_len))) {
458 		logmsg(MSG_NOMEM, LOG_ERR, "Could not alloc map name");
459 		return (2);
460 	}
461 
462 	/* For all maps in list */
463 	for (index = 0; map_list[index] != NULL; index ++) {
464 
465 		/* Generate full map name */
466 		strcpy(map_name, YPDBPATH);
467 		add_separator(map_name);
468 		strcat(map_name, domain);
469 		add_separator(map_name);
470 		strcat(map_name, NTOL_PREFIX);
471 		strcat(map_name, map_list[index]);
472 
473 		if (0 != (ans = update_single_map(map_name, pwd, adjunct_flag)))
474 			break;
475 	}
476 
477 	/* Done with full map path */
478 	sfree(map_name);
479 
480 	/*
481 	 * If (ans != 0) then one more maps have failed. LDAP has however been
482 	 * updates. This is the definitive source for information there is no
483 	 * need to unwind. (This was probably due to maps that were already
484 	 * corrupt).
485 	 */
486 
487 	/*
488 	 * If it all worked fork off push operations for the maps. Since we
489 	 * want the map to end up with it's traditional name on the slave send
490 	 * the name without its LDAP_ prefix. The slave will call ypxfrd
491 	 * which, since it is running in N2L mode, will put the prefix back on
492 	 * before reading the file.
493 	 */
494 	if (mflag && (0 == ans)) {
495 		for (index = 0; (map_name = map_list[index]) != NULL;
496 								index ++) {
497 			if (fork() == 0) {
498 				/*
499 				 * Define full path to yppush. Probably also
500 				 * best for security.
501 				 */
502 				strcpy(cmdbuf, "/usr/lib/netsvc/yp/yppush ");
503 				strcat(cmdbuf, map_name);
504 				if (0 > system(cmdbuf))
505 					logmsg(MSG_NOTIMECHECK, LOG_ERR,
506 						"Could not initiate yppush");
507 				exit(0);
508 			}
509 		}
510 	}
511 	return (ans);
512 }
513 
514 /*
515  * FUNCTION :	update_single_map()
516  *
517  * DESCRIPTION:	Updates one map. This is messy because we want to lock the map
518  *		to prevent other processes from updating it at the same time.
519  *		This mandates that we open it using the shim. When we
520  *		write to it however we DO NOT want to write through to LDAP
521  *		i.e. do not want to use the shim.
522  *
523  *		Solution : Do not include shim_hooks.h but call the shim
524  *		versions of dbm_functions explicitly where needed.
525  *
526  * INPUT :	Full name of map
527  *		Information to write (including user name)
528  *		Flag indicating if this is an adjunct map.
529  *
530  * OUTPUT :	Answer code
531  *
532  */
533 int
534 update_single_map(char *map_name, struct passwd_entry *pwd, bool_t adjunct_flag)
535 {
536 	DBM	*map;
537 	int	res;
538 	datum	data, key;
539 
540 	/* Set up data */
541 	if (adjunct_flag)
542 		data.dptr = pwd->adjunct_str;
543 	else
544 		data.dptr = pwd->pwd_str;
545 	data.dsize = strlen(data.dptr);
546 
547 	/* Set up key dependent on which type of map this is */
548 	key.dptr = NULL;
549 	if (strend(map_name, BYNAME))
550 		key.dptr = pwd->pw_name;
551 	if (strend(map_name, BYUID))
552 		key.dptr = pwd->pw_uid;
553 	if (strend(map_name, BYGID))
554 		key.dptr = pwd->pw_gid;
555 
556 	if (NULL == key.dptr) {
557 		logmsg(MSG_NOTIMECHECK, LOG_ERR,
558 					"Unrecognized map type %s", map_name);
559 		return (0);		/* Next map */
560 	}
561 	key.dsize = strlen(key.dptr);
562 
563 	/* Open the map */
564 	map = shim_dbm_open(map_name, O_RDWR, 0600);
565 	if (NULL == map) {
566 		logmsg(MSG_NOTIMECHECK, LOG_ERR, "Could not open %s", map_name);
567 		return (0);		/* Next map */
568 	}
569 
570 	/* Lock map for update. Painful and may block but have to do it */
571 	if (SUCCESS != lock_map_update((map_ctrl *)map)) {
572 		logmsg(MSG_NOTIMECHECK, LOG_ERR,
573 				"Could not lock map %s for update", map_name);
574 		shim_dbm_close(map);
575 		return (2);
576 	}
577 
578 	/* Do the update use simple DBM operation */
579 	res = dbm_store(((map_ctrl *)map)->entries, key, data, DBM_REPLACE);
580 
581 	/* update entry TTL. If we fail not a problem will just timeout early */
582 	update_entry_ttl((map_ctrl *)map, &key, TTL_RAND);
583 
584 	/*
585 	 * Map has been modified so update YP_LAST_MODIFIED. In the vanilla
586 	 * NIS case this would have been done by the ypmake done after updating
587 	 * the passwd source file. If this fails not a great problem the map
588 	 */
589 	if (FAILURE == update_timestamp(((map_ctrl *)map)->entries)) {
590 		logmsg(MSG_NOTIMECHECK, LOG_ERR, "Could not update "
591 			"YP_LAST_MODIFIED %s will not be pushed this time",
592 			map_name);
593 	}
594 
595 	/*
596 	 * Possibly should hold the lock until after push is complete
597 	 * but this could deadlock if client is slow and ypxfrd also
598 	 * decides to do an update.
599 	 */
600 	unlock_map_update((map_ctrl *)map);
601 
602 	/* Close the map */
603 	shim_dbm_close(map);
604 
605 	if (0 != res) {
606 		logmsg(MSG_NOTIMECHECK, LOG_ERR,
607 					"Could not update map %s", map_name);
608 		return (2);
609 	}
610 
611 	return (0);
612 }
613 
614 /*
615  * FUNCTION :	strend()
616  *
617  * DESCRIPTION:	Determines if one string ends with another.
618  */
619 bool_t
620 strend(char *s1, char *s2)
621 {
622 	int len_dif;
623 
624 	len_dif = strlen(s1) - strlen(s2);
625 	if (0 > len_dif)
626 		return (FALSE);
627 	if (0 == strcmp(s1 + len_dif, s2))
628 		return (TRUE);
629 	return (FALSE);
630 }
631 
632 /*
633  * FUNCTION:	modify_ent()
634  *
635  * DESCRIPTION: Modify an entry to reflect a request.
636  *
637  * INPUT:	Pointer to the request.
638  *		Pointer to the entry to modify.
639  *		Flag indication if we are root on master
640  *		Domain
641  *
642  * OUTPUT:	Error code
643  */
644 int
645 modify_ent(struct yppasswd *yppwd, struct passwd_entry *old_ent,
646 					bool_t root_on_master, char *domain)
647 {
648 	int change_list;
649 	struct spwd *shadow;
650 	time_t now;
651 
652 	/* Get list of changes */
653 	change_list = get_change_list(yppwd, old_ent);
654 
655 	if (!change_list) {
656 		logmsg(MSG_NOTIMECHECK, LOG_NOTICE,
657 				"No change for %s", yppwd->newpw.pw_name);
658 		return (3);
659 	}
660 
661 	/* Check that the shell we have been given is acceptable. */
662 	if ((change_list & CNG_SH) && (!validloginshell(old_ent->pw_shell,
663 					yppwd->newpw.pw_shell, root_on_master)))
664 		return (2);
665 
666 	/*
667 	 * If changing the password do any aging checks.
668 	 * Since there are no shadow maps this is done by accessing
669 	 * attributes in the DIT via the mapping system.
670 	 */
671 	if (change_list & CNG_PASSWD) {
672 
673 		/* Try to get shadow information */
674 		shadow = get_old_shadow(yppwd->newpw.pw_name, domain);
675 
676 		/* If there is shadow information make password aging checks */
677 		if (NULL != shadow) {
678 			now = DAY_NOW;
679 			/* password aging - bug for bug compatibility */
680 			if (shadow->sp_max != -1) {
681 				if (now < shadow->sp_lstchg + shadow->sp_min) {
682 					logmsg(MSG_NOTIMECHECK, LOG_ERR,
683 					"Sorry: < %ld days since "
684 					"the last change", shadow->sp_min);
685 					free_shadow_entry(shadow);
686 					return (2);
687 				}
688 		}
689 
690 			/* Update time of change */
691 			shadow->sp_lstchg = now;
692 
693 			/* Write it back */
694 			write_shadow_info(domain, shadow);
695 
696 			free_shadow_entry(shadow);
697 		}
698 	}
699 
700 	/* Make changes to old entity */
701 	if (change_list & CNG_GECOS) {
702 		if (NULL != old_ent->pw_gecos)
703 			sfree(old_ent->pw_gecos);
704 		old_ent->pw_gecos = strdup(yppwd->newpw.pw_gecos);
705 		if (NULL == old_ent->pw_gecos) {
706 			logmsg(MSG_NOMEM, LOG_ERR, "Could not allocate gecos");
707 			return (2);
708 		}
709 	}
710 
711 	if (change_list & CNG_SH) {
712 		if (NULL != old_ent->pw_shell)
713 			sfree(old_ent->pw_shell);
714 		old_ent->pw_shell = strdup(yppwd->newpw.pw_shell);
715 		if (NULL == old_ent->pw_shell) {
716 			logmsg(MSG_NOMEM, LOG_ERR, "Could not allocate shell");
717 			return (2);
718 		}
719 	}
720 
721 	if (change_list & CNG_PASSWD) {
722 		if (NULL != old_ent->pw_passwd)
723 			sfree(old_ent->pw_passwd);
724 		old_ent->pw_passwd = strdup(yppwd->newpw.pw_passwd);
725 		if (NULL == old_ent->pw_passwd) {
726 			logmsg(MSG_NOMEM, LOG_ERR, "Could not allocate passwd");
727 			return (2);
728 		}
729 	}
730 
731 	return (0);
732 }
733 
734 /*
735  * FUNCTION :	get_change_list()
736  *
737  * DESCRIPTION:	Works out what we have to change.
738  *
739  * INPUTS :	Request.
740  *		Structure containing current state of entry
741  *
742  * OUTPUTS :	A bitmask signaling what to change. (Implemented in this
743  *		way to make it easy to pass between functions).
744  */
745 int
746 get_change_list(struct yppasswd *yppwd, struct passwd_entry *old_ent)
747 {
748 	int list = 0;
749 	char *p;
750 
751 	p = yppwd->newpw.pw_passwd;
752 	if ((!nopw) &&
753 		p && *p &&
754 		!(*p++ == '#' && *p++ == '#' &&
755 		(strcmp(p, old_ent->pw_name) == 0)) &&
756 		(strcmp(crypt(old_ent->pw_passwd,
757 			yppwd->newpw.pw_passwd), yppwd->newpw.pw_passwd) != 0))
758 		list |= CNG_PASSWD;
759 
760 	if ((NULL != old_ent->pw_shell) &&
761 		(!noshell) &&
762 		(strcmp(old_ent->pw_shell, yppwd->newpw.pw_shell) != 0)) {
763 		if (single)
764 			list = 0;
765 		list |= CNG_SH;
766 	}
767 
768 	if ((NULL != old_ent->pw_gecos) &&
769 		(!nogecos) &&
770 		(strcmp(old_ent->pw_gecos, yppwd->newpw.pw_gecos) != 0)) {
771 		if (single)
772 			list = 0;
773 		list |= CNG_GECOS;
774 	}
775 
776 	return (list);
777 }
778 
779 /*
780  * FUNCTION :	decode_pwd_entry()
781  *
782  * DESCRIPTION:	Pulls apart a password entry. Because the password entry has
783  *		come from the mapping system it can be assumed to be correctly
784  *		formatted and relatively simple parsing can be done.
785  *
786  *		Substrings are put into malloced memory. Caller to free.
787  *
788  *		For adjunct files most of it is left empty.
789  *
790  *		It would be nice to use getpwent and friends for this work but
791  *		these only seem to exist for files and it seems excessive to
792  *		create a temporary file for this operation.
793  *
794  * INPUTS:	Pointer to datum containing password string.
795  *		Pointer to structure in which to return results
796  *		Flag indicating if we are decoding passwd or passwd.adjunct
797  *
798  * OUTPUTS:	SUCCESS = Decoded successfully
799  *		FAILURE = Not decoded successfully. Caller to tidy up.
800  */
801 suc_code
802 decode_pwd_entry(datum *data, struct passwd_entry *pwd, bool_t adjunct)
803 {
804 	char *myself = "decode_pwd_entry";
805 	char *p, *str_end, *temp;
806 
807 	/* Work out last location in string */
808 	str_end = data->dptr + data->dsize;
809 
810 	/* Name */
811 	if (NULL == (p = get_next_token(data->dptr, &temp, str_end)))
812 		return (FAILURE);
813 	if (adjunct) {
814 		/* If we found an adjunct version this is the one to use */
815 		if (NULL != pwd->pw_name)
816 			sfree(pwd->pw_name);
817 	}
818 	pwd->pw_name = temp;
819 
820 	/* Password */
821 	if (NULL == (p = get_next_token(p, &temp, str_end)))
822 		return (FAILURE);
823 	if (adjunct) {
824 		/* If we found an adjunct version this is the one to use */
825 		if (NULL != pwd->pw_passwd)
826 			sfree(pwd->pw_passwd);
827 	}
828 	pwd->pw_passwd = temp;
829 
830 	if (adjunct) {
831 		/* Store adjunct information in opaque string */
832 		pwd->adjunct_tail = am(myself, str_end - p + 1);
833 		if (NULL == pwd->adjunct_tail)
834 			return (FAILURE);
835 		strncpy(pwd->adjunct_tail, p, str_end - p);
836 		pwd->adjunct_tail[str_end - p] = '\0';
837 
838 		/* Remember that LDAP contained adjunct data */
839 		pwd->adjunct = TRUE;
840 		return (SUCCESS);
841 	}
842 
843 	/* If we get here not adjunct. Decode rest of passwd */
844 
845 	/* UID */
846 	if (NULL == (p = get_next_token(p, &(pwd->pw_uid), str_end)))
847 		return (FAILURE);
848 
849 	/* GID */
850 	if (NULL == (p = get_next_token(p, &(pwd->pw_gid), str_end)))
851 		return (FAILURE);
852 
853 	/* Gecos */
854 	if (NULL == (p = get_next_token(p, &(pwd->pw_gecos), str_end)))
855 		return (FAILURE);
856 
857 	/* Home dir */
858 	if (NULL == (p = get_next_token(p, &(pwd->pw_dir), str_end)))
859 		return (FAILURE);
860 
861 	/* Shell may not be present so don't check return */
862 	get_next_token(p, &(pwd->pw_shell), str_end);
863 
864 	if (NULL == pwd->pw_shell)
865 		return (FAILURE);
866 
867 	return (SUCCESS);
868 }
869 
870 /*
871  * FUNCTION :	get_next_token()
872  *
873  * DESCRIPTION:	Gets the next token from a string upto the next colon or the
874  *		end of the string. The duplicates this token into malloced
875  *		memory removing any spaces.
876  *
877  * INPUTS :	String to search for token. NOT NULL TERMINATED
878  *		Location to return result (NULL if result not required)
879  *		Last location in string
880  *
881  * OUTPUT :	Pointer into the string immediately after the token.
882  *		NULL if end of string reached or error.
883  */
884 static char *
885 get_next_token(char *str, char **op, char *str_end)
886 {
887 	char *myself = "get_next_token";
888 	char *p, *tok_start, *tok_end;
889 
890 	p = str;
891 	/* Skip leading whitespace */
892 	while (' ' == *p)
893 		p++;
894 	tok_start = p;
895 	tok_end = p;
896 
897 	while ((str_end + 1 != p) && (COLON_CHAR != *p)) {
898 		if (' ' != *p)
899 			tok_end = p;
900 		p++;
901 	}
902 
903 	/* Required string is now between start and end */
904 	if (NULL != op) {
905 		*op = am(myself, tok_end - tok_start + 2);
906 		if (NULL == *op) {
907 			logmsg(MSG_NOMEM, LOG_ERR,
908 					"Could not alloc memory for token");
909 			return (NULL);
910 		}
911 		strncpy(*op, tok_start, tok_end - tok_start + 1);
912 
913 		/* Terminate token */
914 		(*op)[tok_end - tok_start + 1] = '\0';
915 
916 	}
917 
918 	/* Check if we reached the end of the input string */
919 	if ('\0' == *p)
920 		return (NULL);
921 
922 	/* There is some more */
923 	p++;
924 	return (p);
925 }
926 
927 /*
928  * FUNCTION :	free_pwd_entry()
929  *
930  * DESCRIPTION:	Frees up a pwd_entry structure and its contents.
931  *
932  * INPUTS:	Pointer to the structure to free.
933  *
934  * OUTPUT:	Nothing
935  */
936 void
937 free_pwd_entry(struct passwd_entry *pwd)
938 {
939 	/* Free up strings */
940 	if (NULL != pwd->pw_name)
941 		sfree(pwd->pw_name);
942 
943 	if (NULL != pwd->pw_passwd)
944 		sfree(pwd->pw_passwd);
945 
946 	if (NULL != pwd->pw_gecos)
947 		sfree(pwd->pw_gecos);
948 
949 	if (NULL != pwd->pw_shell)
950 		sfree(pwd->pw_shell);
951 
952 	if (NULL != pwd->pw_dir)
953 		sfree(pwd->pw_dir);
954 
955 	if (NULL != pwd->adjunct_tail)
956 		sfree(pwd->adjunct_tail);
957 
958 	if (NULL != pwd->pwd_str)
959 		sfree(pwd->pwd_str);
960 
961 	if (NULL != pwd->adjunct_str)
962 		sfree(pwd->adjunct_str);
963 
964 	/* Free up structure */
965 	sfree(pwd);
966 }
967 
968 /*
969  * FUNCTION :	create_pwd_str()
970  *
971  * DESCRIPTION:	Builds up a new password entity string from a passwd structure.
972  *
973  * INPUTS :	Structure containing password details
974  *		Flag indicating if we should create an adjunct or passwd string.
975  *
976  * OUTPUTS :	String in malloced memory (to be freed by caller).
977  *		NULL on failure.
978  */
979 char *
980 create_pwd_str(struct passwd_entry *pwd, bool_t adjunct)
981 {
982 	char *myself = "create_pwd_str";
983 	char *s;
984 	int len;
985 
986 	/* Separator string so we can strcat separator onto things */
987 	char sep_str[2] = {COLON_CHAR, '\0'};
988 
989 	/* Work out the size */
990 	len = strlen(pwd->pw_name) + 1;
991 	len += strlen(pwd->pw_passwd) + 1;
992 	if (adjunct) {
993 		len += strlen(pwd->adjunct_tail) + 1;
994 	} else {
995 		len += strlen(pwd->pw_uid) + 1;
996 		len += strlen(pwd->pw_gid) + 1;
997 		len += strlen(pwd->pw_gecos) + 1;
998 		len += strlen(pwd->pw_dir) + 1;
999 		len += strlen(pwd->pw_shell) + 1;
1000 	}
1001 
1002 	/* Allocate some memory for it */
1003 	s = am(myself, len);
1004 	if (NULL == s)
1005 		return (NULL);
1006 
1007 	strcpy(s, pwd->pw_name);
1008 	strcat(s, sep_str);
1009 	if (!adjunct) {
1010 		/* Build up a passwd string */
1011 
1012 		/* If LDAP contains adjunct info then passwd is 'x' */
1013 		if (pwd->adjunct) {
1014 			strcat(s, "##");
1015 			strcat(s,  pwd->pw_name);
1016 		} else {
1017 			strcat(s, pwd->pw_passwd);
1018 		}
1019 		strcat(s, sep_str);
1020 		strcat(s, pwd->pw_uid);
1021 		strcat(s, sep_str);
1022 		strcat(s, pwd->pw_gid);
1023 		strcat(s, sep_str);
1024 		strcat(s, pwd->pw_gecos);
1025 		strcat(s, sep_str);
1026 		strcat(s, pwd->pw_dir);
1027 		strcat(s, sep_str);
1028 		strcat(s, pwd->pw_shell);
1029 	} else {
1030 		/* Build up a passwd_adjunct string */
1031 		strcat(s, pwd->pw_passwd);
1032 		strcat(s, sep_str);
1033 		strcat(s, pwd->adjunct_tail);
1034 	}
1035 
1036 	return (s);
1037 }
1038 
1039 /*
1040  * FUNCTION:	get_old_info()
1041  *
1042  * DESCRIPTION:	Gets as much information as possible from LDAP about one user.
1043  *
1044  *		This goes through the mapping system. This is messy because
1045  *		them mapping system will build up a password entry from the
1046  *		contents of the DIT. We then have to parse this to recover
1047  *		it's individual fields.
1048  *
1049  * INPUT:	Pointer to user name
1050  *		Domain
1051  *
1052  * OUTPUT:	The info in malloced space. To be freed by caller.
1053  *		NULL on failure.
1054  */
1055 struct passwd_entry *
1056 get_old_info(char *name, char *domain)
1057 {
1058 	char *myself = "get_old_info";
1059 	struct passwd_entry *old_passwd;
1060 	datum	key, data;
1061 	suc_code res;
1062 
1063 	/* Get the password entry */
1064 	key.dptr = name;
1065 	key.dsize = strlen(key.dptr);
1066 	read_from_dit(PASSWD_MAPPING, domain, &key, &data);
1067 	if (NULL == data.dptr) {
1068 		logmsg(MSG_NOTIMECHECK, LOG_ERR,
1069 					"Could not read old pwd for %s", name);
1070 		return (NULL);
1071 	}
1072 
1073 	/* Pull password apart */
1074 	old_passwd = am(myself, sizeof (struct passwd_entry));
1075 	if (NULL == old_passwd) {
1076 		logmsg(MSG_NOMEM, LOG_ERR, "Could not alloc for pwd decode");
1077 		sfree(data.dptr);
1078 		return (NULL);
1079 	}
1080 
1081 	/* No data yet */
1082 	old_passwd->pw_name = NULL;
1083 	old_passwd->pw_passwd = NULL;
1084 	old_passwd->pw_uid = NULL;
1085 	old_passwd->pw_gid = NULL;
1086 	old_passwd->pw_gecos = NULL;
1087 	old_passwd->pw_dir = NULL;
1088 	old_passwd->pw_shell = NULL;
1089 	old_passwd->adjunct_tail = NULL;
1090 	old_passwd->pwd_str = NULL;
1091 	old_passwd->adjunct_str = NULL;
1092 	old_passwd->adjunct = FALSE;
1093 
1094 	res = decode_pwd_entry(&data, old_passwd, FALSE);
1095 	sfree(data.dptr);
1096 	if (SUCCESS != res) {
1097 		free_pwd_entry(old_passwd);
1098 		return (NULL);
1099 	}
1100 
1101 	/* Try to get the adjunct entry */
1102 	read_from_dit(PASSWD_ADJUNCT_MAPPING, domain, &key, &data);
1103 	if (NULL == data.dptr) {
1104 		/* Fine just no adjunct data */
1105 		old_passwd->adjunct = FALSE;
1106 	} else {
1107 		res = decode_pwd_entry(&data, old_passwd, TRUE);
1108 		sfree(data.dptr);
1109 		if (SUCCESS != res) {
1110 			free_pwd_entry(old_passwd);
1111 			return (NULL);
1112 		}
1113 	}
1114 
1115 	return (old_passwd);
1116 }
1117 
1118 /*
1119  * FUNCTION :	put_new_info()
1120  *
1121  * DESCRIPTION:	Generates new map strings and puts them back to LDAP
1122  *
1123  * INPUTS:	Info to put back
1124  *		Domain
1125  *
1126  * OUTPUT:	Answer code.
1127  */
1128 int
1129 put_new_info(struct passwd_entry *pwd, char *domain)
1130 {
1131 	datum	key, data;
1132 
1133 	/* Write it back to LDAP */
1134 	data.dptr = pwd->pwd_str;
1135 	data.dsize = strlen(data.dptr);
1136 	key.dptr = pwd->pw_name;
1137 	key.dsize = strlen(key.dptr);
1138 	if (SUCCESS != write_to_dit(PASSWD_MAPPING, domain, key, data,
1139 								TRUE, FALSE))
1140 		return (2);
1141 
1142 
1143 	/* If DIT contains adjunct information do the same for adjunct */
1144 	if (pwd->adjunct) {
1145 		data.dptr = pwd->adjunct_str;
1146 		data.dsize = strlen(data.dptr);
1147 		key.dptr = pwd->pw_name;
1148 		key.dsize = strlen(key.dptr);
1149 		if (SUCCESS != write_to_dit(PASSWD_ADJUNCT_MAPPING, domain,
1150 						key, data, TRUE, FALSE))
1151 			return (2);
1152 	}
1153 
1154 	return (0);
1155 
1156 }
1157 
1158 /*
1159  * FUNCTION :   get_old_shadow()
1160  *
1161  * DESCRIPTION :Extracts and decodes shadow information from the DIT
1162  *		See also comments under decode_pwd_entry().
1163  *
1164  * INPUTS :     User name
1165  *		Domain name
1166  *
1167  * OUTPUT :     Shadow information in malloced memory. To be freed by caller.
1168  */
1169 struct spwd *
1170 get_old_shadow(char *name, char *domain)
1171 {
1172 	char *myself = "get_old_shadow";
1173 	struct spwd *sp;
1174 	datum key, data;
1175 	suc_code res;
1176 
1177 	/* Get the info */
1178 	key.dptr = name;
1179 	key.dsize = strlen(key.dptr);	/* Len excluding terminator */
1180 	read_from_dit(AGEING_MAPPING, domain, &key, &data);
1181 
1182 	if (NULL == data.dptr) {
1183 		/* OK just have no shadow info in DIT */
1184 		return (NULL);
1185 	}
1186 
1187 	/* Pull shadow apart */
1188 	if (NULL == (sp = am(myself, sizeof (struct spwd)))) {
1189 		logmsg(MSG_NOMEM, LOG_ERR,
1190 					"Could not alloc for shadow decode");
1191 		sfree(data.dptr);
1192 		return (NULL);
1193 	}
1194 	sp->sp_namp = NULL;
1195 	sp->sp_pwdp = NULL;
1196 
1197 	res = decode_shadow_entry(&data, sp);
1198 	sfree(data.dptr);
1199 	if (SUCCESS != res) {
1200 		free_shadow_entry(sp);
1201 		return (NULL);
1202 	}
1203 
1204 	return (sp);
1205 }
1206 
1207 /*
1208  * FUNCTION :	decode_shadow_entry()
1209  *
1210  * DESCRIPTION:	Pulls apart ageing information. For convenience this is stored
1211  *		in a partially filled spwd structure.
1212  *
1213  *		SEE COMMENTS FOR decode_pwd_entry()
1214  */
1215 suc_code
1216 decode_shadow_entry(datum *data, struct spwd *sp)
1217 {
1218 	char *p, *str_end, *temp;
1219 
1220 	/* Work out last location in string */
1221 	str_end = data->dptr + data->dsize;
1222 
1223 	/* Name */
1224 	if (NULL == (p = get_next_token(data->dptr, &(sp->sp_namp), str_end)))
1225 		return (FAILURE);
1226 
1227 	/* date of last change */
1228 	if (NULL == (p = get_next_token(p, &temp, str_end)))
1229 		return (FAILURE);
1230 	sp->sp_lstchg = atoi(temp);
1231 
1232 	/* min days to passwd change */
1233 	if (NULL == (p = get_next_token(p, &temp, str_end)))
1234 		return (FAILURE);
1235 	sp->sp_min = atoi(temp);
1236 
1237 	/* max days to passwd change */
1238 	if (NULL == (p = get_next_token(p, &temp, str_end)))
1239 		return (FAILURE);
1240 	sp->sp_max = atoi(temp);
1241 
1242 	/* warning period */
1243 	if (NULL == (p = get_next_token(p, &temp, str_end)))
1244 		return (FAILURE);
1245 	sp->sp_warn = atoi(temp);
1246 
1247 	/* max days inactive */
1248 	if (NULL == (p = get_next_token(p, &temp, str_end)))
1249 		return (FAILURE);
1250 	sp->sp_inact = atoi(temp);
1251 
1252 	/* account expiry date */
1253 	if (NULL == (p = get_next_token(p, &temp, str_end)))
1254 		return (FAILURE);
1255 	sp->sp_expire = atoi(temp);
1256 
1257 	/* flag  */
1258 	if (NULL != (p = get_next_token(p, &temp, str_end)))
1259 		return (FAILURE);
1260 	sp->sp_flag = atoi(temp);
1261 
1262 	return (SUCCESS);
1263 }
1264 
1265 /*
1266  * FUNCTION :	write_shadow_info()
1267  *
1268  * DESCRIPTION:	Writes shadow information back to the DIT.
1269  *
1270  * INPUTS :	Domain
1271  *		Information to write
1272  *
1273  * OUTPUT :	Success code
1274  *
1275  */
1276 suc_code
1277 write_shadow_info(char *domain, struct spwd *sp)
1278 {
1279 	char *myself = "write_shadow_info";
1280 	datum key, data;
1281 	char *str;
1282 	suc_code res;
1283 	int len;
1284 
1285 	/* Work out how long string will be */
1286 	len = strlen(sp->sp_namp) + 1;
1287 
1288 	/*
1289 	 * Bit crude but if we assume 1 byte is 3 decimal characters
1290 	 * will get enough buffer for the longs and some spare.
1291 	 */
1292 	len += 7 * (3 * sizeof (long) + 1);
1293 
1294 	/* Allocate some memory */
1295 	str = am(myself, len);
1296 	if (NULL == str) {
1297 		logmsg(MSG_NOMEM, LOG_ERR, "Could not aloc for shadow write");
1298 		return (FAILURE);
1299 	}
1300 
1301 	/* Build up shadow string */
1302 	sprintf(str, "%s%c%d%c%d%c%d%c%d%c%d%c%d%c%d",
1303 		sp->sp_namp, COLON_CHAR,
1304 		sp->sp_lstchg, COLON_CHAR,
1305 		sp->sp_min, COLON_CHAR,
1306 		sp->sp_max, COLON_CHAR,
1307 		sp->sp_warn, COLON_CHAR,
1308 		sp->sp_inact, COLON_CHAR,
1309 		sp->sp_expire, COLON_CHAR,
1310 		sp->sp_flag);
1311 
1312 	/* Write it */
1313 	data.dptr = str;
1314 	data.dsize = strlen(data.dptr);
1315 	key.dptr = sp->sp_namp;
1316 	key.dsize = strlen(key.dptr);
1317 	res = write_to_dit(AGEING_MAPPING, domain, key, data, TRUE, FALSE);
1318 
1319 	sfree(str);
1320 	return (res);
1321 }
1322 
1323 /*
1324  * FUNCTION :	free_shadow_entry()
1325  *
1326  * DESCRIPTION:	Frees up a shadow information structure
1327  *
1328  * INPUTS :	Structure to free
1329  *
1330  * OUTPUTS :	Nothing
1331  */
1332 void
1333 free_shadow_entry(struct spwd *spwd)
1334 {
1335 	if (NULL != spwd->sp_namp)
1336 		sfree(spwd->sp_namp);
1337 
1338 	if (NULL != spwd->sp_pwdp)
1339 		sfree(spwd->sp_pwdp);
1340 
1341 	/* No need to free numerics */
1342 
1343 	/* Free up structure */
1344 	sfree(spwd);
1345 }
1346