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 2008 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #include <sys/types.h> 27 #include <nsswitch.h> 28 #include <stdlib.h> 29 #include <stdio.h> 30 #include <string.h> 31 #include <syslog.h> 32 #include <stdlib.h> 33 #include <unistd.h> 34 35 #include "ns_sldap.h" 36 #include <nss_dbdefs.h> 37 #include <nsswitch.h> 38 #include <pwd.h> 39 #include <shadow.h> 40 #include <rpcsvc/nis.h> 41 42 #include "passwdutil.h" 43 44 static struct passwd *nisplus_getpw_from_master(const char *, char *); 45 static struct spwd *nisplus_getsp_from_master(const char *, char *); 46 47 /* 48 * name_to_int(rep) 49 * 50 * Translate the repository to a bitmask. 51 * if we don't recognise the repository name, we return REP_ERANGE 52 */ 53 int 54 name_to_int(char *rep_name) 55 { 56 int result = REP_ERANGE; 57 58 if (strcmp(rep_name, "files") == 0) 59 result = REP_FILES; 60 else if (strcmp(rep_name, "nis") == 0) 61 result = REP_NIS; 62 else if (strcmp(rep_name, "nisplus") == 0) 63 result = REP_NISPLUS; 64 else if (strcmp(rep_name, "ldap") == 0) 65 result = REP_LDAP; 66 else if (strcmp(rep_name, "compat") == 0) { 67 struct __nsw_switchconfig *cfg; 68 enum __nsw_parse_err pserr; 69 70 cfg = __nsw_getconfig("passwd_compat", &pserr); 71 if (cfg == NULL) { 72 result = REP_FILES | REP_NIS; 73 } else { 74 if (strcmp(cfg->lookups->service_name, "nisplus") == 0) 75 result = REP_FILES | REP_NISPLUS; 76 else if (strcmp(cfg->lookups->service_name, "ldap") == 77 0) 78 result = REP_FILES | REP_LDAP; 79 else 80 result = REP_ERANGE; 81 __nsw_freeconfig(cfg); 82 } 83 } 84 85 return (result); 86 } 87 88 /* 89 * Figure out which repository we use in compat mode. 90 */ 91 int 92 get_compat_mode(void) 93 { 94 struct __nsw_switchconfig *cfg; 95 enum __nsw_parse_err pserr; 96 int result = REP_COMPAT_NIS; 97 98 if ((cfg = __nsw_getconfig("passwd_compat", &pserr)) != NULL) { 99 if (strcmp(cfg->lookups->service_name, "nisplus") == 0) 100 result = REP_COMPAT_NISPLUS; 101 else if (strcmp(cfg->lookups->service_name, "ldap") == 0) 102 result = REP_COMPAT_LDAP; 103 } 104 __nsw_freeconfig(cfg); 105 106 return (result); 107 } 108 109 /* 110 * get_ns(rep, accesstype) 111 * 112 * returns a bitmask of repositories to use based on either 113 * 1. the repository that is given as argument 114 * 2. the nsswitch.conf file 115 * 3. the type of access requested 116 * 117 * "accesstype" indicates whether we are reading from or writing to the 118 * repository. We need to know this since "compat" will translate into 119 * REP_NSS (the nss-switch) for READ access (needed to decode 120 * the black-magic '+' entries) but it translates into a bitmask 121 * on WRITE access. 122 * 123 * If we detect read-access in compat mode, we augment the result 124 * with one of REP_COMPAT_{NIS,NISPLUS,LDAP}. We need this in order to 125 * implement ATTR_REP_NAME in nss_getpwnam. 126 * 127 * A return value of REP_NOREP indicates an error. 128 */ 129 int 130 get_ns(pwu_repository_t *rep, int accesstype) 131 { 132 struct __nsw_switchconfig *conf = NULL; 133 enum __nsw_parse_err pserr; 134 struct __nsw_lookup *lkp; 135 struct __nsw_lookup *lkp2; 136 struct __nsw_lookup *lkp3; 137 struct __nsw_lookup *lkpn; 138 int result = REP_NOREP; 139 140 if (rep != PWU_DEFAULT_REP) { 141 result = name_to_int(rep->type); 142 return (result); 143 } 144 145 conf = __nsw_getconfig("passwd", &pserr); 146 if (conf == NULL) { 147 /* 148 * No config found. The user didn't supply a repository, 149 * so we try to change the password in the default 150 * repositories (files and nis) even though we cannot 151 * find the name service switch entry. (Backward compat) 152 */ 153 syslog(LOG_ERR, "passwdutil.so: nameservice switch entry for " 154 "passwd not found."); 155 result = REP_FILES | REP_NIS; 156 return (result); 157 } 158 159 lkp = conf->lookups; 160 161 /* 162 * Supported nsswitch.conf can have a maximum of 3 repositories. 163 * If we encounter an unsupported nsswitch.conf, we return REP_NSS 164 * to fall back to the nsswitch backend. 165 * 166 * Note that specifying 'ad' in the configuration is acceptable 167 * though changing AD users' passwords through passwd(1) is not. 168 * Therefore "ad" will be silently ignored. 169 */ 170 if (conf->num_lookups == 1) { 171 /* files or compat */ 172 173 if (strcmp(lkp->service_name, "files") == 0) { 174 result = name_to_int(lkp->service_name); 175 } else if (strcmp(lkp->service_name, "compat") == 0) { 176 if (accesstype == PWU_READ) 177 result = REP_NSS | get_compat_mode(); 178 else 179 result = name_to_int(lkp->service_name); 180 } else 181 result = REP_NSS; 182 183 } else if (conf->num_lookups == 2) { 184 lkp2 = lkp->next; 185 if (strcmp(lkp->service_name, "files") == 0) { 186 result = REP_FILES; 187 if (strcmp(lkp2->service_name, "ldap") == 0) 188 result |= REP_LDAP; 189 else if (strcmp(lkp2->service_name, "nis") == 0) 190 result |= REP_NIS; 191 else if (strcmp(lkp2->service_name, "nisplus") == 0) 192 result |= REP_NISPLUS; 193 else if (strcmp(lkp2->service_name, "ad") != 0) 194 result = REP_NSS; 195 /* AD is ignored */ 196 } else { 197 result = REP_NSS; 198 } 199 } else if (conf->num_lookups == 3) { 200 /* 201 * Valid configurations with 3 repositories are: 202 * files ad [nis | ldap | nisplus] OR 203 * files [nis | ldap | nisplus] ad 204 */ 205 lkp2 = lkp->next; 206 lkp3 = lkp2->next; 207 if (strcmp(lkp2->service_name, "ad") == 0) 208 lkpn = lkp3; 209 else if (strcmp(lkp3->service_name, "ad") == 0) 210 lkpn = lkp2; 211 else 212 lkpn = NULL; 213 if (strcmp(lkp->service_name, "files") == 0 && 214 lkpn != NULL) { 215 result = REP_FILES; 216 if (strcmp(lkpn->service_name, "ldap") == 0) 217 result |= REP_LDAP; 218 else if (strcmp(lkpn->service_name, "nis") == 0) 219 result |= REP_NIS; 220 else if (strcmp(lkpn->service_name, "nisplus") == 0) 221 result |= REP_NISPLUS; 222 else 223 result = REP_NSS; 224 } else { 225 result = REP_NSS; 226 } 227 } else { 228 result = REP_NSS; 229 } 230 231 __nsw_freeconfig(conf); 232 return (result); 233 } 234 235 static void 236 nss_ldap_passwd(p) 237 nss_db_params_t *p; 238 { 239 p->name = NSS_DBNAM_PASSWD; 240 p->flags |= NSS_USE_DEFAULT_CONFIG; 241 p->default_config = "ldap"; 242 } 243 244 static void 245 nss_ldap_shadow(p) 246 nss_db_params_t *p; 247 { 248 p->name = NSS_DBNAM_SHADOW; 249 p->config_name = NSS_DBNAM_PASSWD; /* Use config for "passwd" */ 250 p->flags |= NSS_USE_DEFAULT_CONFIG; 251 p->default_config = "ldap"; 252 } 253 254 255 #ifdef PAM_NIS 256 static void 257 nss_nis_passwd(p) 258 nss_db_params_t *p; 259 { 260 p->name = NSS_DBNAM_PASSWD; 261 p->flags |= NSS_USE_DEFAULT_CONFIG; 262 p->default_config = "nis"; 263 } 264 265 static void 266 nss_nis_shadow(p) 267 nss_db_params_t *p; 268 { 269 p->name = NSS_DBNAM_SHADOW; 270 p->config_name = NSS_DBNAM_PASSWD; /* Use config for "passwd" */ 271 p->flags |= NSS_USE_DEFAULT_CONFIG; 272 p->default_config = "nis"; 273 } 274 #endif /* PAM_NIS */ 275 276 277 static void 278 nss_nisplus_passwd(p) 279 nss_db_params_t *p; 280 { 281 p->name = NSS_DBNAM_PASSWD; 282 p->flags |= NSS_USE_DEFAULT_CONFIG; 283 p->default_config = "nisplus"; 284 } 285 286 static void 287 nss_nisplus_shadow(p) 288 nss_db_params_t *p; 289 { 290 p->name = NSS_DBNAM_SHADOW; 291 p->config_name = NSS_DBNAM_PASSWD; /* Use config for "passwd" */ 292 p->flags |= NSS_USE_DEFAULT_CONFIG; 293 p->default_config = "nisplus"; 294 } 295 296 297 static char * 298 gettok(nextpp) 299 char **nextpp; 300 { 301 char *p = *nextpp; 302 char *q = p; 303 char c; 304 305 if (p == 0) { 306 return (0); 307 } 308 while ((c = *q) != '\0' && c != ':') { 309 q++; 310 } 311 if (c == '\0') { 312 *nextpp = 0; 313 } else { 314 *q++ = '\0'; 315 *nextpp = q; 316 } 317 return (p); 318 } 319 320 /* 321 * Return values: 0 = success, 1 = parse error, 2 = erange ... 322 * The structure pointer passed in is a structure in the caller's space 323 * wherein the field pointers would be set to areas in the buffer if 324 * need be. instring and buffer should be separate areas. 325 */ 326 static int 327 str2passwd(const char *instr, int lenstr, void *ent, char *buffer, int buflen) 328 { 329 struct passwd *passwd = (struct passwd *)ent; 330 char *p, *next; 331 int black_magic; /* "+" or "-" entry */ 332 333 if (lenstr + 1 > buflen) { 334 return (NSS_STR_PARSE_ERANGE); 335 } 336 /* 337 * We copy the input string into the output buffer and 338 * operate on it in place. 339 */ 340 (void) memcpy(buffer, instr, lenstr); 341 buffer[lenstr] = '\0'; 342 343 next = buffer; 344 345 passwd->pw_name = p = gettok(&next); /* username */ 346 if (*p == '\0') { 347 /* Empty username; not allowed */ 348 return (NSS_STR_PARSE_PARSE); 349 } 350 black_magic = (*p == '+' || *p == '-'); 351 if (black_magic) { 352 passwd->pw_uid = UID_NOBODY; 353 passwd->pw_gid = GID_NOBODY; 354 /* 355 * pwconv tests pw_passwd and pw_age == NULL 356 */ 357 passwd->pw_passwd = ""; 358 passwd->pw_age = ""; 359 /* 360 * the rest of the passwd entry is "optional" 361 */ 362 passwd->pw_comment = ""; 363 passwd->pw_gecos = ""; 364 passwd->pw_dir = ""; 365 passwd->pw_shell = ""; 366 } 367 368 passwd->pw_passwd = p = gettok(&next); /* password */ 369 if (p == 0) { 370 if (black_magic) 371 return (NSS_STR_PARSE_SUCCESS); 372 else 373 return (NSS_STR_PARSE_PARSE); 374 } 375 for (; *p != '\0'; p++) { /* age */ 376 if (*p == ',') { 377 *p++ = '\0'; 378 break; 379 } 380 } 381 passwd->pw_age = p; 382 383 p = next; /* uid */ 384 if (p == 0 || *p == '\0') { 385 if (black_magic) 386 return (NSS_STR_PARSE_SUCCESS); 387 else 388 return (NSS_STR_PARSE_PARSE); 389 } 390 if (!black_magic) { 391 passwd->pw_uid = strtol(p, &next, 10); 392 if (next == p) { 393 /* uid field should be nonempty */ 394 return (NSS_STR_PARSE_PARSE); 395 } 396 /* 397 * The old code (in 2.0 thru 2.5) would check 398 * for the uid being negative, or being greater 399 * than 60001 (the rfs limit). If it met either of 400 * these conditions, the uid was translated to 60001. 401 * 402 * Now we just check for ephemeral uids; anything else 403 * is administrative policy 404 */ 405 if (passwd->pw_uid > MAXUID) 406 passwd->pw_uid = UID_NOBODY; 407 } 408 if (*next++ != ':') { 409 if (black_magic) 410 p = gettok(&next); 411 else 412 return (NSS_STR_PARSE_PARSE); 413 } 414 p = next; /* gid */ 415 if (p == 0 || *p == '\0') { 416 if (black_magic) 417 return (NSS_STR_PARSE_SUCCESS); 418 else 419 return (NSS_STR_PARSE_PARSE); 420 } 421 if (!black_magic) { 422 passwd->pw_gid = strtol(p, &next, 10); 423 if (next == p) { 424 /* gid field should be nonempty */ 425 return (NSS_STR_PARSE_PARSE); 426 } 427 /* 428 * gid should be non-negative; anything else 429 * is administrative policy. 430 */ 431 if (passwd->pw_gid > MAXUID) 432 passwd->pw_gid = GID_NOBODY; 433 } 434 if (*next++ != ':') { 435 if (black_magic) 436 p = gettok(&next); 437 else 438 return (NSS_STR_PARSE_PARSE); 439 } 440 441 passwd->pw_gecos = passwd->pw_comment = p = gettok(&next); 442 if (p == 0) { 443 if (black_magic) 444 return (NSS_STR_PARSE_SUCCESS); 445 else 446 return (NSS_STR_PARSE_PARSE); 447 } 448 449 passwd->pw_dir = p = gettok(&next); 450 if (p == 0) { 451 if (black_magic) 452 return (NSS_STR_PARSE_SUCCESS); 453 else 454 return (NSS_STR_PARSE_PARSE); 455 } 456 457 passwd->pw_shell = p = gettok(&next); 458 if (p == 0) { 459 if (black_magic) 460 return (NSS_STR_PARSE_SUCCESS); 461 else 462 return (NSS_STR_PARSE_PARSE); 463 } 464 465 /* Better not be any more fields... */ 466 if (next == 0) { 467 /* Successfully parsed and stored */ 468 return (NSS_STR_PARSE_SUCCESS); 469 } 470 return (NSS_STR_PARSE_PARSE); 471 } 472 473 typedef const char *constp; 474 475 /* 476 * Return value 1 means success and more input, 0 means error or no more 477 */ 478 static int 479 getfield(nextp, limit, uns, valp) 480 constp *nextp; 481 constp limit; 482 int uns; 483 void *valp; 484 { 485 constp p = *nextp; 486 char *endfield; 487 char numbuf[12]; /* Holds -2^31 and trailing ':' */ 488 int len; 489 long x; 490 unsigned long ux; 491 492 if (p == 0 || p >= limit) { 493 return (0); 494 } 495 if (*p == ':') { 496 p++; 497 *nextp = p; 498 return (p < limit); 499 } 500 if ((len = limit - p) > sizeof (numbuf) - 1) { 501 len = sizeof (numbuf) - 1; 502 } 503 /* 504 * We want to use strtol() and we have a readonly non-zero-terminated 505 * string, so first we copy and terminate the interesting bit. 506 * Ugh. (It's convenient to terminate with a colon rather than \0). 507 */ 508 if ((endfield = memccpy(numbuf, p, ':', len)) == 0) { 509 if (len != limit - p) { 510 /* Error -- field is too big to be a legit number */ 511 return (0); 512 } 513 numbuf[len] = ':'; 514 p = limit; 515 } else { 516 p += (endfield - numbuf); 517 } 518 if (uns) { 519 ux = strtoul(numbuf, &endfield, 10); 520 if (*endfield != ':') { 521 /* Error -- expected <integer><colon> */ 522 return (0); 523 } 524 *((unsigned int *)valp) = (unsigned int)ux; 525 } else { 526 x = strtol(numbuf, &endfield, 10); 527 if (*endfield != ':') { 528 /* Error -- expected <integer><colon> */ 529 return (0); 530 } 531 *((int *)valp) = (int)x; 532 } 533 *nextp = p; 534 return (p < limit); 535 } 536 537 /* 538 * str2spwd() -- convert a string to a shadow passwd entry. The parser is 539 * more liberal than the passwd or group parsers; since it's legitimate 540 * for almost all the fields here to be blank, the parser lets one omit 541 * any number of blank fields at the end of the entry. The acceptable 542 * forms for '+' and '-' entries are the same as those for normal entries. 543 * === Is this likely to do more harm than good? 544 * 545 * Return values: 0 = success, 1 = parse error, 2 = erange ... 546 * The structure pointer passed in is a structure in the caller's space 547 * wherein the field pointers would be set to areas in the buffer if 548 * need be. instring and buffer should be separate areas. 549 */ 550 int 551 str2spwd(instr, lenstr, ent, buffer, buflen) 552 const char *instr; 553 int lenstr; 554 void *ent; /* really (struct spwd *) */ 555 char *buffer; 556 int buflen; 557 { 558 struct spwd *shadow = (struct spwd *)ent; 559 const char *p = instr, *limit; 560 char *bufp; 561 int lencopy, black_magic; 562 563 limit = p + lenstr; 564 if ((p = memchr(instr, ':', lenstr)) == 0 || 565 ++p >= limit || 566 (p = memchr(p, ':', limit - p)) == 0) { 567 lencopy = lenstr; 568 p = 0; 569 } else { 570 lencopy = p - instr; 571 p++; 572 } 573 if (lencopy + 1 > buflen) { 574 return (NSS_STR_PARSE_ERANGE); 575 } 576 (void) memcpy(buffer, instr, lencopy); 577 buffer[lencopy] = 0; 578 579 black_magic = (*instr == '+' || *instr == '-'); 580 shadow->sp_namp = bufp = buffer; 581 shadow->sp_pwdp = 0; 582 shadow->sp_lstchg = -1; 583 shadow->sp_min = -1; 584 shadow->sp_max = -1; 585 shadow->sp_warn = -1; 586 shadow->sp_inact = -1; 587 shadow->sp_expire = -1; 588 shadow->sp_flag = 0; 589 590 if ((bufp = strchr(bufp, ':')) == 0) { 591 if (black_magic) 592 return (NSS_STR_PARSE_SUCCESS); 593 else 594 return (NSS_STR_PARSE_PARSE); 595 } 596 *bufp++ = '\0'; 597 598 shadow->sp_pwdp = bufp; 599 if (instr == 0) { 600 if ((bufp = strchr(bufp, ':')) == 0) { 601 if (black_magic) 602 return (NSS_STR_PARSE_SUCCESS); 603 else 604 return (NSS_STR_PARSE_PARSE); 605 } 606 *bufp++ = '\0'; 607 p = bufp; 608 } /* else p was set when we copied name and passwd into the buffer */ 609 610 if (!getfield(&p, limit, 0, &shadow->sp_lstchg)) 611 return (NSS_STR_PARSE_SUCCESS); 612 if (!getfield(&p, limit, 0, &shadow->sp_min)) 613 return (NSS_STR_PARSE_SUCCESS); 614 if (!getfield(&p, limit, 0, &shadow->sp_max)) 615 return (NSS_STR_PARSE_SUCCESS); 616 if (!getfield(&p, limit, 0, &shadow->sp_warn)) 617 return (NSS_STR_PARSE_SUCCESS); 618 if (!getfield(&p, limit, 0, &shadow->sp_inact)) 619 return (NSS_STR_PARSE_SUCCESS); 620 if (!getfield(&p, limit, 0, &shadow->sp_expire)) 621 return (NSS_STR_PARSE_SUCCESS); 622 if (!getfield(&p, limit, 1, &shadow->sp_flag)) 623 return (NSS_STR_PARSE_SUCCESS); 624 if (p != limit) { 625 /* Syntax error -- garbage at end of line */ 626 return (NSS_STR_PARSE_PARSE); 627 } 628 return (NSS_STR_PARSE_SUCCESS); 629 } 630 631 static nss_XbyY_buf_t *buffer; 632 static DEFINE_NSS_DB_ROOT(db_root); 633 634 #define GETBUF() \ 635 NSS_XbyY_ALLOC(&buffer, sizeof (struct passwd), NSS_BUFLEN_PASSWD) 636 637 #pragma fini(endutilpwent) 638 639 static void 640 endutilpwent(void) 641 { 642 NSS_XbyY_FREE(&buffer); 643 nss_delete(&db_root); 644 } 645 646 struct passwd * 647 getpwnam_from(const char *name, pwu_repository_t *rep, int reptype) 648 { 649 nss_XbyY_buf_t *b = GETBUF(); 650 nss_XbyY_args_t arg; 651 652 if (b == 0) 653 return (0); 654 655 NSS_XbyY_INIT(&arg, b->result, b->buffer, b->buflen, str2passwd); 656 arg.key.name = name; 657 658 switch (reptype) { 659 case REP_LDAP: 660 (void) nss_search(&db_root, nss_ldap_passwd, 661 NSS_DBOP_PASSWD_BYNAME, &arg); 662 break; 663 case REP_NISPLUS: 664 if (rep && rep->scope) 665 return (nisplus_getpw_from_master(name, rep->scope)); 666 667 (void) nss_search(&db_root, nss_nisplus_passwd, 668 NSS_DBOP_PASSWD_BYNAME, &arg); 669 break; 670 #ifdef PAM_NIS 671 case REP_NIS: 672 (void) nss_search(&db_root, nss_nis_passwd, 673 NSS_DBOP_PASSWD_BYNAME, &arg); 674 break; 675 #endif 676 default: 677 return (NULL); 678 } 679 680 return (struct passwd *)NSS_XbyY_FINI(&arg); 681 } 682 683 /*ARGSUSED*/ 684 struct passwd * 685 getpwuid_from(uid_t uid, pwu_repository_t *rep, int reptype) 686 { 687 nss_XbyY_buf_t *b = GETBUF(); 688 nss_XbyY_args_t arg; 689 690 if (b == 0) 691 return (0); 692 693 NSS_XbyY_INIT(&arg, b->result, b->buffer, b->buflen, str2passwd); 694 arg.key.uid = uid; 695 696 switch (reptype) { 697 case REP_LDAP: 698 (void) nss_search(&db_root, nss_ldap_passwd, 699 NSS_DBOP_PASSWD_BYUID, &arg); 700 break; 701 case REP_NISPLUS: 702 (void) nss_search(&db_root, nss_nisplus_passwd, 703 NSS_DBOP_PASSWD_BYUID, &arg); 704 break; 705 #ifdef PAM_NIS 706 case REP_NIS: 707 (void) nss_search(&db_root, nss_nis_passwd, 708 NSS_DBOP_PASSWD_BYUID, &arg); 709 break; 710 #endif 711 default: 712 return (NULL); 713 } 714 715 return (struct passwd *)NSS_XbyY_FINI(&arg); 716 } 717 718 static nss_XbyY_buf_t *spbuf; 719 static DEFINE_NSS_DB_ROOT(spdb_root); 720 721 #define GETSPBUF() \ 722 NSS_XbyY_ALLOC(&spbuf, sizeof (struct spwd), NSS_BUFLEN_SHADOW) 723 724 #pragma fini(endutilspent) 725 726 static void 727 endutilspent(void) 728 { 729 NSS_XbyY_FREE(&spbuf); 730 nss_delete(&spdb_root); 731 } 732 733 struct spwd * 734 getspnam_from(const char *name, pwu_repository_t *rep, int reptype) 735 { 736 nss_XbyY_buf_t *b = GETSPBUF(); 737 nss_XbyY_args_t arg; 738 739 if (b == 0) 740 return (0); 741 742 NSS_XbyY_INIT(&arg, b->result, b->buffer, b->buflen, str2spwd); 743 arg.key.name = name; 744 switch (reptype) { 745 case REP_LDAP: 746 (void) nss_search(&spdb_root, nss_ldap_shadow, 747 NSS_DBOP_SHADOW_BYNAME, &arg); 748 break; 749 case REP_NISPLUS: 750 if (rep && rep->scope) 751 return (nisplus_getsp_from_master(name, rep->scope)); 752 753 (void) nss_search(&spdb_root, nss_nisplus_shadow, 754 NSS_DBOP_SHADOW_BYNAME, &arg); 755 break; 756 #ifdef PAM_NIS 757 case REP_NIS: 758 (void) nss_search(&spdb_root, nss_nis_shadow, 759 NSS_DBOP_SHADOW_BYNAME, &arg); 760 break; 761 #endif 762 default: 763 return (NULL); 764 } 765 return (struct spwd *)NSS_XbyY_FINI(&arg); 766 } 767 768 769 static nis_result * 770 nisplus_match(const char *name, char *domain, char *buf, int len) 771 { 772 int n; 773 int flags; 774 nis_result *res; 775 nis_object *object; 776 777 n = snprintf(buf, len, "[name=%s],passwd.org_dir.%s", name, domain); 778 if (n >= len) { 779 syslog(LOG_ERR, "nisplus_match: name too long"); 780 return (NULL); 781 } 782 if (buf[n-1] != '.') { 783 if (n == len-1) { 784 syslog(LOG_ERR, "nisplus_match: name too long"); 785 return (NULL); 786 } 787 buf[n++] = '.'; 788 buf[n] = '\0'; 789 } 790 791 flags = USE_DGRAM | FOLLOW_LINKS | FOLLOW_PATH | MASTER_ONLY; 792 793 res = nis_list(buf, flags, NULL, NULL); 794 795 if (res == NULL) { 796 syslog(LOG_ERR, "nisplus_match: nis_list returned NULL"); 797 return (NULL); 798 } 799 800 if (NIS_RES_STATUS(res) != NIS_SUCCESS || NIS_RES_NUMOBJ(res) != 1) { 801 syslog(LOG_ERR, "nisplus_match: match failed: %s", 802 nis_sperrno(NIS_RES_STATUS(res))); 803 nis_freeresult(res); 804 return (NULL); 805 } 806 807 object = NIS_RES_OBJECT(res); 808 809 if (object->EN_data.en_cols.en_cols_len < 8) { 810 syslog(LOG_ERR, "nisplus_match: " 811 "not a valid passwd table entry for user %s", name); 812 nis_freeresult(res); 813 return (NULL); 814 } 815 816 return (res); 817 } 818 819 #define SAFE_STRDUP(dst, idx) \ 820 if ((idx) <= 3 && ENTRY_VAL(nret, (idx)) == NULL) { \ 821 syslog(LOG_ERR, \ 822 "passwdutil: missing field from password entry"); \ 823 goto error; \ 824 } \ 825 len = ENTRY_LEN(nret, (idx)); \ 826 (dst) = malloc(len+1); \ 827 if ((dst) == NULL) { \ 828 syslog(LOG_ERR, "passwdutil: out of memory"); \ 829 goto error; \ 830 } \ 831 (dst)[len] = '\0'; \ 832 (void) strncpy((dst), \ 833 ENTRY_VAL(nret, (idx)) ? ENTRY_VAL(nret, (idx)) : "", \ 834 len); 835 836 837 838 static struct passwd * 839 nisplus_getpw_from_master(const char *name, char *domain) 840 { 841 char lookup[NIS_MAXNAMELEN+1]; 842 nis_result *res; 843 nis_object *nret; 844 int len; 845 char *p; 846 struct passwd *pw; 847 848 if ((pw = calloc(1, sizeof (*pw))) == NULL) 849 return (NULL); 850 851 res = nisplus_match(name, domain, lookup, sizeof (lookup)); 852 853 if (res == NULL) 854 return (NULL); 855 856 nret = NIS_RES_OBJECT(res); 857 858 SAFE_STRDUP(pw->pw_name, 0); 859 860 if ((pw->pw_passwd = strdup("x")) == NULL) { 861 syslog(LOG_ERR, "passwdutil: out of memory"); 862 goto error; 863 } 864 865 SAFE_STRDUP(p, 2); 866 pw->pw_uid = atoi(p); 867 free(p); 868 869 SAFE_STRDUP(p, 3); 870 pw->pw_gid = atoi(p); 871 free(p); 872 873 pw->pw_age = NULL; 874 pw->pw_comment = NULL; 875 876 /*CONSTANTCONDITION*/ 877 SAFE_STRDUP(pw->pw_gecos, 4); 878 879 /*CONSTANTCONDITION*/ 880 SAFE_STRDUP(pw->pw_dir, 5); 881 882 /*CONSTANTCONDITION*/ 883 SAFE_STRDUP(pw->pw_shell, 6); 884 885 nis_freeresult(res); 886 887 return (pw); 888 889 error: 890 nis_freeresult(res); 891 if (pw->pw_name) 892 free(pw->pw_name); 893 if (pw->pw_passwd) 894 free(pw->pw_passwd); 895 if (pw->pw_gecos) 896 free(pw->pw_gecos); 897 if (pw->pw_dir) 898 free(pw->pw_dir); 899 if (pw->pw_shell) 900 free(pw->pw_shell); 901 free(pw); 902 903 return (NULL); 904 } 905 906 /* 907 * struct spwd * nisplus_getsp_from_master() 908 * 909 * Get the shadow structure from a NIS+ master. 910 * This routine normally runs with EUID==0. This can cause trouble 911 * if the NIS+ tables are locked down so that only the owner can 912 * access the encrypted password. If we detect that scenario, we switch 913 * EUID to the owner of the record and refetch it. 914 */ 915 static struct spwd * 916 nisplus_getsp_from_master(const char *name, char *domain) 917 { 918 char lookup[NIS_MAXNAMELEN+1]; 919 nis_result *res = NULL; 920 nis_object *nret = NULL; 921 int len; 922 struct spwd *spw; 923 char *shadow = NULL; 924 const char *p = NULL; 925 const char *limit; 926 927 res = nisplus_match(name, domain, lookup, sizeof (lookup)); 928 if (res == NULL) 929 return (NULL); 930 931 nret = NIS_RES_OBJECT(res); 932 933 /*CONSTANTCONDITION*/ 934 SAFE_STRDUP(shadow, 7); 935 936 /* 937 * If we got NOPWDRTR as password, try again with EUID set 938 * to the UID of the record-owner. 939 */ 940 if (strncmp(shadow, NOPWDRTR, 4) == 0) { 941 char *p; 942 uid_t owner_uid; 943 uid_t euid = geteuid(); 944 945 SAFE_STRDUP(p, 2); /* record-owner field */ 946 owner_uid = atoi(p); 947 free(p); 948 949 if (owner_uid != euid) { 950 /* re-obtain entry using owners EUID */ 951 free(shadow); 952 nis_freeresult(res); 953 954 (void) seteuid(owner_uid); 955 res = nisplus_match(name, domain, lookup, 956 sizeof (lookup)); 957 (void) seteuid(euid); 958 959 if (res == NULL) 960 return (NULL); 961 nret = NIS_RES_OBJECT(res); 962 963 /*CONSTANTCONDITION*/ 964 SAFE_STRDUP(shadow, 7); 965 } 966 } 967 968 if ((spw = calloc(1, sizeof (*spw))) == NULL) { 969 nis_freeresult(res); 970 return (NULL); 971 } 972 973 SAFE_STRDUP(spw->sp_namp, 0); 974 SAFE_STRDUP(spw->sp_pwdp, 1); 975 976 nis_freeresult(res); 977 978 limit = shadow + strlen(shadow) + 1; 979 p = shadow; 980 981 spw->sp_lstchg = -1; 982 spw->sp_min = -1; 983 spw->sp_max = -1; 984 spw->sp_warn = -1; 985 spw->sp_inact = -1; 986 spw->sp_expire = -1; 987 spw->sp_flag = 0; 988 989 if (!getfield(&p, limit, 0, &spw->sp_lstchg)) 990 goto out; 991 992 if (!getfield(&p, limit, 0, &spw->sp_min)) 993 goto out; 994 995 if (!getfield(&p, limit, 0, &spw->sp_max)) 996 goto out; 997 998 if (!getfield(&p, limit, 0, &spw->sp_warn)) 999 goto out; 1000 1001 if (!getfield(&p, limit, 0, &spw->sp_inact)) 1002 goto out; 1003 1004 if (!getfield(&p, limit, 0, &spw->sp_expire)) 1005 goto out; 1006 1007 if (!getfield(&p, limit, 1, &spw->sp_flag)) 1008 goto out; 1009 1010 if (p != limit) { 1011 syslog(LOG_ERR, "passwdutil: garbage at end of record"); 1012 goto error; 1013 } 1014 1015 out: 1016 free(shadow); 1017 return (spw); 1018 1019 error: 1020 if (spw->sp_namp) 1021 free(spw->sp_namp); 1022 if (spw->sp_pwdp) 1023 free(spw->sp_pwdp); 1024 free(spw); 1025 if (shadow) 1026 free(shadow); 1027 return (NULL); 1028 } 1029