OpenSolaris_b135/lib/libnisdb/yptol/shim_changepasswd.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 * DESCRIPTION: This is the N2L equivalent of changepasswd.c. The traditional
 *		version modifies the NIS source files and then initiates a
 *		ypmake to make the maps and push them.
 *
 *		For N2L there are no source files and the policy is that the
 *		definitive information is that contained in the DIT. Old
 *		information is read from LDAP. Assuming	this authenticates, and
 *		the change is acceptable, this information is modified and
 *		written back to LDAP.
 *
 *		Related map entries are then found and 	updated finally
 *		yppushes of the changed maps are initiated. Since the
 *		definitive information has already correctly been updated the
 *		code is tolerant of some errors during this operation.
 *
 *		What was previously in the maps is irrelevant.
 *
 *		Some less than perfect code (like inline constants for
 *		return values and a few globals) is retained from the original.
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <ctype.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <syslog.h>
#include <pwd.h>
#include <signal.h>
#include <crypt.h>
#include <rpc/rpc.h>
#include <rpcsvc/yppasswd.h>
#include <utmpx.h>
#include <shadow.h>

#include <ndbm.h>
/* DO NOT INCLUDE SHIM_HOOKS.H */
#include "shim.h"
#include "yptol.h"
#include "../ldap_util.h"

/* Constants */
#define	CRYPTPWSIZE CRYPT_MAXCIPHERTEXTLEN
#define	STRSIZE 100
#define	FINGERSIZE (4 * STRSIZE - 4)
#define	SHELLSIZE (STRSIZE - 2)

#define	UTUSERLEN (sizeof (((struct utmpx *)0)->ut_user))
#define	COLON_CHAR ':'

/*
 * Path to DBM files. This is only required for N2L mode. Traditional mode
 * works with the source files and uses the NIS Makefile to generate the maps.
 * Seems to be hard coded in the rest of NIS so same is done here.
 */
#define	YPDBPATH "/var/yp"

/* Names of password and adjunct mappings. Used to access DIT */
#define	BYNAME ".byname"
#define	BYUID ".byuid"
#define	BYGID ".bygid"
#define	PASSWD_MAPPING "passwd" BYNAME
#define	PASSWD_ADJUNCT_MAPPING "passwd.adjunct" BYNAME
#define	AGEING_MAPPING "ageing" BYNAME

/* Bitmasks used in list of fields to change */
#define	CNG_PASSWD 	0x0001
#define	CNG_SH		0x0002
#define	CNG_GECOS	0x0004

/* Globals :-( */
extern int single, nogecos, noshell, nopw, mflag;

/*
 * Structure for containing the information is currently in the DIT. This is
 * similar to the passwd structure defined in getpwent(3C) apart from.
 *
 * 1. Since GID and UID are never changed they are not converted to integers.
 * 2. There are extra fields to hold adjunct information.
 * 3. There are extra fields to hold widely used information.
 */
struct passwd_entry {
	char 	*pw_name;
	char 	*pw_passwd;
	char	*pw_uid;
	char	*pw_gid;
	char	*pw_gecos;
	char	*pw_dir;
	char	*pw_shell;
	char	*adjunct_tail;	/* Tail of adjunct entry (opaque) */
	bool_t	adjunct;	/* Flag indicating if DIT has adjunct info */
	char	*pwd_str;	/* New password string */
	char	*adjunct_str;	/* New adjunct string */
};

/* Prototypes */
extern bool_t validloginshell(char *sh, char *arg, int);
extern int    validstr(char *str, size_t size);

suc_code write_shadow_info(char *, struct spwd *);
int put_new_info(struct passwd_entry *, char *);
char *create_pwd_str(struct passwd_entry *, bool_t);
int proc_domain(struct yppasswd *, bool_t, char *);
int proc_request(struct yppasswd *, struct passwd_entry *, bool_t, char *);
int modify_ent(struct yppasswd *, struct passwd_entry *t, bool_t, char *);
int get_change_list(struct yppasswd *, struct passwd_entry *);
struct passwd_entry *get_old_info(char *, char *);
static char *get_next_token(char *, char **, char *);
void free_pwd_entry(struct passwd_entry *);
struct spwd *get_old_shadow(char *, char *);
suc_code decode_shadow_entry(datum *, struct spwd *);
void free_shadow_entry(struct spwd *);
int proc_maps(char *, struct passwd_entry *);
int proc_map_list(char **, char *, struct passwd_entry *, bool_t);
int update_single_map(char *, struct passwd_entry *, bool_t);
bool_t strend(char *s1, char *s2);

/*
 * FUNCTION:	shim_changepasswd()
 *
 * DESCRIPTION:	N2L version of changepasswd(). When this is called 'useshadow'
 *		etc. will have been set up but are meaningless. We work out
 *		what to change based on information from the DIT.
 *
 * INPUTS:	Identical to changepasswd()
 *
 * OUTPUTS:	Identical to changepasswd()
 */
