/*
* 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
* 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 <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 <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"
/*
* Undocumented external function in libnsl
*/
extern int getdomainname(char *, int);
/* Constants */
/*
* 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.
*/
/* Names of password and adjunct mappings. Used to access DIT */
/* Bitmasks used in list of fields to change */
/* Globals :-( */
/*
* 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;
};
/* Prototypes */
int put_new_info(struct passwd_entry *, char *);
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 *);
void free_shadow_entry(struct spwd *);
int proc_maps(char *, struct passwd_entry *);
/*
* 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
{
char **domain_list;
int dom_count, i;
/* Clean out yppwd ... maybe we don't trust RPC */
/* Get the RPC args */
return;
}
/* Perform basic validation */
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)
*/
"Couldn't get local user credentials");
}
/*
* 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 !=
/* Got a domain list ... process all the domains */
for (i = 0; i < dom_count; i ++) {
domain_list[i]);
/* If one has worked don't care if others fail */
if (0 != ans)
}
}
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.
*/
"Could not get any domain info");
} else {
/* Got one domain ... process it. */
}
}
/* Send reply packet */
"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
{
char *p;
/* security hole fix from original source */
if ((*p == ':') || !(isprint(*p)))
*p = '$'; /* you lose buckwheat */
if ((*p == ':') || !(isprint(*p)))
*p = '$'; /* you lose buckwheat */
/* Get old info from DIT for this domain */
"Could not get old information for %s in "
return (ans);
}
/* Have a request that can be replied to */
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
{
int ans;
/* Authenticate */
return (7);
}
/* Work out what we have to change and change it */
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.
*/
"Could not create passwd entry");
return (2);
}
"Could not create adjunct entry");
return (2);
}
} else {
}
/* Put the information back to DIT */
if (0 != ans) {
return (ans);
}
/* Are going to be forking pushes, set up signals */
/* Update and push all the maps */
/* Tidy up signals */
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
{
int ans = 0;
/* Get list of passwd maps from mapping file */
/* Process list of passwd maps */
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 */
/*
* 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
*/
}
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
{
char *map_name;
int map_name_len = 0;
/* If this is a adjunct list check LDAP had some adjunct info */
"Have adjunct map list but no adjunct data in DIT");
/* Not a disaster */
return (0);
}
/* Allocate enough buffer to take longest map name */
map_name_len += 3;
return (2);
}
/* For all maps in list */
/* Generate full map name */
break;
}
/* Done with full map path */
/*
* 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.
*/
index ++) {
if (fork() == 0) {
/*
* Define full path to yppush. Probably also
* best for security.
*/
"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
{
int res;
/* Set up data */
if (adjunct_flag)
else
/* Set up key dependent on which type of map this is */
"Unrecognized map type %s", map_name);
return (0); /* Next map */
}
/* Open the map */
return (0); /* Next map */
}
/* Lock map for update. Painful and may block but have to do it */
"Could not lock map %s for update", map_name);
return (2);
}
/* Do the update use simple DBM operation */
/* update entry TTL. If we fail not a problem will just timeout early */
/*
* 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
*/
"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.
*/
/* Close the map */
if (0 != res) {
"Could not update map %s", map_name);
return (2);
}
return (0);
}
/*
* FUNCTION : strend()
*
* DESCRIPTION: Determines if one string ends with another.
*/
{
int len_dif;
if (0 > len_dif)
return (FALSE);
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
{
int change_list;
/* Get list of changes */
if (!change_list) {
return (3);
}
/* Check that the shell we have been given is acceptable. */
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 */
/* If there is shadow information make password aging checks */
/* password aging - bug for bug compatibility */
"Sorry: < %ld days since "
return (2);
}
}
/* Update time of change */
/* Write it back */
}
}
/* Make changes to old entity */
if (change_list & CNG_GECOS) {
return (2);
}
}
if (change_list & CNG_SH) {
return (2);
}
}
if (change_list & CNG_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
{
int list = 0;
char *p;
if ((!nopw) &&
p && *p &&
!(*p++ == '#' && *p++ == '#' &&
list |= CNG_PASSWD;
(!noshell) &&
if (single)
list = 0;
}
(!nogecos) &&
if (single)
list = 0;
}
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.
*/
{
/* Work out last location in string */
/* Name */
return (FAILURE);
if (adjunct) {
/* If we found an adjunct version this is the one to use */
}
/* Password */
return (FAILURE);
if (adjunct) {
/* If we found an adjunct version this is the one to use */
}
if (adjunct) {
/* Store adjunct information in opaque string */
return (FAILURE);
/* Remember that LDAP contained adjunct data */
return (SUCCESS);
}
/* If we get here not adjunct. Decode rest of passwd */
/* UID */
return (FAILURE);
/* GID */
return (FAILURE);
/* Gecos */
return (FAILURE);
/* Home dir */
return (FAILURE);
/* Shell may not be present so don't check return */
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 *
{
p = str;
/* Skip leading whitespace */
while (' ' == *p)
p++;
tok_start = p;
tok_end = p;
if (' ' != *p)
tok_end = p;
p++;
}
/* Required string is now between start and end */
"Could not alloc memory for token");
return (NULL);
}
/* Terminate token */
}
/* 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 up strings */
/* Free up structure */
}
/*
* 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 *
{
char *s;
int len;
/* Separator string so we can strcat separator onto things */
/* Work out the size */
if (adjunct) {
} else {
}
/* Allocate some memory for it */
if (NULL == s)
return (NULL);
if (!adjunct) {
/* Build up a passwd string */
/* If LDAP contains adjunct info then passwd is 'x' */
strcat(s, "##");
} else {
}
} else {
/* Build up a passwd_adjunct string */
}
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 the password entry */
"Could not read old pwd for %s", name);
return (NULL);
}
/* Pull password apart */
if (NULL == old_passwd) {
return (NULL);
}
/* No data yet */
return (NULL);
}
/* Try to get the adjunct entry */
/* Fine just no adjunct data */
} else {
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
{
/* Write it back to LDAP */
return (2);
/* If DIT contains adjunct information do the same for adjunct */
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 the info */
/* OK just have no shadow info in DIT */
return (NULL);
}
/* Pull shadow apart */
"Could not alloc for shadow decode");
return (NULL);
}
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()
*/
{
/* Work out last location in string */
/* Name */
return (FAILURE);
/* date of last change */
return (FAILURE);
/* min days to passwd change */
return (FAILURE);
/* max days to passwd change */
return (FAILURE);
/* warning period */
return (FAILURE);
/* max days inactive */
return (FAILURE);
/* account expiry date */
return (FAILURE);
/* flag */
return (FAILURE);
return (SUCCESS);
}
/*
* FUNCTION : write_shadow_info()
*
* DESCRIPTION: Writes shadow information back to the DIT.
*
* INPUTS : Domain
* Information to write
*
* OUTPUT : Success code
*
*/
{
char *str;
int len;
/* Work out how long string will be */
/*
* Bit crude but if we assume 1 byte is 3 decimal characters
* will get enough buffer for the longs and some spare.
*/
/* Allocate some memory */
return (FAILURE);
}
/* Build up shadow string */
/* Write it */
return (res);
}
/*
* FUNCTION : free_shadow_entry()
*
* DESCRIPTION: Frees up a shadow information structure
*
* INPUTS : Structure to free
*
* OUTPUTS : Nothing
*/
void
{
/* No need to free numerics */
/* Free up structure */
}