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