1 /* 2 * Copyright (c) 2001-2002 Sendmail, Inc. and its suppliers. 3 * All rights reserved. 4 * 5 * By using this file, you agree to the terms and conditions set 6 * forth in the LICENSE file which can be found at the top level of 7 * the sendmail distribution. 8 */ 9 10 #include <sm/gen.h> 11 SM_RCSID("@(#)$Id: mbdb.c,v 1.28 2002/01/07 23:29:43 gshapiro Exp $") 12 13 #include <sys/param.h> 14 15 #include <ctype.h> 16 #include <errno.h> 17 #include <pwd.h> 18 #include <stdlib.h> 19 #include <setjmp.h> 20 21 #include <sm/limits.h> 22 #include <sm/conf.h> 23 #include <sm/assert.h> 24 #include <sm/bitops.h> 25 #include <sm/errstring.h> 26 #include <sm/heap.h> 27 #include <sm/mbdb.h> 28 #include <sm/string.h> 29 #include <sm/sysexits.h> 30 31 #if LDAPMAP 32 # if _LDAP_EXAMPLE_ 33 # include <sm/ldap.h> 34 # endif /* _LDAP_EXAMPLE_ */ 35 #endif /* LDAPMAP */ 36 37 typedef struct 38 { 39 char *mbdb_typename; 40 int (*mbdb_initialize) __P((char *)); 41 int (*mbdb_lookup) __P((char *name, SM_MBDB_T *user)); 42 void (*mbdb_terminate) __P((void)); 43 } SM_MBDB_TYPE_T; 44 45 static int mbdb_pw_initialize __P((char *)); 46 static int mbdb_pw_lookup __P((char *name, SM_MBDB_T *user)); 47 static void mbdb_pw_terminate __P((void)); 48 49 #if LDAPMAP 50 # if _LDAP_EXAMPLE_ 51 static struct sm_ldap_struct LDAPLMAP; 52 static int mbdb_ldap_initialize __P((char *)); 53 static int mbdb_ldap_lookup __P((char *name, SM_MBDB_T *user)); 54 static void mbdb_ldap_terminate __P((void)); 55 # endif /* _LDAP_EXAMPLE_ */ 56 #endif /* LDAPMAP */ 57 58 static SM_MBDB_TYPE_T SmMbdbTypes[] = 59 { 60 { "pw", mbdb_pw_initialize, mbdb_pw_lookup, mbdb_pw_terminate }, 61 #if LDAPMAP 62 # if _LDAP_EXAMPLE_ 63 { "ldap", mbdb_ldap_initialize, mbdb_ldap_lookup, mbdb_ldap_terminate }, 64 # endif /* _LDAP_EXAMPLE_ */ 65 #endif /* LDAPMAP */ 66 { NULL, NULL, NULL, NULL } 67 }; 68 69 static SM_MBDB_TYPE_T *SmMbdbType = &SmMbdbTypes[0]; 70 71 /* 72 ** SM_MBDB_INITIALIZE -- specify which mailbox database to use 73 ** 74 ** If this function is not called, then the "pw" implementation 75 ** is used by default; this implementation uses getpwnam(). 76 ** 77 ** Parameters: 78 ** mbdb -- Which mailbox database to use. 79 ** The argument has the form "name" or "name.arg". 80 ** "pw" means use getpwnam(). 81 ** 82 ** Results: 83 ** EX_OK on success, or an EX_* code on failure. 84 */ 85 86 int 87 sm_mbdb_initialize(mbdb) 88 char *mbdb; 89 { 90 size_t namelen; 91 int err; 92 char *name; 93 char *arg; 94 SM_MBDB_TYPE_T *t; 95 96 SM_REQUIRE(mbdb != NULL); 97 98 name = mbdb; 99 arg = strchr(mbdb, '.'); 100 if (arg == NULL) 101 namelen = strlen(name); 102 else 103 { 104 namelen = arg - name; 105 ++arg; 106 } 107 108 for (t = SmMbdbTypes; t->mbdb_typename != NULL; ++t) 109 { 110 if (strlen(t->mbdb_typename) == namelen && 111 strncmp(name, t->mbdb_typename, namelen) == 0) 112 { 113 err = t->mbdb_initialize(arg); 114 if (err == EX_OK) 115 SmMbdbType = t; 116 return err; 117 } 118 } 119 return EX_UNAVAILABLE; 120 } 121 122 /* 123 ** SM_MBDB_TERMINATE -- terminate connection to the mailbox database 124 ** 125 ** Because this function closes any cached file descriptors that 126 ** are being held open for the connection to the mailbox database, 127 ** it should be called for security reasons prior to dropping privileges 128 ** and execing another process. 129 ** 130 ** Parameters: 131 ** none. 132 ** 133 ** Results: 134 ** none. 135 */ 136 137 void 138 sm_mbdb_terminate() 139 { 140 SmMbdbType->mbdb_terminate(); 141 } 142 143 /* 144 ** SM_MBDB_LOOKUP -- look up a local mail recipient, given name 145 ** 146 ** Parameters: 147 ** name -- name of local mail recipient 148 ** user -- pointer to structure to fill in on success 149 ** 150 ** Results: 151 ** On success, fill in *user and return EX_OK. 152 ** If the user does not exist, return EX_NOUSER. 153 ** If a temporary failure (eg, a network failure) occurred, 154 ** return EX_TEMPFAIL. Otherwise return EX_OSERR. 155 */ 156 157 int 158 sm_mbdb_lookup(name, user) 159 char *name; 160 SM_MBDB_T *user; 161 { 162 return SmMbdbType->mbdb_lookup(name, user); 163 } 164 165 /* 166 ** SM_MBDB_FROMPW -- copy from struct pw to SM_MBDB_T 167 ** 168 ** Parameters: 169 ** user -- destination user information structure 170 ** pw -- source passwd structure 171 ** 172 ** Results: 173 ** none. 174 */ 175 176 void 177 sm_mbdb_frompw(user, pw) 178 SM_MBDB_T *user; 179 struct passwd *pw; 180 { 181 SM_REQUIRE(user != NULL); 182 (void) sm_strlcpy(user->mbdb_name, pw->pw_name, 183 sizeof(user->mbdb_name)); 184 user->mbdb_uid = pw->pw_uid; 185 user->mbdb_gid = pw->pw_gid; 186 sm_pwfullname(pw->pw_gecos, pw->pw_name, user->mbdb_fullname, 187 sizeof(user->mbdb_fullname)); 188 (void) sm_strlcpy(user->mbdb_homedir, pw->pw_dir, 189 sizeof(user->mbdb_homedir)); 190 (void) sm_strlcpy(user->mbdb_shell, pw->pw_shell, 191 sizeof(user->mbdb_shell)); 192 } 193 194 /* 195 ** SM_PWFULLNAME -- build full name of user from pw_gecos field. 196 ** 197 ** This routine interprets the strange entry that would appear 198 ** in the GECOS field of the password file. 199 ** 200 ** Parameters: 201 ** gecos -- name to build. 202 ** user -- the login name of this user (for &). 203 ** buf -- place to put the result. 204 ** buflen -- length of buf. 205 ** 206 ** Returns: 207 ** none. 208 */ 209 210 void 211 sm_pwfullname(gecos, user, buf, buflen) 212 register char *gecos; 213 char *user; 214 char *buf; 215 size_t buflen; 216 { 217 register char *p; 218 register char *bp = buf; 219 220 if (*gecos == '*') 221 gecos++; 222 223 /* copy gecos, interpolating & to be full name */ 224 for (p = gecos; *p != '\0' && *p != ',' && *p != ';' && *p != '%'; p++) 225 { 226 if (bp >= &buf[buflen - 1]) 227 { 228 /* buffer overflow -- just use login name */ 229 (void) sm_strlcpy(buf, user, buflen); 230 return; 231 } 232 if (*p == '&') 233 { 234 /* interpolate full name */ 235 (void) sm_strlcpy(bp, user, buflen - (bp - buf)); 236 *bp = toupper(*bp); 237 bp += strlen(bp); 238 } 239 else 240 *bp++ = *p; 241 } 242 *bp = '\0'; 243 } 244 245 /* 246 ** /etc/passwd implementation. 247 */ 248 249 /* 250 ** MBDB_PW_INITIALIZE -- initialize getpwnam() version 251 ** 252 ** Parameters: 253 ** arg -- unused. 254 ** 255 ** Results: 256 ** EX_OK. 257 */ 258 259 /* ARGSUSED0 */ 260 static int 261 mbdb_pw_initialize(arg) 262 char *arg; 263 { 264 return EX_OK; 265 } 266 267 /* 268 ** MBDB_PW_LOOKUP -- look up a local mail recipient, given name 269 ** 270 ** Parameters: 271 ** name -- name of local mail recipient 272 ** user -- pointer to structure to fill in on success 273 ** 274 ** Results: 275 ** On success, fill in *user and return EX_OK. 276 ** Failure: EX_NOUSER. 277 */ 278 279 static int 280 mbdb_pw_lookup(name, user) 281 char *name; 282 SM_MBDB_T *user; 283 { 284 struct passwd *pw; 285 286 #ifdef HESIOD 287 /* DEC Hesiod getpwnam accepts numeric strings -- short circuit it */ 288 { 289 char *p; 290 291 for (p = name; *p != '\0'; p++) 292 if (!isascii(*p) || !isdigit(*p)) 293 break; 294 if (*p == '\0') 295 return EX_NOUSER; 296 } 297 #endif /* HESIOD */ 298 299 errno = 0; 300 pw = getpwnam(name); 301 if (pw == NULL) 302 { 303 #if 0 304 /* 305 ** getpwnam() isn't advertised as setting errno. 306 ** In fact, under FreeBSD, non-root getpwnam() on 307 ** non-existant users returns NULL with errno = EPERM. 308 ** This test won't work. 309 */ 310 switch (errno) 311 { 312 case 0: 313 return EX_NOUSER; 314 case EIO: 315 return EX_OSERR; 316 default: 317 return EX_TEMPFAIL; 318 } 319 #endif /* 0 */ 320 return EX_NOUSER; 321 } 322 323 sm_mbdb_frompw(user, pw); 324 return EX_OK; 325 } 326 327 /* 328 ** MBDB_PW_TERMINATE -- terminate connection to the mailbox database 329 ** 330 ** Parameters: 331 ** none. 332 ** 333 ** Results: 334 ** none. 335 */ 336 337 static void 338 mbdb_pw_terminate() 339 { 340 endpwent(); 341 } 342 343 #if LDAPMAP 344 # if _LDAP_EXAMPLE_ 345 /* 346 ** LDAP example implementation based on RFC 2307, "An Approach for Using 347 ** LDAP as a Network Information Service": 348 ** 349 ** ( nisSchema.1.0 NAME 'uidNumber' 350 ** DESC 'An integer uniquely identifying a user in an 351 ** administrative domain' 352 ** EQUALITY integerMatch SYNTAX 'INTEGER' SINGLE-VALUE ) 353 ** 354 ** ( nisSchema.1.1 NAME 'gidNumber' 355 ** DESC 'An integer uniquely identifying a group in an 356 ** administrative domain' 357 ** EQUALITY integerMatch SYNTAX 'INTEGER' SINGLE-VALUE ) 358 ** 359 ** ( nisSchema.1.2 NAME 'gecos' 360 ** DESC 'The GECOS field; the common name' 361 ** EQUALITY caseIgnoreIA5Match 362 ** SUBSTRINGS caseIgnoreIA5SubstringsMatch 363 ** SYNTAX 'IA5String' SINGLE-VALUE ) 364 ** 365 ** ( nisSchema.1.3 NAME 'homeDirectory' 366 ** DESC 'The absolute path to the home directory' 367 ** EQUALITY caseExactIA5Match 368 ** SYNTAX 'IA5String' SINGLE-VALUE ) 369 ** 370 ** ( nisSchema.1.4 NAME 'loginShell' 371 ** DESC 'The path to the login shell' 372 ** EQUALITY caseExactIA5Match 373 ** SYNTAX 'IA5String' SINGLE-VALUE ) 374 ** 375 ** ( nisSchema.2.0 NAME 'posixAccount' SUP top AUXILIARY 376 ** DESC 'Abstraction of an account with POSIX attributes' 377 ** MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory ) 378 ** MAY ( userPassword $ loginShell $ gecos $ description ) ) 379 ** 380 */ 381 382 # define MBDB_LDAP_LABEL "MailboxDatabase" 383 384 # ifndef MBDB_LDAP_FILTER 385 # define MBDB_LDAP_FILTER "(&(objectClass=posixAccount)(uid=%0))" 386 # endif /* MBDB_LDAP_FILTER */ 387 388 # ifndef MBDB_DEFAULT_LDAP_BASEDN 389 # define MBDB_DEFAULT_LDAP_BASEDN NULL 390 # endif /* MBDB_DEFAULT_LDAP_BASEDN */ 391 392 # ifndef MBDB_DEFAULT_LDAP_SERVER 393 # define MBDB_DEFAULT_LDAP_SERVER NULL 394 # endif /* MBDB_DEFAULT_LDAP_SERVER */ 395 396 /* 397 ** MBDB_LDAP_INITIALIZE -- initialize LDAP version 398 ** 399 ** Parameters: 400 ** arg -- LDAP specification 401 ** 402 ** Results: 403 ** EX_OK on success, or an EX_* code on failure. 404 */ 405 406 static int 407 mbdb_ldap_initialize(arg) 408 char *arg; 409 { 410 sm_ldap_clear(&LDAPLMAP); 411 LDAPLMAP.ldap_base = MBDB_DEFAULT_LDAP_BASEDN; 412 LDAPLMAP.ldap_host = MBDB_DEFAULT_LDAP_SERVER; 413 LDAPLMAP.ldap_filter = MBDB_LDAP_FILTER; 414 415 /* Only want one match */ 416 LDAPLMAP.ldap_sizelimit = 1; 417 418 /* interpolate new ldap_base and ldap_host from arg if given */ 419 if (arg != NULL && *arg != '\0') 420 { 421 char *new; 422 char *sep; 423 size_t len; 424 425 len = strlen(arg) + 1; 426 new = sm_malloc(len); 427 if (new == NULL) 428 return EX_TEMPFAIL; 429 (void) sm_strlcpy(new, arg, len); 430 sep = strrchr(new, '@'); 431 if (sep != NULL) 432 { 433 *sep++ = '\0'; 434 LDAPLMAP.ldap_host = sep; 435 } 436 LDAPLMAP.ldap_base = new; 437 } 438 439 /* No connection yet, connect */ 440 if (!sm_ldap_start(MBDB_LDAP_LABEL, &LDAPLMAP)) 441 return EX_UNAVAILABLE; 442 return EX_OK; 443 } 444 445 446 /* 447 ** MBDB_LDAP_LOOKUP -- look up a local mail recipient, given name 448 ** 449 ** Parameters: 450 ** name -- name of local mail recipient 451 ** user -- pointer to structure to fill in on success 452 ** 453 ** Results: 454 ** On success, fill in *user and return EX_OK. 455 ** Failure: EX_NOUSER. 456 */ 457 458 #define NEED_FULLNAME 0x01 459 #define NEED_HOMEDIR 0x02 460 #define NEED_SHELL 0x04 461 #define NEED_UID 0x08 462 #define NEED_GID 0x10 463 464 static int 465 mbdb_ldap_lookup(name, user) 466 char *name; 467 SM_MBDB_T *user; 468 { 469 int msgid; 470 int need; 471 int ret; 472 int save_errno; 473 LDAPMessage *entry; 474 BerElement *ber; 475 char *attr = NULL; 476 477 if (strlen(name) >= sizeof(user->mbdb_name)) 478 { 479 errno = EINVAL; 480 return EX_NOUSER; 481 } 482 483 if (LDAPLMAP.ldap_filter == NULL) 484 { 485 /* map not initialized, but don't have arg here */ 486 errno = EFAULT; 487 return EX_TEMPFAIL; 488 } 489 490 if (LDAPLMAP.ldap_ld == NULL) 491 { 492 /* map not open, try to open now */ 493 if (!sm_ldap_start(MBDB_LDAP_LABEL, &LDAPLMAP)) 494 return EX_TEMPFAIL; 495 } 496 497 sm_ldap_setopts(LDAPLMAP.ldap_ld, &LDAPLMAP); 498 msgid = sm_ldap_search(&LDAPLMAP, name); 499 if (msgid == -1) 500 { 501 save_errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld) + E_LDAPBASE; 502 # ifdef LDAP_SERVER_DOWN 503 if (errno == LDAP_SERVER_DOWN) 504 { 505 /* server disappeared, try reopen on next search */ 506 sm_ldap_close(&LDAPLMAP); 507 } 508 # endif /* LDAP_SERVER_DOWN */ 509 errno = save_errno; 510 return EX_TEMPFAIL; 511 } 512 513 /* Get results */ 514 ret = ldap_result(LDAPLMAP.ldap_ld, msgid, 1, 515 (LDAPLMAP.ldap_timeout.tv_sec == 0 ? NULL : 516 &(LDAPLMAP.ldap_timeout)), 517 &(LDAPLMAP.ldap_res)); 518 519 if (ret != LDAP_RES_SEARCH_RESULT && 520 ret != LDAP_RES_SEARCH_ENTRY) 521 { 522 if (ret == 0) 523 errno = ETIMEDOUT; 524 else 525 errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld); 526 ret = EX_TEMPFAIL; 527 goto abort; 528 } 529 530 entry = ldap_first_entry(LDAPLMAP.ldap_ld, LDAPLMAP.ldap_res); 531 if (entry == NULL) 532 { 533 save_errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld); 534 if (save_errno == LDAP_SUCCESS) 535 { 536 errno = ENOENT; 537 ret = EX_NOUSER; 538 } 539 else 540 { 541 errno = save_errno; 542 ret = EX_TEMPFAIL; 543 } 544 goto abort; 545 } 546 547 # if !defined(LDAP_VERSION_MAX) && !defined(LDAP_OPT_SIZELIMIT) 548 /* 549 ** Reset value to prevent lingering 550 ** LDAP_DECODING_ERROR due to 551 ** OpenLDAP 1.X's hack (see below) 552 */ 553 554 LDAPLMAP.ldap_ld->ld_errno = LDAP_SUCCESS; 555 # endif /* !defined(LDAP_VERSION_MAX) !defined(LDAP_OPT_SIZELIMIT) */ 556 557 ret = EX_OK; 558 need = NEED_FULLNAME|NEED_HOMEDIR|NEED_SHELL|NEED_UID|NEED_GID; 559 for (attr = ldap_first_attribute(LDAPLMAP.ldap_ld, entry, &ber); 560 attr != NULL; 561 attr = ldap_next_attribute(LDAPLMAP.ldap_ld, entry, ber)) 562 { 563 char **vals; 564 565 vals = ldap_get_values(LDAPLMAP.ldap_ld, entry, attr); 566 if (vals == NULL) 567 { 568 errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld); 569 if (errno == LDAP_SUCCESS) 570 { 571 # if USING_NETSCAPE_LDAP 572 ldap_memfree(attr); 573 # endif /* USING_NETSCAPE_LDAP */ 574 continue; 575 } 576 577 /* Must be an error */ 578 errno += E_LDAPBASE; 579 ret = EX_TEMPFAIL; 580 goto abort; 581 } 582 583 # if !defined(LDAP_VERSION_MAX) && !defined(LDAP_OPT_SIZELIMIT) 584 /* 585 ** Reset value to prevent lingering 586 ** LDAP_DECODING_ERROR due to 587 ** OpenLDAP 1.X's hack (see below) 588 */ 589 590 LDAPLMAP.ldap_ld->ld_errno = LDAP_SUCCESS; 591 # endif /* !defined(LDAP_VERSION_MAX) !defined(LDAP_OPT_SIZELIMIT) */ 592 593 if (vals[0] == NULL || vals[0][0] == '\0') 594 goto skip; 595 596 if (strcasecmp(attr, "gecos") == 0) 597 { 598 if (!bitset(NEED_FULLNAME, need) || 599 strlen(vals[0]) >= sizeof(user->mbdb_fullname)) 600 goto skip; 601 602 sm_pwfullname(vals[0], name, user->mbdb_fullname, 603 sizeof(user->mbdb_fullname)); 604 need &= ~NEED_FULLNAME; 605 } 606 else if (strcasecmp(attr, "homeDirectory") == 0) 607 { 608 if (!bitset(NEED_HOMEDIR, need) || 609 strlen(vals[0]) >= sizeof(user->mbdb_homedir)) 610 goto skip; 611 612 (void) sm_strlcpy(user->mbdb_homedir, vals[0], 613 sizeof(user->mbdb_homedir)); 614 need &= ~NEED_HOMEDIR; 615 } 616 else if (strcasecmp(attr, "loginShell") == 0) 617 { 618 if (!bitset(NEED_SHELL, need) || 619 strlen(vals[0]) >= sizeof(user->mbdb_shell)) 620 goto skip; 621 622 (void) sm_strlcpy(user->mbdb_shell, vals[0], 623 sizeof(user->mbdb_shell)); 624 need &= ~NEED_SHELL; 625 } 626 else if (strcasecmp(attr, "uidNumber") == 0) 627 { 628 char *p; 629 630 if (!bitset(NEED_UID, need)) 631 goto skip; 632 633 for (p = vals[0]; *p != '\0'; p++) 634 { 635 /* allow negative numbers */ 636 if (p == vals[0] && *p == '-') 637 { 638 /* but not simply '-' */ 639 if (*(p + 1) == '\0') 640 goto skip; 641 } 642 else if (!isascii(*p) || !isdigit(*p)) 643 goto skip; 644 } 645 user->mbdb_uid = atoi(vals[0]); 646 need &= ~NEED_UID; 647 } 648 else if (strcasecmp(attr, "gidNumber") == 0) 649 { 650 char *p; 651 652 if (!bitset(NEED_GID, need)) 653 goto skip; 654 655 for (p = vals[0]; *p != '\0'; p++) 656 { 657 /* allow negative numbers */ 658 if (p == vals[0] && *p == '-') 659 { 660 /* but not simply '-' */ 661 if (*(p + 1) == '\0') 662 goto skip; 663 } 664 else if (!isascii(*p) || !isdigit(*p)) 665 goto skip; 666 } 667 user->mbdb_gid = atoi(vals[0]); 668 need &= ~NEED_GID; 669 } 670 671 skip: 672 ldap_value_free(vals); 673 # if USING_NETSCAPE_LDAP 674 ldap_memfree(attr); 675 # endif /* USING_NETSCAPE_LDAP */ 676 } 677 678 errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld); 679 680 /* 681 ** We check errno != LDAP_DECODING_ERROR since 682 ** OpenLDAP 1.X has a very ugly *undocumented* 683 ** hack of returning this error code from 684 ** ldap_next_attribute() if the library freed the 685 ** ber attribute. See: 686 ** http://www.openldap.org/lists/openldap-devel/9901/msg00064.html 687 */ 688 689 if (errno != LDAP_SUCCESS && 690 errno != LDAP_DECODING_ERROR) 691 { 692 /* Must be an error */ 693 errno += E_LDAPBASE; 694 ret = EX_TEMPFAIL; 695 goto abort; 696 } 697 698 abort: 699 save_errno = errno; 700 if (attr != NULL) 701 { 702 # if USING_NETSCAPE_LDAP 703 ldap_memfree(attr); 704 # endif /* USING_NETSCAPE_LDAP */ 705 attr = NULL; 706 } 707 if (LDAPLMAP.ldap_res != NULL) 708 { 709 ldap_msgfree(LDAPLMAP.ldap_res); 710 LDAPLMAP.ldap_res = NULL; 711 } 712 if (ret == EX_OK) 713 { 714 if (need == 0) 715 { 716 (void) sm_strlcpy(user->mbdb_name, name, 717 sizeof(user->mbdb_name)); 718 save_errno = 0; 719 } 720 else 721 { 722 ret = EX_NOUSER; 723 save_errno = EINVAL; 724 } 725 } 726 errno = save_errno; 727 return ret; 728 } 729 730 /* 731 ** MBDB_LDAP_TERMINATE -- terminate connection to the mailbox database 732 ** 733 ** Parameters: 734 ** none. 735 ** 736 ** Results: 737 ** none. 738 */ 739 740 static void 741 mbdb_ldap_terminate() 742 { 743 sm_ldap_close(&LDAPLMAP); 744 } 745 # endif /* _LDAP_EXAMPLE_ */ 746 #endif /* LDAPMAP */ 747