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