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 }