/* * 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 2015 Gary Mills * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* DO NOT INCLUDE SHIM_HOOKS.H */ #include "shim.h" #include "yptol.h" #include "../ldap_util.h" /* * Undocumented external function in libnsl */ extern int getdomainname(char *, int); /* 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; /* 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; 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); }