void
shim_changepasswd(SVCXPRT *transp)
{
	struct yppasswd yppwd;
	bool_t	root_on_master = FALSE;
	char domain[MAXNETNAMELEN+1];
	char **domain_list;
	int dom_count, i;

	int	ret, ans = 2;	/* Answer codes */

	/* Clean out yppwd ... maybe we don't trust RPC */
	memset(&yppwd, 0, sizeof (struct yppasswd));

	/* Get the RPC args */
	if (!svc_getargs(transp, xdr_yppasswd, (caddr_t)&yppwd)) {
		svcerr_decode(transp);
		return;
	}

	/* Perform basic validation */
	if ((!validstr(yppwd.newpw.pw_passwd, CRYPTPWSIZE)) ||
		(!validstr(yppwd.newpw.pw_name, UTUSERLEN)) ||
		(!validstr(yppwd.newpw.pw_gecos, FINGERSIZE)) ||
		(!validstr(yppwd.newpw.pw_shell, SHELLSIZE))) {
		svcerr_decode(transp);
		return;
	}

	/*
	 * Special case: root on the master server can change other
	 * users' passwords without first entering the old password.
	 * We need to ensure that this is indeed root on the master
	 * server. (bug 1253949)
	 */
	if (strcmp(transp->xp_netid, "ticlts") == 0) {
		svc_local_cred_t cred;
		if (!svc_get_local_cred(transp, &cred)) {
			logmsg(MSG_NOTIMECHECK, LOG_ERR,
					"Couldn't get local user credentials");
		} else if (cred.ruid == 0)
			root_on_master = TRUE;
	}

	/*
	 * Get the domain name. This is tricky because a N2L server may be
	 * handling multiple domains. There is nothing in the request to
	 * indicate which one we are trying to change a passwd for. First
	 * we try to get a list of password related domains from the mapping
	 * file.
	 */
	if (0 !=
	    (dom_count = get_mapping_yppasswdd_domain_list(&domain_list))) {
		/* Got a domain list ... process all the domains */
		for (i = 0; i < dom_count; i ++) {
			ret = proc_domain(&yppwd, root_on_master,
								domain_list[i]);

			/* If one has worked don't care if others fail */
			if (0 != ans)
				ans = ret;
		}
	}
	else
	{
		/*
		 * There was no domain list in the mapping file. The
		 * traditional version of this code calls ypmake which picks
		 * up the domain returned by getdomainname(). Fall back to the
		 * same mechanism.
		 */
		if (0 > getdomainname(domain, MAXNETNAMELEN+1)) {
			logmsg(MSG_NOTIMECHECK, LOG_ERR,
					"Could not get any domain info");
		} else {
			/* Got one domain ... process it. */
			ans = proc_domain(&yppwd, root_on_master, domain);
		}
	}

	/* Send reply packet */
	if (!svc_sendreply(transp, xdr_int, (char *)&ans))
		logmsg(MSG_NOTIMECHECK, LOG_WARNING,
						"could not reply to RPC call");
}

/*
 * FUNCTION : 	proc_domain()
 *
 * DESCRIPTION:	Process a request for one domain
 *
 * GIVEN :	Pointer to the request.
 *		Root on master flag
 *		Domain
 *
 * OUTPUTS :	Answer code for reply
 */
int
proc_domain(struct yppasswd *yppwd, bool_t root_on_master, char *domain)
{
	struct passwd_entry *old_pwd;
	char	*p;
	int ans = 2;

	/* security hole fix from original source */
	for (p = yppwd->newpw.pw_name; (*p != '\0'); p++)
		if ((*p == ':') || !(isprint(*p)))
			*p = '$';	/* you lose buckwheat */
	for (p = yppwd->newpw.pw_passwd; (*p != '\0'); p++)
		if ((*p == ':') || !(isprint(*p)))
			*p = '$';	/* you lose buckwheat */

	/* Get old info from DIT for this domain */
	old_pwd = get_old_info(yppwd->newpw.pw_name, domain);
	if (NULL ==  old_pwd) {
		logmsg(MSG_NOTIMECHECK, LOG_ERR,
				"Could not get old information for %s in "
				"domain %s", yppwd->newpw.pw_name, domain);
		return (ans);
	}

	/* Have a request that can be replied to */
	ans = proc_request(yppwd, old_pwd, root_on_master, domain);
	free_pwd_entry(old_pwd);

	return (ans);
}

/*
 * FUNCTION :	proc_request()
 *
 * DESCRIPTION:	Process a request
 *
 * GIVEN :	Pointer to the request.
 *		Pointer to old information from LDAP
 *		Root on master flag
 *		Domain
 *
 * OUTPUTS :	Answer code for reply
 */
