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