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, Version 1.0 only 6 * (the "License"). You may not use this file except in compliance 7 * with the License. 8 * 9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 10 * or http://www.opensolaris.org/os/licensing. 11 * See the License for the specific language governing permissions 12 * and limitations under the License. 13 * 14 * When distributing Covered Code, include this CDDL HEADER in each 15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 16 * If applicable, add the following below this CDDL HEADER, with the 17 * fields enclosed by brackets "[]" replaced with your own identifying 18 * information: Portions Copyright [yyyy] [name of copyright owner] 19 * 20 * CDDL HEADER END 21 */ 22 /* 23 * Copyright 2003 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #pragma ident "%Z%%M% %I% %E% SMI" 28 29 /* 30 * DESCRIPTION: This is the N2L equivalent of changepasswd.c. The traditional 31 * version modifies the NIS source files and then initiates a 32 * ypmake to make the maps and push them. 33 * 34 * For N2L there are no source files and the policy is that the 35 * definitive information is that contained in the DIT. Old 36 * information is read from LDAP. Assuming this authenticates, and 37 * the change is acceptable, this information is modified and 38 * written back to LDAP. 39 * 40 * Related map entries are then found and updated finally 41 * yppushes of the changed maps are initiated. Since the 42 * definitive information has already correctly been updated the 43 * code is tolerant of some errors during this operation. 44 * 45 * What was previously in the maps is irrelevant. 46 * 47 * Some less than perfect code (like inline constants for 48 * return values and a few globals) is retained from the original. 49 */ 50 51 #include <sys/types.h> 52 #include <sys/stat.h> 53 #include <ctype.h> 54 #include <unistd.h> 55 #include <stdlib.h> 56 #include <string.h> 57 #include <stdio.h> 58 #include <errno.h> 59 #include <syslog.h> 60 #include <pwd.h> 61 #include <signal.h> 62 #include <crypt.h> 63 #include <rpc/rpc.h> 64 #include <rpcsvc/yppasswd.h> 65 #include <utmpx.h> 66 #include <shadow.h> 67 68 #include <ndbm.h> 69 /* DO NOT INCLUDE SHIM_HOOKS.H */ 70 #include "shim.h" 71 #include "yptol.h" 72 #include "../ldap_util.h" 73 74 /* Constants */ 75 #define CRYPTPWSIZE CRYPT_MAXCIPHERTEXTLEN 76 #define STRSIZE 100 77 #define FINGERSIZE (4 * STRSIZE - 4) 78 #define SHELLSIZE (STRSIZE - 2) 79 80 #define UTUSERLEN (sizeof (((struct utmpx *)0)->ut_user)) 81 #define COLON_CHAR ':' 82 83 /* 84 * Path to DBM files. This is only required for N2L mode. Traditional mode 85 * works with the source files and uses the NIS Makefile to generate the maps. 86 * Seems to be hard coded in the rest of NIS so same is done here. 87 */ 88 #define YPDBPATH "/var/yp" 89 90 /* Names of password and adjunct mappings. Used to access DIT */ 91 #define BYNAME ".byname" 92 #define BYUID ".byuid" 93 #define BYGID ".bygid" 94 #define PASSWD_MAPPING "passwd" BYNAME 95 #define PASSWD_ADJUNCT_MAPPING "passwd.adjunct" BYNAME 96 #define AGEING_MAPPING "ageing" BYNAME 97 98 /* Bitmasks used in list of fields to change */ 99 #define CNG_PASSWD 0x0001 100 #define CNG_SH 0x0002 101 #define CNG_GECOS 0x0004 102 103 /* Globals :-( */ 104 extern int single, nogecos, noshell, nopw, mflag; 105 106 /* 107 * Structure for containing the information is currently in the DIT. This is 108 * similar to the passwd structure defined in getpwent(3C) apart from. 109 * 110 * 1. Since GID and UID are never changed they are not converted to integers. 111 * 2. There are extra fields to hold adjunct information. 112 * 3. There are extra fields to hold widely used information. 113 */ 114 struct passwd_entry { 115 char *pw_name; 116 char *pw_passwd; 117 char *pw_uid; 118 char *pw_gid; 119 char *pw_gecos; 120 char *pw_dir; 121 char *pw_shell; 122 char *adjunct_tail; /* Tail of adjunct entry (opaque) */ 123 bool_t adjunct; /* Flag indicating if DIT has adjunct info */ 124 char *pwd_str; /* New password string */ 125 char *adjunct_str; /* New adjunct string */ 126 }; 127 128 /* Prototypes */ 129 extern bool_t validloginshell(char *sh, char *arg, int); 130 extern int validstr(char *str, size_t size); 131 132 suc_code write_shadow_info(char *, struct spwd *); 133 int put_new_info(struct passwd_entry *, char *); 134 char *create_pwd_str(struct passwd_entry *, bool_t); 135 int proc_domain(struct yppasswd *, bool_t, char *); 136 int proc_request(struct yppasswd *, struct passwd_entry *, bool_t, char *); 137 int modify_ent(struct yppasswd *, struct passwd_entry *t, bool_t, char *); 138 int get_change_list(struct yppasswd *, struct passwd_entry *); 139 struct passwd_entry *get_old_info(char *, char *); 140 static char *get_next_token(char *, char **, char *); 141 void free_pwd_entry(struct passwd_entry *); 142 struct spwd *get_old_shadow(char *, char *); 143 suc_code decode_shadow_entry(datum *, struct spwd *); 144 void free_shadow_entry(struct spwd *); 145 int proc_maps(char *, struct passwd_entry *); 146 int proc_map_list(char **, char *, struct passwd_entry *, bool_t); 147 int update_single_map(char *, struct passwd_entry *, bool_t); 148 bool_t strend(char *s1, char *s2); 149 150 /* 151 * FUNCTION: shim_changepasswd() 152 * 153 * DESCRIPTION: N2L version of changepasswd(). When this is called 'useshadow' 154 * etc. will have been set up but are meaningless. We work out 155 * what to change based on information from the DIT. 156 * 157 * INPUTS: Identical to changepasswd() 158 * 159 * OUTPUTS: Identical to changepasswd() 160 */ 161 void 162 shim_changepasswd(SVCXPRT *transp) 163 { 164 struct yppasswd yppwd; 165 bool_t root_on_master = FALSE; 166 char domain[MAXNETNAMELEN+1]; 167 char **domain_list; 168 int dom_count, i; 169 170 int ret, ans = 2; /* Answer codes */ 171 172 /* Clean out yppwd ... maybe we don't trust RPC */ 173 memset(&yppwd, 0, sizeof (struct yppasswd)); 174 175 /* Get the RPC args */ 176 if (!svc_getargs(transp, xdr_yppasswd, (caddr_t)&yppwd)) { 177 svcerr_decode(transp); 178 return; 179 } 180 181 /* Perform basic validation */ 182 if ((!validstr(yppwd.newpw.pw_passwd, CRYPTPWSIZE)) || 183 (!validstr(yppwd.newpw.pw_name, UTUSERLEN)) || 184 (!validstr(yppwd.newpw.pw_gecos, FINGERSIZE)) || 185 (!validstr(yppwd.newpw.pw_shell, SHELLSIZE))) { 186 svcerr_decode(transp); 187 return; 188 } 189 190 /* 191 * Special case: root on the master server can change other 192 * users' passwords without first entering the old password. 193 * We need to ensure that this is indeed root on the master 194 * server. (bug 1253949) 195 */ 196 if (strcmp(transp->xp_netid, "ticlts") == 0) { 197 svc_local_cred_t cred; 198 if (!svc_get_local_cred(transp, &cred)) { 199 logmsg(MSG_NOTIMECHECK, LOG_ERR, 200 "Couldn't get local user credentials"); 201 } else if (cred.ruid == 0) 202 root_on_master = TRUE; 203 } 204 205 /* 206 * Get the domain name. This is tricky because a N2L server may be 207 * handling multiple domains. There is nothing in the request to 208 * indicate which one we are trying to change a passwd for. First 209 * we try to get a list of password related domains from the mapping 210 * file. 211 */ 212 if (0 != (dom_count = get_mapping_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 } 1344