int
proc_request(struct yppasswd *yppwd, struct passwd_entry *old_pwd,
					bool_t root_on_master, char *domain)
{
	struct sigaction sa, osa1, osa2, osa3;
	int	ans;

	/* Authenticate */
	if ((0 != strcmp(crypt(yppwd->oldpass, old_pwd->pw_passwd),
				old_pwd->pw_passwd)) && !root_on_master) {
		logmsg(MSG_NOTIMECHECK, LOG_NOTICE, "Passwd incorrect %s",
						yppwd->newpw.pw_name);
		return (7);
	}

	/* Work out what we have to change and change it */
	ans = modify_ent(yppwd, old_pwd, root_on_master, domain);
	if (0 != ans)
		return (ans);

	/*
	 * Generate passwd and adjunct map entries. This creates extra
	 * malloced strings in old_pwd. These will be freed when
	 * free_pwd_entry() is called to free up the rest of the structure.
	 */
	old_pwd->pwd_str = create_pwd_str(old_pwd, FALSE);
	if (NULL == old_pwd->pwd_str) {
		logmsg(MSG_NOTIMECHECK, LOG_ERR,
					"Could not create passwd entry");
		return (2);
	}
	if (old_pwd->adjunct) {
		old_pwd->adjunct_str = create_pwd_str(old_pwd, TRUE);
		if (NULL == old_pwd->adjunct_str) {
			logmsg(MSG_NOTIMECHECK, LOG_ERR,
					"Could not create adjunct entry");
			return (2);
		}
	} else {
		old_pwd->adjunct_str = NULL;
	}

	/* Put the information back to DIT */
	ans = put_new_info(old_pwd, domain);
	if (0 != ans) {
		return (ans);
	}

	/* Are going to be forking pushes, set up signals */
	memset(&sa, 0, sizeof (struct sigaction));
	sa.sa_handler = SIG_IGN;
	sigaction(SIGTSTP, &sa, (struct sigaction *)0);
	sigaction(SIGHUP,  &sa, &osa1);
	sigaction(SIGINT,  &sa, &osa2);
	sigaction(SIGQUIT, &sa, &osa3);

	/* Update and push all the maps */
	ans = proc_maps(domain, old_pwd);

	/* Tidy up signals */
	sigaction(SIGHUP,  &osa1, (struct sigaction *)0);
	sigaction(SIGINT,  &osa2, (struct sigaction *)0);
	sigaction(SIGQUIT, &osa3, (struct sigaction *)0);

	return (ans);
}

/*
 * FUNCTION:	proc_maps()
 *
 * DESCRIPTION: Gets all the map lists and processes them.
 *
 * INPUTS:	Domain name
 *		New info to write into maps
 *
 * OUTPUT :	Answer code
 */
int
proc_maps(char *domain, struct passwd_entry *pwd)
{
	char	**map_list; 	/* Array of passwd or adjunct maps */
	int	ans = 0;

	/* Get list of passwd maps from mapping file */
	map_list = get_passwd_list(FALSE, domain);
	if (map_list != NULL) {
		/* Process list of passwd maps */
		ans = proc_map_list(map_list, domain, pwd, FALSE);
		free_passwd_list(map_list);
		if (0 != ans)
			return (ans);
	}

	/*
	 * If we get here either there were no passwd maps or there were
	 * some and they were processed successfully. Either case is good
	 * continue and process passwd.adjunct maps.
	 */

	/* Get list of adjunct maps from mapping file */
	map_list = get_passwd_list(TRUE, domain);
	if (map_list != NULL) {
		/*
		 * Process list of adjunct maps. If the required information
		 * is not present in LDAP then the updates attempts will log
		 * an error. No need to make the check here
		 */
		ans = proc_map_list(map_list, domain, pwd, TRUE);
		free_passwd_list(map_list);
	}

	return (ans);
}

/*
 * FUNCTION:	proc_map_list()
 *
 * DESCRIPTION: Finds entries in one list of map that need to be updated.
 *		updates them and writes them back.
 *
 * INPUTS:	Null terminated list of maps to process.
 *		Domain name
 *		Information to write (including user name)
 *		Flag indicating if this is the adjunct list
 *
 * OUTPUTS:	An error code
 */
int
proc_map_list(char **map_list, char *domain,
				struct passwd_entry *pwd, bool_t adjunct_flag)
{
	char 	*myself = "proc_map_list";
	char	*map_name;
	char	cmdbuf[BUFSIZ];
	int	map_name_len = 0;
	int	index, ans = 0;
	int	res;

	/* If this is a adjunct list check LDAP had some adjunct info */
	if ((adjunct_flag) && (!pwd->adjunct)) {
		logmsg(MSG_NOTIMECHECK, LOG_INFO,
			"Have adjunct map list but no adjunct data in DIT");
		/* Not a disaster */
		return (0);
	}

	/* Allocate enough buffer to take longest map name */
	for (index = 0; map_list[index] != NULL; index ++)
		if (map_name_len < strlen(map_list[index]))
			map_name_len = strlen(map_list[index]);
	map_name_len += strlen(YPDBPATH);
	map_name_len += strlen(NTOL_PREFIX);
	map_name_len += strlen(domain);
	map_name_len += 3;
	if (NULL == (map_name = am(myself, map_name_len))) {
		logmsg(MSG_NOMEM, LOG_ERR, "Could not alloc map name");
		return (2);
	}

	/* For all maps in list */
	for (index = 0; map_list[index] != NULL; index ++) {

		/* Generate full map name */
		strcpy(map_name, YPDBPATH);
		add_separator(map_name);
		strcat(map_name, domain);
		add_separator(map_name);
		strcat(map_name, NTOL_PREFIX);
		strcat(map_name, map_list[index]);

		if (0 != (ans = update_single_map(map_name, pwd, adjunct_flag)))
			break;
	}

	/* Done with full map path */
	sfree(map_name);

	/*
	 * If (ans != 0) then one more maps have failed. LDAP has however been
	 * updates. This is the definitive source for information there is no
	 * need to unwind. (This was probably due to maps that were already
	 * corrupt).
	 */

