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 */
75extern 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 :-( */
107extern 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 */
117struct 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 */
132extern bool_t validloginshell(char *sh, char *arg, int);
133extern int    validstr(char *str, size_t size);
134
135suc_code write_shadow_info(char *, struct spwd *);
136int put_new_info(struct passwd_entry *, char *);
137char *create_pwd_str(struct passwd_entry *, bool_t);
138int proc_domain(struct yppasswd *, bool_t, char *);
139int proc_request(struct yppasswd *, struct passwd_entry *, bool_t, char *);
140int modify_ent(struct yppasswd *, struct passwd_entry *t, bool_t, char *);
141int get_change_list(struct yppasswd *, struct passwd_entry *);
142struct passwd_entry *get_old_info(char *, char *);
143static char *get_next_token(char *, char **, char *);
144void free_pwd_entry(struct passwd_entry *);
145struct spwd *get_old_shadow(char *, char *);
146suc_code decode_shadow_entry(datum *, struct spwd *);
147void free_shadow_entry(struct spwd *);
148int proc_maps(char *, struct passwd_entry *);
149int proc_map_list(char **, char *, struct passwd_entry *, bool_t);
150int update_single_map(char *, struct passwd_entry *, bool_t);
151bool_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 */
164void
165shim_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 */
261int
262proc_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 */
304int
305proc_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 */
381int
382proc_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 */
431int
432proc_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 */
533int
534update_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 */
619bool_t
620strend(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 */
644int
645modify_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 */
745int
746get_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 */
801suc_code
802decode_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 */
884static char *
885get_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 */
936void
937free_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 */
979char *
980create_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 */
1055struct passwd_entry *
1056get_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 */
1128int
1129put_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 */
1169struct spwd *
1170get_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 */
1215suc_code
1216decode_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 */
1276suc_code
1277write_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 */
1332void
1333free_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