2 * Copyright (c) 2001-2003,2009 Proofpoint, Inc. and its suppliers.
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.
11 SM_RCSID("@(#)$Id: mbdb.c,v 1.43 2014-01-08 17:03:15 ca Exp $")
13 #include <sys/param.h>
22 #include <sm/limits.h>
24 #include <sm/assert.h>
25 #include <sm/bitops.h>
26 #include <sm/errstring.h>
29 #include <sm/string.h>
31 # undef EX_OK /* for SVr4.2 SMP */
33 #include <sm/sysexits.h>
44 int (*mbdb_initialize) __P((char *));
45 int (*mbdb_lookup) __P((char *name, SM_MBDB_T *user));
46 void (*mbdb_terminate) __P((void));
49 static int mbdb_pw_initialize __P((char *));
50 static int mbdb_pw_lookup __P((char *name, SM_MBDB_T *user));
51 static void mbdb_pw_terminate __P((void));
55 static struct sm_ldap_struct LDAPLMAP;
56 static int mbdb_ldap_initialize __P((char *));
57 static int mbdb_ldap_lookup __P((char *name, SM_MBDB_T *user));
58 static void mbdb_ldap_terminate __P((void));
59 # endif /* _LDAP_EXAMPLE_ */
62 static SM_MBDB_TYPE_T SmMbdbTypes[] =
64 { "pw", mbdb_pw_initialize, mbdb_pw_lookup, mbdb_pw_terminate },
67 { "ldap", mbdb_ldap_initialize, mbdb_ldap_lookup, mbdb_ldap_terminate },
70 { NULL, NULL, NULL, NULL }
73 static SM_MBDB_TYPE_T *SmMbdbType = &SmMbdbTypes[0];
76 ** SM_MBDB_INITIALIZE -- specify which mailbox database to use
78 ** If this function is not called, then the "pw" implementation
79 ** is used by default; this implementation uses getpwnam().
82 ** mbdb -- Which mailbox database to use.
83 ** The argument has the form "name" or "name.arg".
84 ** "pw" means use getpwnam().
87 ** EX_OK on success, or an EX_* code on failure.
91 sm_mbdb_initialize(mbdb)
100 SM_REQUIRE(mbdb != NULL);
103 arg = strchr(mbdb, '.');
105 namelen = strlen(name);
108 namelen = arg - name;
112 for (t = SmMbdbTypes; t->mbdb_typename != NULL; ++t)
114 if (strlen(t->mbdb_typename) == namelen &&
115 strncmp(name, t->mbdb_typename, namelen) == 0)
118 if (t->mbdb_initialize != NULL)
119 err = t->mbdb_initialize(arg);
125 return EX_UNAVAILABLE;
129 ** SM_MBDB_TERMINATE -- terminate connection to the mailbox database
131 ** Because this function closes any cached file descriptors that
132 ** are being held open for the connection to the mailbox database,
133 ** it should be called for security reasons prior to dropping privileges
134 ** and execing another process.
146 if (SmMbdbType->mbdb_terminate != NULL)
147 SmMbdbType->mbdb_terminate();
151 ** SM_MBDB_LOOKUP -- look up a local mail recipient, given name
154 ** name -- name of local mail recipient
155 ** user -- pointer to structure to fill in on success
158 ** On success, fill in *user and return EX_OK.
159 ** If the user does not exist, return EX_NOUSER.
160 ** If a temporary failure (eg, a network failure) occurred,
161 ** return EX_TEMPFAIL. Otherwise return EX_OSERR.
165 sm_mbdb_lookup(name, user)
171 if (SmMbdbType->mbdb_lookup != NULL)
172 ret = SmMbdbType->mbdb_lookup(name, user);
177 ** SM_MBDB_FROMPW -- copy from struct pw to SM_MBDB_T
180 ** user -- destination user information structure
181 ** pw -- source passwd structure
188 sm_mbdb_frompw(user, pw)
192 SM_REQUIRE(user != NULL);
193 (void) sm_strlcpy(user->mbdb_name, pw->pw_name,
194 sizeof(user->mbdb_name));
195 user->mbdb_uid = pw->pw_uid;
196 user->mbdb_gid = pw->pw_gid;
197 sm_pwfullname(pw->pw_gecos, pw->pw_name, user->mbdb_fullname,
198 sizeof(user->mbdb_fullname));
199 (void) sm_strlcpy(user->mbdb_homedir, pw->pw_dir,
200 sizeof(user->mbdb_homedir));
201 (void) sm_strlcpy(user->mbdb_shell, pw->pw_shell,
202 sizeof(user->mbdb_shell));
206 ** SM_PWFULLNAME -- build full name of user from pw_gecos field.
208 ** This routine interprets the strange entry that would appear
209 ** in the GECOS field of the password file.
212 ** gecos -- name to build.
213 ** user -- the login name of this user (for &).
214 ** buf -- place to put the result.
215 ** buflen -- length of buf.
221 #if _FFR_HANDLE_ISO8859_GECOS
222 static char Latin1ToASCII[128] =
224 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
225 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 33,
226 99, 80, 36, 89, 124, 36, 34, 99, 97, 60, 45, 45, 114, 45, 111, 42,
227 50, 51, 39, 117, 80, 46, 44, 49, 111, 62, 42, 42, 42, 63, 65, 65,
228 65, 65, 65, 65, 65, 67, 69, 69, 69, 69, 73, 73, 73, 73, 68, 78, 79,
229 79, 79, 79, 79, 88, 79, 85, 85, 85, 85, 89, 80, 66, 97, 97, 97, 97,
230 97, 97, 97, 99, 101, 101, 101, 101, 105, 105, 105, 105, 100, 110,
231 111, 111, 111, 111, 111, 47, 111, 117, 117, 117, 117, 121, 112, 121
233 #endif /* _FFR_HANDLE_ISO8859_GECOS */
236 sm_pwfullname(gecos, user, buf, buflen)
237 register char *gecos;
243 register char *bp = buf;
248 /* copy gecos, interpolating & to be full name */
249 for (p = gecos; *p != '\0' && *p != ',' && *p != ';' && *p != '%'; p++)
251 if (bp >= &buf[buflen - 1])
253 /* buffer overflow -- just use login name */
254 (void) sm_strlcpy(buf, user, buflen);
259 /* interpolate full name */
260 (void) sm_strlcpy(bp, user, buflen - (bp - buf));
266 #if _FFR_HANDLE_ISO8859_GECOS
267 if ((unsigned char) *p >= 128)
268 *bp++ = Latin1ToASCII[(unsigned char) *p - 128];
278 ** /etc/passwd implementation.
282 ** MBDB_PW_INITIALIZE -- initialize getpwnam() version
293 mbdb_pw_initialize(arg)
300 ** MBDB_PW_LOOKUP -- look up a local mail recipient, given name
303 ** name -- name of local mail recipient
304 ** user -- pointer to structure to fill in on success
307 ** On success, fill in *user and return EX_OK.
308 ** Failure: EX_NOUSER.
312 mbdb_pw_lookup(name, user)
318 #if HESIOD && !HESIOD_ALLOW_NUMERIC_LOGIN
319 /* DEC Hesiod getpwnam accepts numeric strings -- short circuit it */
323 for (p = name; *p != '\0'; p++)
324 if (!isascii(*p) || !isdigit(*p))
329 #endif /* HESIOD && !HESIOD_ALLOW_NUMERIC_LOGIN */
335 #if _FFR_USE_GETPWNAM_ERRNO
337 ** Only enable this code iff
338 ** user unknown <-> getpwnam() == NULL && errno == 0
339 ** (i.e., errno unchanged); see the POSIX spec.
344 #endif /* _FFR_USE_GETPWNAM_ERRNO */
348 sm_mbdb_frompw(user, pw);
353 ** MBDB_PW_TERMINATE -- terminate connection to the mailbox database
371 ** LDAP example implementation based on RFC 2307, "An Approach for Using
372 ** LDAP as a Network Information Service":
374 ** ( nisSchema.1.0 NAME 'uidNumber'
375 ** DESC 'An integer uniquely identifying a user in an
376 ** administrative domain'
377 ** EQUALITY integerMatch SYNTAX 'INTEGER' SINGLE-VALUE )
379 ** ( nisSchema.1.1 NAME 'gidNumber'
380 ** DESC 'An integer uniquely identifying a group in an
381 ** administrative domain'
382 ** EQUALITY integerMatch SYNTAX 'INTEGER' SINGLE-VALUE )
384 ** ( nisSchema.1.2 NAME 'gecos'
385 ** DESC 'The GECOS field; the common name'
386 ** EQUALITY caseIgnoreIA5Match
387 ** SUBSTRINGS caseIgnoreIA5SubstringsMatch
388 ** SYNTAX 'IA5String' SINGLE-VALUE )
390 ** ( nisSchema.1.3 NAME 'homeDirectory'
391 ** DESC 'The absolute path to the home directory'
392 ** EQUALITY caseExactIA5Match
393 ** SYNTAX 'IA5String' SINGLE-VALUE )
395 ** ( nisSchema.1.4 NAME 'loginShell'
396 ** DESC 'The path to the login shell'
397 ** EQUALITY caseExactIA5Match
398 ** SYNTAX 'IA5String' SINGLE-VALUE )
400 ** ( nisSchema.2.0 NAME 'posixAccount' SUP top AUXILIARY
401 ** DESC 'Abstraction of an account with POSIX attributes'
402 ** MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory )
403 ** MAY ( userPassword $ loginShell $ gecos $ description ) )
407 # define MBDB_LDAP_LABEL "MailboxDatabase"
409 # ifndef MBDB_LDAP_FILTER
410 # define MBDB_LDAP_FILTER "(&(objectClass=posixAccount)(uid=%0))"
413 # ifndef MBDB_DEFAULT_LDAP_BASEDN
414 # define MBDB_DEFAULT_LDAP_BASEDN NULL
417 # ifndef MBDB_DEFAULT_LDAP_SERVER
418 # define MBDB_DEFAULT_LDAP_SERVER NULL
422 ** MBDB_LDAP_INITIALIZE -- initialize LDAP version
425 ** arg -- LDAP specification
428 ** EX_OK on success, or an EX_* code on failure.
432 mbdb_ldap_initialize(arg)
435 sm_ldap_clear(&LDAPLMAP);
436 LDAPLMAP.ldap_base = MBDB_DEFAULT_LDAP_BASEDN;
437 LDAPLMAP.ldap_host = MBDB_DEFAULT_LDAP_SERVER;
438 LDAPLMAP.ldap_filter = MBDB_LDAP_FILTER;
440 /* Only want one match */
441 LDAPLMAP.ldap_sizelimit = 1;
443 /* interpolate new ldap_base and ldap_host from arg if given */
444 if (arg != NULL && *arg != '\0')
450 len = strlen(arg) + 1;
451 new = sm_malloc(len);
454 (void) sm_strlcpy(new, arg, len);
455 sep = strrchr(new, '@');
459 LDAPLMAP.ldap_host = sep;
461 LDAPLMAP.ldap_base = new;
468 ** MBDB_LDAP_LOOKUP -- look up a local mail recipient, given name
471 ** name -- name of local mail recipient
472 ** user -- pointer to structure to fill in on success
475 ** On success, fill in *user and return EX_OK.
476 ** Failure: EX_NOUSER.
479 #define NEED_FULLNAME 0x01
480 #define NEED_HOMEDIR 0x02
481 #define NEED_SHELL 0x04
482 #define NEED_UID 0x08
483 #define NEED_GID 0x10
486 mbdb_ldap_lookup(name, user)
498 if (strlen(name) >= sizeof(user->mbdb_name))
504 if (LDAPLMAP.ldap_filter == NULL)
506 /* map not initialized, but don't have arg here */
511 if (LDAPLMAP.ldap_pid != getpid())
513 /* re-open map in this child process */
514 LDAPLMAP.ldap_ld = NULL;
517 if (LDAPLMAP.ldap_ld == NULL)
519 /* map not open, try to open now */
520 if (!sm_ldap_start(MBDB_LDAP_LABEL, &LDAPLMAP))
524 sm_ldap_setopts(LDAPLMAP.ldap_ld, &LDAPLMAP);
525 msgid = sm_ldap_search(&LDAPLMAP, name);
528 save_errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld) + E_LDAPBASE;
529 # ifdef LDAP_SERVER_DOWN
530 if (errno == LDAP_SERVER_DOWN)
532 /* server disappeared, try reopen on next search */
533 sm_ldap_close(&LDAPLMAP);
535 # endif /* LDAP_SERVER_DOWN */
541 ret = ldap_result(LDAPLMAP.ldap_ld, msgid, 1,
542 (LDAPLMAP.ldap_timeout.tv_sec == 0 ? NULL :
543 &(LDAPLMAP.ldap_timeout)),
544 &(LDAPLMAP.ldap_res));
546 if (ret != LDAP_RES_SEARCH_RESULT &&
547 ret != LDAP_RES_SEARCH_ENTRY)
552 errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld);
557 entry = ldap_first_entry(LDAPLMAP.ldap_ld, LDAPLMAP.ldap_res);
563 ** We may have gotten an LDAP_RES_SEARCH_RESULT response
564 ** with an error inside it, so we have to extract that
565 ** with ldap_parse_result(). This can happen when talking
566 ** to an LDAP proxy whose backend has gone down.
569 save_errno = ldap_parse_result(LDAPLMAP.ldap_ld,
570 LDAPLMAP.ldap_res, &rc, NULL,
571 NULL, NULL, NULL, 0);
572 if (save_errno == LDAP_SUCCESS)
574 if (save_errno == LDAP_SUCCESS)
587 # if !defined(LDAP_VERSION_MAX) && !defined(LDAP_OPT_SIZELIMIT)
589 ** Reset value to prevent lingering
590 ** LDAP_DECODING_ERROR due to
591 ** OpenLDAP 1.X's hack (see below)
594 LDAPLMAP.ldap_ld->ld_errno = LDAP_SUCCESS;
595 # endif /* !defined(LDAP_VERSION_MAX) !defined(LDAP_OPT_SIZELIMIT) */
598 need = NEED_FULLNAME|NEED_HOMEDIR|NEED_SHELL|NEED_UID|NEED_GID;
599 for (attr = ldap_first_attribute(LDAPLMAP.ldap_ld, entry, &ber);
601 attr = ldap_next_attribute(LDAPLMAP.ldap_ld, entry, ber))
605 vals = ldap_get_values(LDAPLMAP.ldap_ld, entry, attr);
608 errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld);
609 if (errno == LDAP_SUCCESS)
615 /* Must be an error */
621 # if !defined(LDAP_VERSION_MAX) && !defined(LDAP_OPT_SIZELIMIT)
623 ** Reset value to prevent lingering
624 ** LDAP_DECODING_ERROR due to
625 ** OpenLDAP 1.X's hack (see below)
628 LDAPLMAP.ldap_ld->ld_errno = LDAP_SUCCESS;
629 # endif /* !defined(LDAP_VERSION_MAX) !defined(LDAP_OPT_SIZELIMIT) */
631 if (vals[0] == NULL || vals[0][0] == '\0')
634 if (strcasecmp(attr, "gecos") == 0)
636 if (!bitset(NEED_FULLNAME, need) ||
637 strlen(vals[0]) >= sizeof(user->mbdb_fullname))
640 sm_pwfullname(vals[0], name, user->mbdb_fullname,
641 sizeof(user->mbdb_fullname));
642 need &= ~NEED_FULLNAME;
644 else if (strcasecmp(attr, "homeDirectory") == 0)
646 if (!bitset(NEED_HOMEDIR, need) ||
647 strlen(vals[0]) >= sizeof(user->mbdb_homedir))
650 (void) sm_strlcpy(user->mbdb_homedir, vals[0],
651 sizeof(user->mbdb_homedir));
652 need &= ~NEED_HOMEDIR;
654 else if (strcasecmp(attr, "loginShell") == 0)
656 if (!bitset(NEED_SHELL, need) ||
657 strlen(vals[0]) >= sizeof(user->mbdb_shell))
660 (void) sm_strlcpy(user->mbdb_shell, vals[0],
661 sizeof(user->mbdb_shell));
664 else if (strcasecmp(attr, "uidNumber") == 0)
668 if (!bitset(NEED_UID, need))
671 for (p = vals[0]; *p != '\0'; p++)
673 /* allow negative numbers */
674 if (p == vals[0] && *p == '-')
676 /* but not simply '-' */
677 if (*(p + 1) == '\0')
680 else if (!isascii(*p) || !isdigit(*p))
683 user->mbdb_uid = atoi(vals[0]);
686 else if (strcasecmp(attr, "gidNumber") == 0)
690 if (!bitset(NEED_GID, need))
693 for (p = vals[0]; *p != '\0'; p++)
695 /* allow negative numbers */
696 if (p == vals[0] && *p == '-')
698 /* but not simply '-' */
699 if (*(p + 1) == '\0')
702 else if (!isascii(*p) || !isdigit(*p))
705 user->mbdb_gid = atoi(vals[0]);
710 ldap_value_free(vals);
714 errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld);
717 ** We check errno != LDAP_DECODING_ERROR since
718 ** OpenLDAP 1.X has a very ugly *undocumented*
719 ** hack of returning this error code from
720 ** ldap_next_attribute() if the library freed the
721 ** ber attribute. See:
722 ** http://www.openldap.org/lists/openldap-devel/9901/msg00064.html
725 if (errno != LDAP_SUCCESS &&
726 errno != LDAP_DECODING_ERROR)
728 /* Must be an error */
741 if (LDAPLMAP.ldap_res != NULL)
743 ldap_msgfree(LDAPLMAP.ldap_res);
744 LDAPLMAP.ldap_res = NULL;
750 (void) sm_strlcpy(user->mbdb_name, name,
751 sizeof(user->mbdb_name));
765 ** MBDB_LDAP_TERMINATE -- terminate connection to the mailbox database
775 mbdb_ldap_terminate()
777 sm_ldap_close(&LDAPLMAP);
779 # endif /* _LDAP_EXAMPLE_ */