	/*
	 * If it all worked fork off push operations for the maps. Since we
	 * want the map to end up with it's traditional name on the slave send
	 * the name without its LDAP_ prefix. The slave will call ypxfrd
	 * which, since it is running in N2L mode, will put the prefix back on
	 * before reading the file.
	 */
	if (mflag && (0 == ans)) {
		for (index = 0; (map_name = map_list[index]) != NULL;
								index ++) {
			if (fork() == 0) {
				/*
				 * Define full path to yppush. Probably also
				 * best for security.
				 */
				strcpy(cmdbuf, "/usr/lib/netsvc/yp/yppush ");
				strcat(cmdbuf, map_name);
				if (0 > system(cmdbuf))
					logmsg(MSG_NOTIMECHECK, LOG_ERR,
						"Could not initiate yppush");
				exit(0);
			}
		}
	}
	return (ans);
}

/*
 * FUNCTION :	update_single_map()
 *
 * DESCRIPTION:	Updates one map. This is messy because we want to lock the map
 *		to prevent other processes from updating it at the same time.
 *		This mandates that we open it using the shim. When we
 *		write to it however we DO NOT want to write through to LDAP
 *		i.e. do not want to use the shim.
 *
 *		Solution : Do not include shim_hooks.h but call the shim
 *		versions of dbm_functions explicitly where needed.
 *
 * INPUT :	Full name of map
 *		Information to write (including user name)
 *		Flag indicating if this is an adjunct map.
 *
 * OUTPUT :	Answer code
 *
 */
int
update_single_map(char *map_name, struct passwd_entry *pwd, bool_t adjunct_flag)
{
	DBM	*map;
	int	res;
	datum	data, key;

	/* Set up data */
	if (adjunct_flag)
		data.dptr = pwd->adjunct_str;
	else
		data.dptr = pwd->pwd_str;
	data.dsize = strlen(data.dptr);

	/* Set up key dependent on which type of map this is */
	key.dptr = NULL;
	if (strend(map_name, BYNAME))
		key.dptr = pwd->pw_name;
	if (strend(map_name, BYUID))
		key.dptr = pwd->pw_uid;
	if (strend(map_name, BYGID))
		key.dptr = pwd->pw_gid;

	if (NULL == key.dptr) {
		logmsg(MSG_NOTIMECHECK, LOG_ERR,
					"Unrecognized map type %s", map_name);
		return (0);		/* Next map */
	}
	key.dsize = strlen(key.dptr);

	/* Open the map */
	map = shim_dbm_open(map_name, O_RDWR, 0600);
	if (NULL == map) {
		logmsg(MSG_NOTIMECHECK, LOG_ERR, "Could not open %s", map_name);
		return (0);		/* Next map */
	}

	/* Lock map for update. Painful and may block but have to do it */
	if (SUCCESS != lock_map_update((map_ctrl *)map)) {
		logmsg(MSG_NOTIMECHECK, LOG_ERR,
				"Could not lock map %s for update", map_name);
		shim_dbm_close(map);
		return (2);
	}

	/* Do the update use simple DBM operation */
	res = dbm_store(((map_ctrl *)map)->entries, key, data, DBM_REPLACE);

	/* update entry TTL. If we fail not a problem will just timeout early */
	update_entry_ttl((map_ctrl *)map, &key, TTL_RAND);

	/*
	 * Map has been modified so update YP_LAST_MODIFIED. In the vanilla
	 * NIS case this would have been done by the ypmake done after updating
	 * the passwd source file. If this fails not a great problem the map
	 */
	if (FAILURE == update_timestamp(((map_ctrl *)map)->entries)) {
		logmsg(MSG_NOTIMECHECK, LOG_ERR, "Could not update "
			"YP_LAST_MODIFIED %s will not be pushed this time",
			map_name);
	}

	/*
	 * Possibly should hold the lock until after push is complete
	 * but this could deadlock if client is slow and ypxfrd also
	 * decides to do an update.
	 */
	unlock_map_update((map_ctrl *)map);

	/* Close the map */
	shim_dbm_close(map);

	if (0 != res) {
		logmsg(MSG_NOTIMECHECK, LOG_ERR,
					"Could not update map %s", map_name);
		return (2);
	}

	return (0);
}

/*
 * FUNCTION :	strend()
 *
 * DESCRIPTION:	Determines if one string ends with another.
 */
bool_t
strend(char *s1, char *s2)
{
	int len_dif;

	len_dif = strlen(s1) - strlen(s2);
	if (0 > len_dif)
		return (FALSE);
	if (0 == strcmp(s1 + len_dif, s2))
		return (TRUE);
	return (FALSE);
}

/*
 * FUNCTION:	modify_ent()
 *
 * DESCRIPTION: Modify an entry to reflect a request.
 *
 * INPUT:	Pointer to the request.
 *		Pointer to the entry to modify.
 *		Flag indication if we are root on master
 *		Domain
 *
 * OUTPUT:	Error code
 */
int
modify_ent(struct yppasswd *yppwd, struct passwd_entry *old_ent,
					bool_t root_on_master, char *domain)
{
	int change_list;
	struct spwd *shadow;
	time_t now;

	/* Get list of changes */
	change_list = get_change_list(yppwd, old_ent);

	if (!change_list) {
		logmsg(MSG_NOTIMECHECK, LOG_NOTICE,
				"No change for %s", yppwd->newpw.pw_name);
		return (3);
	}

	/* Check that the shell we have been given is acceptable. */
	if ((change_list & CNG_SH) && (!validloginshell(old_ent->pw_shell,
					yppwd->newpw.pw_shell, root_on_master)))
		return (2);

	/*
	 * If changing the password do any aging checks.
	 * Since there are no shadow maps this is done by accessing
	 * attributes in the DIT via the mapping system.
	 */
	if (change_list & CNG_PASSWD) {

		/* Try to get shadow information */
		shadow = get_old_shadow(yppwd->newpw.pw_name, domain);

		/* If there is shadow information make password aging checks */
		if (NULL != shadow) {
			now = DAY_NOW;
			/* password aging - bug for bug compatibility */
			if (shadow->sp_max != -1) {
				if (now < shadow->sp_lstchg + shadow->sp_min) {
					logmsg(MSG_NOTIMECHECK, LOG_ERR,
					"Sorry: < %ld days since "
					"the last change", shadow->sp_min);
					free_shadow_entry(shadow);
					return (2);
				}
		}

			/* Update time of change */
			shadow->sp_lstchg = now;

			/* Write it back */
			write_shadow_info(domain, shadow);

			free_shadow_entry(shadow);
		}
	}

	/* Make changes to old entity */
	if (change_list & CNG_GECOS) {
		if (NULL != old_ent->pw_gecos)
			sfree(old_ent->pw_gecos);
		old_ent->pw_gecos = strdup(yppwd->newpw.pw_gecos);
		if (NULL == old_ent->pw_gecos) {
			logmsg(MSG_NOMEM, LOG_ERR, "Could not allocate gecos");
			return (2);
		}
	}

	if (change_list & CNG_SH) {
		if (NULL != old_ent->pw_shell)
			sfree(old_ent->pw_shell);
		old_ent->pw_shell = strdup(yppwd->newpw.pw_shell);
		if (NULL == old_ent->pw_shell) {
			logmsg(MSG_NOMEM, LOG_ERR, "Could not allocate shell");
			return (2);
		}
	}

	if (change_list & CNG_PASSWD) {
		if (NULL != old_ent->pw_passwd)
			sfree(old_ent->pw_passwd);
		old_ent->pw_passwd = strdup(yppwd->newpw.pw_passwd);
		if (NULL == old_ent->pw_passwd) {
			logmsg(MSG_NOMEM, LOG_ERR, "Could not allocate passwd");
			return (2);
		}
	}

	return (0);
}

/*
 * FUNCTION :	get_change_list()
 *
 * DESCRIPTION:	Works out what we have to change.
 *
 * INPUTS :	Request.
 *		Structure containing current state of entry
 *
 * OUTPUTS :	A bitmask signaling what to change. (Implemented in this
 *		way to make it easy to pass between functions).
 */
int
get_change_list(struct yppasswd *yppwd, struct passwd_entry *old_ent)
{
	int list = 0;
	char *p;

	p = yppwd->newpw.pw_passwd;
	if ((!nopw) &&
		p && *p &&
		!(*p++ == '#' && *p++ == '#' &&
		(strcmp(p, old_ent->pw_name) == 0)) &&
		(strcmp(crypt(old_ent->pw_passwd,
			yppwd->newpw.pw_passwd), yppwd->newpw.pw_passwd) != 0))
		list |= CNG_PASSWD;

	if ((NULL != old_ent->pw_shell) &&
		(!noshell) &&
		(strcmp(old_ent->pw_shell, yppwd->newpw.pw_shell) != 0)) {
		if (single)
			list = 0;
		list |= CNG_SH;
	}

	if ((NULL != old_ent->pw_gecos) &&
		(!nogecos) &&
		(strcmp(old_ent->pw_gecos, yppwd->newpw.pw_gecos) != 0)) {
		if (single)
			list = 0;
		list |= CNG_GECOS;
	}

	return (list);
}

/*
 * FUNCTION :	decode_pwd_entry()
 *
 * DESCRIPTION:	Pulls apart a password entry. Because the password entry has
 *		come from the mapping system it can be assumed to be correctly
 *		formatted and relatively simple parsing can be done.
 *
 *		Substrings are put into malloced memory. Caller to free.
 *
 *		For adjunct files most of it is left empty.
 *
 *		It would be nice to use getpwent and friends for this work but
 *		these only seem to exist for files and it seems excessive to
 *		create a temporary file for this operation.
 *
 * INPUTS:	Pointer to datum containing password string.
 *		Pointer to structure in which to return results
 *		Flag indicating if we are decoding passwd or passwd.adjunct
 *
 * OUTPUTS:	SUCCESS = Decoded successfully
 *		FAILURE = Not decoded successfully. Caller to tidy up.
 */
suc_code
decode_pwd_entry(datum *data, struct passwd_entry *pwd, bool_t adjunct)
{
	char *myself = "decode_pwd_entry";
	char *p, *str_end, *temp;

	/* Work out last location in string */
	str_end = data->dptr + data->dsize;

	/* Name */
	if (NULL == (p = get_next_token(data->dptr, &temp, str_end)))
		return (FAILURE);
	if (adjunct) {
		/* If we found an adjunct version this is the one to use */
		if (NULL != pwd->pw_name)
			sfree(pwd->pw_name);
	}
	pwd->pw_name = temp;

	/* Password */
	if (NULL == (p = get_next_token(p, &temp, str_end)))
		return (FAILURE);
	if (adjunct) {
		/* If we found an adjunct version this is the one to use */
		if (NULL != pwd->pw_passwd)
			sfree(pwd->pw_passwd);
	}
	pwd->pw_passwd = temp;

	if (adjunct) {
		/* Store adjunct information in opaque string */
		pwd->adjunct_tail = am(myself, str_end - p + 1);
		if (NULL == pwd->adjunct_tail)
			return (FAILURE);
		strncpy(pwd->adjunct_tail, p, str_end - p);
		pwd->adjunct_tail[str_end - p] = '\0';

		/* Remember that LDAP contained adjunct data */
		pwd->adjunct = TRUE;
		return (SUCCESS);
	}

	/* If we get here not adjunct. Decode rest of passwd */

	/* UID */
	if (NULL == (p = get_next_token(p, &(pwd->pw_uid), str_end)))
		return (FAILURE);

	/* GID */
	if (NULL == (p = get_next_token(p, &(pwd->pw_gid), str_end)))
		return (FAILURE);

	/* Gecos */
	if (NULL == (p = get_next_token(p, &(pwd->pw_gecos), str_end)))
		return (FAILURE);

	/* Home dir */
	if (NULL == (p = get_next_token(p, &(pwd->pw_dir), str_end)))
		return (FAILURE);

	/* Shell may not be present so don't check return */
	get_next_token(p, &(pwd->pw_shell), str_end);

	if (NULL == pwd->pw_shell)
		return (FAILURE);

	return (SUCCESS);
}

/*
 * FUNCTION :	get_next_token()
 *
 * DESCRIPTION:	Gets the next token from a string upto the next colon or the
 *		end of the string. The duplicates this token into malloced
 *		memory removing any spaces.
 *
 * INPUTS :	String to search for token. NOT NULL TERMINATED
 *		Location to return result (NULL if result not required)
 *		Last location in string
 *
 * OUTPUT :	Pointer into the string immediately after the token.
 *		NULL if end of string reached or error.
 */
static char *
get_next_token(char *str, char **op, char *str_end)
{
	char *myself = "get_next_token";
	char *p, *tok_start, *tok_end;

	p = str;
	/* Skip leading whitespace */
	while (' ' == *p)
		p++;
	tok_start = p;
	tok_end = p;

	while ((str_end + 1 != p) && (COLON_CHAR != *p)) {
		if (' ' != *p)
			tok_end = p;
		p++;
	}

	/* Required string is now between start and end */
	if (NULL != op) {
		*op = am(myself, tok_end - tok_start + 2);
		if (NULL == *op) {
			logmsg(MSG_NOMEM, LOG_ERR,
					"Could not alloc memory for token");
			return (NULL);
		}
		strncpy(*op, tok_start, tok_end - tok_start + 1);

		/* Terminate token */
		(*op)[tok_end - tok_start + 1] = '\0';

	}

	/* Check if we reached the end of the input string */
	if ('\0' == *p)
		return (NULL);

	/* There is some more */
	p++;
	return (p);
}

/*
 * FUNCTION :	free_pwd_entry()
 *
 * DESCRIPTION:	Frees up a pwd_entry structure and its contents.
 *
 * INPUTS:	Pointer to the structure to free.
 *
 * OUTPUT:	Nothing
 */
void
free_pwd_entry(struct passwd_entry *pwd)
{
	/* Free up strings */
	if (NULL != pwd->pw_name)
		sfree(pwd->pw_name);

	if (NULL != pwd->pw_passwd)
		sfree(pwd->pw_passwd);

	if (NULL != pwd->pw_gecos)
		sfree(pwd->pw_gecos);

	if (NULL != pwd->pw_shell)
		sfree(pwd->pw_shell);

	if (NULL != pwd->pw_dir)
		sfree(pwd->pw_dir);

	if (NULL != pwd->adjunct_tail)
		sfree(pwd->adjunct_tail);

	if (NULL != pwd->pwd_str)
		sfree(pwd->pwd_str);

	if (NULL != pwd->adjunct_str)
		sfree(pwd->adjunct_str);

	/* Free up structure */
	sfree(pwd);
}

/*
 * FUNCTION :	create_pwd_str()
 *
 * DESCRIPTION:	Builds up a new password entity string from a passwd structure.
 *
 * INPUTS :	Structure containing password details
 *		Flag indicating if we should create an adjunct or passwd string.
 *
 * OUTPUTS :	String in malloced memory (to be freed by caller).
 *		NULL on failure.
 */
char *
create_pwd_str(struct passwd_entry *pwd, bool_t adjunct)
{
	char *myself = "create_pwd_str";
	char *s;
	int len;

	/* Separator string so we can strcat separator onto things */
	char sep_str[2] = {COLON_CHAR, '\0'};

	/* Work out the size */
	len = strlen(pwd->pw_name) + 1;
	len += strlen(pwd->pw_passwd) + 1;
	if (adjunct) {
		len += strlen(pwd->adjunct_tail) + 1;
	} else {
		len += strlen(pwd->pw_uid) + 1;
		len += strlen(pwd->pw_gid) + 1;
		len += strlen(pwd->pw_gecos) + 1;
		len += strlen(pwd->pw_dir) + 1;
		len += strlen(pwd->pw_shell) + 1;
	}

	/* Allocate some memory for it */
	s = am(myself, len);
	if (NULL == s)
		return (NULL);

	strcpy(s, pwd->pw_name);
	strcat(s, sep_str);
	if (!adjunct) {
		/* Build up a passwd string */

		/* If LDAP contains adjunct info then passwd is 'x' */
		if (pwd->adjunct) {
			strcat(s, "##");
			strcat(s,  pwd->pw_name);
		} else {
			strcat(s, pwd->pw_passwd);
		}
		strcat(s, sep_str);
		strcat(s, pwd->pw_uid);
		strcat(s, sep_str);
		strcat(s, pwd->pw_gid);
		strcat(s, sep_str);
		strcat(s, pwd->pw_gecos);
		strcat(s, sep_str);
		strcat(s, pwd->pw_dir);
		strcat(s, sep_str);
		strcat(s, pwd->pw_shell);
	} else {
		/* Build up a passwd_adjunct string */
		strcat(s, pwd->pw_passwd);
		strcat(s, sep_str);
		strcat(s, pwd->adjunct_tail);
	}

	return (s);
}

/*
 * FUNCTION:	get_old_info()
 *
 * DESCRIPTION:	Gets as much information as possible from LDAP about one user.
 *
 *		This goes through the mapping system. This is messy because
 *		them mapping system will build up a password entry from the
 *		contents of the DIT. We then have to parse this to recover
 *		it's individual fields.
 *
 * INPUT:	Pointer to user name
 *		Domain
 *
 * OUTPUT:	The info in malloced space. To be freed by caller.
 *		NULL on failure.
 */
struct passwd_entry *
get_old_info(char *name, char *domain)
{
	char *myself = "get_old_info";
	struct passwd_entry *old_passwd;
	char	*p;
	datum	key, data;
	suc_code res;

	/* Get the password entry */
	key.dptr = name;
	key.dsize = strlen(key.dptr);
	read_from_dit(PASSWD_MAPPING, domain, &key, &data);
	if (NULL == data.dptr) {
		logmsg(MSG_NOTIMECHECK, LOG_ERR,
					"Could not read old pwd for %s", name);
		return (NULL);
	}

	/* Pull password apart */
	old_passwd = am(myself, sizeof (struct passwd_entry));
	if (NULL == old_passwd) {
		logmsg(MSG_NOMEM, LOG_ERR, "Could not alloc for pwd decode");
		sfree(data.dptr);
		return (NULL);
	}

	/* No data yet */
	old_passwd->pw_name = NULL;
	old_passwd->pw_passwd = NULL;
	old_passwd->pw_uid = NULL;
	old_passwd->pw_gid = NULL;
	old_passwd->pw_gecos = NULL;
	old_passwd->pw_dir = NULL;
	old_passwd->pw_shell = NULL;
	old_passwd->adjunct_tail = NULL;
	old_passwd->pwd_str = NULL;
	old_passwd->adjunct_str = NULL;
	old_passwd->adjunct = FALSE;

	res = decode_pwd_entry(&data, old_passwd, FALSE);
	sfree(data.dptr);
	if (SUCCESS != res) {
		free_pwd_entry(old_passwd);
		return (NULL);
	}

	/* Try to get the adjunct entry */
	read_from_dit(PASSWD_ADJUNCT_MAPPING, domain, &key, &data);
	if (NULL == data.dptr) {
		/* Fine just no adjunct data */
		old_passwd->adjunct = FALSE;
	} else {
		res = decode_pwd_entry(&data, old_passwd, TRUE);
		sfree(data.dptr);
		if (SUCCESS != res) {
			free_pwd_entry(old_passwd);
			return (NULL);
		}
	}

	return (old_passwd);
}

/*
 * FUNCTION :	put_new_info()
 *
 * DESCRIPTION:	Generates new map strings and puts them back to LDAP
 *
 * INPUTS:	Info to put back
 *		Domain
 *
 * OUTPUT:	Answer code.
 */
int
put_new_info(struct passwd_entry *pwd, char *domain)
{
	datum	key, data;

	/* Write it back to LDAP */
	data.dptr = pwd->pwd_str;
	data.dsize = strlen(data.dptr);
	key.dptr = pwd->pw_name;
	key.dsize = strlen(key.dptr);
	if (SUCCESS != write_to_dit(PASSWD_MAPPING, domain, key, data,
								TRUE, FALSE))
		return (2);


	/* If DIT contains adjunct information do the same for adjunct */
	if (pwd->adjunct) {
		data.dptr = pwd->adjunct_str;
		data.dsize = strlen(data.dptr);
		key.dptr = pwd->pw_name;
		key.dsize = strlen(key.dptr);
		if (SUCCESS != write_to_dit(PASSWD_ADJUNCT_MAPPING, domain,
						key, data, TRUE, FALSE))
			return (2);
	}

	return (0);

}

/*
 * FUNCTION :   get_old_shadow()
 *
 * DESCRIPTION :Extracts and decodes shadow information from the DIT
 *		See also comments under decode_pwd_entry().
 *
 * INPUTS :     User name
 *		Domain name
 *
 * OUTPUT :     Shadow information in malloced memory. To be freed by caller.
 */
struct spwd *
get_old_shadow(char *name, char *domain)
{
	char *myself = "get_old_shadow";
	struct spwd *sp;
	datum key, data;
	suc_code res;

	/* Get the info */
	key.dptr = name;
	key.dsize = strlen(key.dptr);	/* Len excluding terminator */
	read_from_dit(AGEING_MAPPING, domain, &key, &data);

	if (NULL == data.dptr) {
		/* OK just have no shadow info in DIT */
		return (NULL);
	}

	/* Pull shadow apart */
	if (NULL == (sp = am(myself, sizeof (struct spwd)))) {
		logmsg(MSG_NOMEM, LOG_ERR,
					"Could not alloc for shadow decode");
		sfree(data.dptr);
		return (NULL);
	}
	sp->sp_namp = NULL;
	sp->sp_pwdp = NULL;

	res = decode_shadow_entry(&data, sp);
	sfree(data.dptr);
	if (SUCCESS != res) {
		free_shadow_entry(sp);
		return (NULL);
	}

	return (sp);
}

/*
 * FUNCTION :	decode_shadow_entry()
 *
 * DESCRIPTION:	Pulls apart ageing information. For convenience this is stored
 *		in a partially filled spwd structure.
 *
 *		SEE COMMENTS FOR decode_pwd_entry()
 */
suc_code
decode_shadow_entry(datum *data, struct spwd *sp)
{
	char *p, *str_end, *temp;

	/* Work out last location in string */
	str_end = data->dptr + data->dsize;

	/* Name */
	if (NULL == (p = get_next_token(data->dptr, &(sp->sp_namp), str_end)))
		return (FAILURE);

	/* date of last change */
	if (NULL == (p = get_next_token(p, &temp, str_end)))
		return (FAILURE);
	sp->sp_lstchg = atoi(temp);

	/* min days to passwd change */
	if (NULL == (p = get_next_token(p, &temp, str_end)))
		return (FAILURE);
	sp->sp_min = atoi(temp);

	/* max days to passwd change */
	if (NULL == (p = get_next_token(p, &temp, str_end)))
		return (FAILURE);
	sp->sp_max = atoi(temp);

	/* warning period */
	if (NULL == (p = get_next_token(p, &temp, str_end)))
		return (FAILURE);
	sp->sp_warn = atoi(temp);

	/* max days inactive */
	if (NULL == (p = get_next_token(p, &temp, str_end)))
		return (FAILURE);
	sp->sp_inact = atoi(temp);

	/* account expiry date */
	if (NULL == (p = get_next_token(p, &temp, str_end)))
		return (FAILURE);
	sp->sp_expire = atoi(temp);

	/* flag  */
	if (NULL != (p = get_next_token(p, &temp, str_end)))
		return (FAILURE);
	sp->sp_flag = atoi(temp);

	return (SUCCESS);
}

/*
 * FUNCTION :	write_shadow_info()
 *
 * DESCRIPTION:	Writes shadow information back to the DIT.
 *
 * INPUTS :	Domain
 *		Information to write
 *
 * OUTPUT :	Success code
 *
 */
suc_code
write_shadow_info(char *domain, struct spwd *sp)
{
	char *myself = "write_shadow_info";
	datum key, data;
	char *str;
	suc_code res;
	int len;

	/* Work out how long string will be */
	len = strlen(sp->sp_namp) + 1;

	/*
	 * Bit crude but if we assume 1 byte is 3 decimal characters
	 * will get enough buffer for the longs and some spare.
	 */
	len += 7 * (3 * sizeof (long) + 1);

	/* Allocate some memory */
	str = am(myself, len);
	if (NULL == str) {
		logmsg(MSG_NOMEM, LOG_ERR, "Could not aloc for shadow write");
		return (FAILURE);
	}

	/* Build up shadow string */
	sprintf(str, "%s%c%d%c%d%c%d%c%d%c%d%c%d%c%d",
		sp->sp_namp, COLON_CHAR,
		sp->sp_lstchg, COLON_CHAR,
		sp->sp_min, COLON_CHAR,
		sp->sp_max, COLON_CHAR,
		sp->sp_warn, COLON_CHAR,
		sp->sp_inact, COLON_CHAR,
		sp->sp_expire, COLON_CHAR,
		sp->sp_flag);

	/* Write it */
	data.dptr = str;
	data.dsize = strlen(data.dptr);
	key.dptr = sp->sp_namp;
	key.dsize = strlen(key.dptr);
	res = write_to_dit(AGEING_MAPPING, domain, key, data, TRUE, FALSE);

	sfree(str);
	return (res);
}

/*
 * FUNCTION :	free_shadow_entry()
 *
 * DESCRIPTION:	Frees up a shadow information structure
 *
 * INPUTS :	Structure to free
 *
 * OUTPUTS :	Nothing
 */
void
free_shadow_entry(struct spwd *spwd)
{
	if (NULL != spwd->sp_namp)
		sfree(spwd->sp_namp);

	if (NULL != spwd->sp_pwdp)
		sfree(spwd->sp_pwdp);

	/* No need to free numerics */

	/* Free up structure */
	sfree(spwd);
}