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>
30 #include <sm/sysexits.h>
32 #if LDAPMAP && _LDAP_EXAMPLE_
39 int (*mbdb_initialize) __P((char *));
40 int (*mbdb_lookup) __P((char *name, SM_MBDB_T *user));
41 void (*mbdb_terminate) __P((void));
44 static int mbdb_pw_initialize __P((char *));
45 static int mbdb_pw_lookup __P((char *name, SM_MBDB_T *user));
46 static void mbdb_pw_terminate __P((void));
48 #if LDAPMAP && _LDAP_EXAMPLE_
49 static struct sm_ldap_struct LDAPLMAP;
50 static int mbdb_ldap_initialize __P((char *));
51 static int mbdb_ldap_lookup __P((char *name, SM_MBDB_T *user));
52 static void mbdb_ldap_terminate __P((void));
53 #endif /* LDAPMAP && _LDAP_EXAMPLE_ */
55 static SM_MBDB_TYPE_T SmMbdbTypes[] =
57 { "pw", mbdb_pw_initialize, mbdb_pw_lookup, mbdb_pw_terminate },
58 #if LDAPMAP && _LDAP_EXAMPLE_
59 { "ldap", mbdb_ldap_initialize, mbdb_ldap_lookup, mbdb_ldap_terminate },
61 { NULL, NULL, NULL, NULL }
64 static SM_MBDB_TYPE_T *SmMbdbType = &SmMbdbTypes[0];
67 ** SM_MBDB_INITIALIZE -- specify which mailbox database to use
69 ** If this function is not called, then the "pw" implementation
70 ** is used by default; this implementation uses getpwnam().
73 ** mbdb -- Which mailbox database to use.
74 ** The argument has the form "name" or "name.arg".
75 ** "pw" means use getpwnam().
78 ** EX_OK on success, or an EX_* code on failure.
82 sm_mbdb_initialize(mbdb)
91 SM_REQUIRE(mbdb != NULL);
94 arg = strchr(mbdb, '.');
96 namelen = strlen(name);
103 for (t = SmMbdbTypes; t->mbdb_typename != NULL; ++t)
105 if (strlen(t->mbdb_typename) == namelen &&
106 strncmp(name, t->mbdb_typename, namelen) == 0)
109 if (t->mbdb_initialize != NULL)
110 err = t->mbdb_initialize(arg);
116 return EX_UNAVAILABLE;
120 ** SM_MBDB_TERMINATE -- terminate connection to the mailbox database
122 ** Because this function closes any cached file descriptors that
123 ** are being held open for the connection to the mailbox database,
124 ** it should be called for security reasons prior to dropping privileges
125 ** and execing another process.
137 if (SmMbdbType->mbdb_terminate != NULL)
138 SmMbdbType->mbdb_terminate();
142 ** SM_MBDB_LOOKUP -- look up a local mail recipient, given name
145 ** name -- name of local mail recipient
146 ** user -- pointer to structure to fill in on success
149 ** On success, fill in *user and return EX_OK.
150 ** If the user does not exist, return EX_NOUSER.
151 ** If a temporary failure (eg, a network failure) occurred,
152 ** return EX_TEMPFAIL. Otherwise return EX_OSERR.
156 sm_mbdb_lookup(name, user)
162 if (SmMbdbType->mbdb_lookup != NULL)
163 ret = SmMbdbType->mbdb_lookup(name, user);
168 ** SM_MBDB_FROMPW -- copy from struct pw to SM_MBDB_T
171 ** user -- destination user information structure
172 ** pw -- source passwd structure
179 sm_mbdb_frompw(user, pw)
183 SM_REQUIRE(user != NULL);
184 (void) sm_strlcpy(user->mbdb_name, pw->pw_name,
185 sizeof(user->mbdb_name));
186 user->mbdb_uid = pw->pw_uid;
187 user->mbdb_gid = pw->pw_gid;
188 sm_pwfullname(pw->pw_gecos, pw->pw_name, user->mbdb_fullname,
189 sizeof(user->mbdb_fullname));
190 (void) sm_strlcpy(user->mbdb_homedir, pw->pw_dir,
191 sizeof(user->mbdb_homedir));
192 (void) sm_strlcpy(user->mbdb_shell, pw->pw_shell,
193 sizeof(user->mbdb_shell));
197 ** SM_PWFULLNAME -- build full name of user from pw_gecos field.
199 ** This routine interprets the strange entry that would appear
200 ** in the GECOS field of the password file.
203 ** gecos -- name to build.
204 ** user -- the login name of this user (for &).
205 ** buf -- place to put the result.
206 ** buflen -- length of buf.
212 #if _FFR_HANDLE_ISO8859_GECOS
213 static char Latin1ToASCII[128] =
215 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32,
216 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 33,
217 99, 80, 36, 89, 124, 36, 34, 99, 97, 60, 45, 45, 114, 45, 111, 42,
218 50, 51, 39, 117, 80, 46, 44, 49, 111, 62, 42, 42, 42, 63, 65, 65,
219 65, 65, 65, 65, 65, 67, 69, 69, 69, 69, 73, 73, 73, 73, 68, 78, 79,
220 79, 79, 79, 79, 88, 79, 85, 85, 85, 85, 89, 80, 66, 97, 97, 97, 97,
221 97, 97, 97, 99, 101, 101, 101, 101, 105, 105, 105, 105, 100, 110,
222 111, 111, 111, 111, 111, 47, 111, 117, 117, 117, 117, 121, 112, 121
224 #endif /* _FFR_HANDLE_ISO8859_GECOS */
227 sm_pwfullname(gecos, user, buf, buflen)
228 register char *gecos;
234 register char *bp = buf;
239 /* copy gecos, interpolating & to be full name */
240 for (p = gecos; *p != '\0' && *p != ',' && *p != ';' && *p != '%'; p++)
242 if (bp >= &buf[buflen - 1])
244 /* buffer overflow -- just use login name */
245 (void) sm_strlcpy(buf, user, buflen);
250 /* interpolate full name */
251 (void) sm_strlcpy(bp, user, buflen - (bp - buf));
257 #if _FFR_HANDLE_ISO8859_GECOS
258 if ((unsigned char) *p >= 128)
259 *bp++ = Latin1ToASCII[(unsigned char) *p - 128];
262 /* "else" in #if code above */
270 ** /etc/passwd implementation.
274 ** MBDB_PW_INITIALIZE -- initialize getpwnam() version
285 mbdb_pw_initialize(arg)
292 ** MBDB_PW_LOOKUP -- look up a local mail recipient, given name
295 ** name -- name of local mail recipient
296 ** user -- pointer to structure to fill in on success
299 ** On success, fill in *user and return EX_OK.
300 ** Failure: EX_NOUSER.
304 mbdb_pw_lookup(name, user)
310 #if HESIOD && !HESIOD_ALLOW_NUMERIC_LOGIN
311 /* DEC Hesiod getpwnam accepts numeric strings -- short circuit it */
315 for (p = name; *p != '\0'; p++)
316 if (!isascii(*p) || !isdigit(*p))
321 #endif /* HESIOD && !HESIOD_ALLOW_NUMERIC_LOGIN */
327 #if _FFR_USE_GETPWNAM_ERRNO
329 ** Only enable this code iff
330 ** user unknown <-> getpwnam() == NULL && errno == 0
331 ** (i.e., errno unchanged); see the POSIX spec.
336 #endif /* _FFR_USE_GETPWNAM_ERRNO */
340 sm_mbdb_frompw(user, pw);
345 ** MBDB_PW_TERMINATE -- terminate connection to the mailbox database
360 #if LDAPMAP && _LDAP_EXAMPLE_
362 ** LDAP example implementation based on RFC 2307, "An Approach for Using
363 ** LDAP as a Network Information Service":
365 ** ( nisSchema.1.0 NAME 'uidNumber'
366 ** DESC 'An integer uniquely identifying a user in an
367 ** administrative domain'
368 ** EQUALITY integerMatch SYNTAX 'INTEGER' SINGLE-VALUE )
370 ** ( nisSchema.1.1 NAME 'gidNumber'
371 ** DESC 'An integer uniquely identifying a group in an
372 ** administrative domain'
373 ** EQUALITY integerMatch SYNTAX 'INTEGER' SINGLE-VALUE )
375 ** ( nisSchema.1.2 NAME 'gecos'
376 ** DESC 'The GECOS field; the common name'
377 ** EQUALITY caseIgnoreIA5Match
378 ** SUBSTRINGS caseIgnoreIA5SubstringsMatch
379 ** SYNTAX 'IA5String' SINGLE-VALUE )
381 ** ( nisSchema.1.3 NAME 'homeDirectory'
382 ** DESC 'The absolute path to the home directory'
383 ** EQUALITY caseExactIA5Match
384 ** SYNTAX 'IA5String' SINGLE-VALUE )
386 ** ( nisSchema.1.4 NAME 'loginShell'
387 ** DESC 'The path to the login shell'
388 ** EQUALITY caseExactIA5Match
389 ** SYNTAX 'IA5String' SINGLE-VALUE )
391 ** ( nisSchema.2.0 NAME 'posixAccount' SUP top AUXILIARY
392 ** DESC 'Abstraction of an account with POSIX attributes'
393 ** MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory )
394 ** MAY ( userPassword $ loginShell $ gecos $ description ) )
398 # define MBDB_LDAP_LABEL "MailboxDatabase"
400 # ifndef MBDB_LDAP_FILTER
401 # define MBDB_LDAP_FILTER "(&(objectClass=posixAccount)(uid=%0))"
404 # ifndef MBDB_DEFAULT_LDAP_BASEDN
405 # define MBDB_DEFAULT_LDAP_BASEDN NULL
408 # ifndef MBDB_DEFAULT_LDAP_SERVER
409 # define MBDB_DEFAULT_LDAP_SERVER NULL
413 ** MBDB_LDAP_INITIALIZE -- initialize LDAP version
416 ** arg -- LDAP specification
419 ** EX_OK on success, or an EX_* code on failure.
423 mbdb_ldap_initialize(arg)
426 sm_ldap_clear(&LDAPLMAP);
427 LDAPLMAP.ldap_base = MBDB_DEFAULT_LDAP_BASEDN;
428 LDAPLMAP.ldap_host = MBDB_DEFAULT_LDAP_SERVER;
429 LDAPLMAP.ldap_filter = MBDB_LDAP_FILTER;
431 /* Only want one match */
432 LDAPLMAP.ldap_sizelimit = 1;
434 /* interpolate new ldap_base and ldap_host from arg if given */
435 if (arg != NULL && *arg != '\0')
441 len = strlen(arg) + 1;
442 new = sm_malloc(len);
445 (void) sm_strlcpy(new, arg, len);
446 sep = strrchr(new, '@');
450 LDAPLMAP.ldap_host = sep;
452 LDAPLMAP.ldap_base = new;
459 ** MBDB_LDAP_LOOKUP -- look up a local mail recipient, given name
462 ** name -- name of local mail recipient
463 ** user -- pointer to structure to fill in on success
466 ** On success, fill in *user and return EX_OK.
467 ** Failure: EX_NOUSER.
470 #define NEED_FULLNAME 0x01
471 #define NEED_HOMEDIR 0x02
472 #define NEED_SHELL 0x04
473 #define NEED_UID 0x08
474 #define NEED_GID 0x10
477 mbdb_ldap_lookup(name, user)
489 if (strlen(name) >= sizeof(user->mbdb_name))
495 if (LDAPLMAP.ldap_filter == NULL)
497 /* map not initialized, but don't have arg here */
502 if (LDAPLMAP.ldap_pid != getpid())
504 /* re-open map in this child process */
505 LDAPLMAP.ldap_ld = NULL;
508 if (LDAPLMAP.ldap_ld == NULL)
510 /* map not open, try to open now */
511 if (!sm_ldap_start(MBDB_LDAP_LABEL, &LDAPLMAP))
515 sm_ldap_setopts(LDAPLMAP.ldap_ld, &LDAPLMAP);
516 msgid = sm_ldap_search(&LDAPLMAP, name);
519 save_errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld) + E_LDAPBASE;
520 # ifdef LDAP_SERVER_DOWN
521 if (errno == LDAP_SERVER_DOWN)
523 /* server disappeared, try reopen on next search */
524 sm_ldap_close(&LDAPLMAP);
526 # endif /* LDAP_SERVER_DOWN */
532 ret = ldap_result(LDAPLMAP.ldap_ld, msgid, 1,
533 (LDAPLMAP.ldap_timeout.tv_sec == 0 ? NULL :
534 &(LDAPLMAP.ldap_timeout)),
535 &(LDAPLMAP.ldap_res));
537 if (ret != LDAP_RES_SEARCH_RESULT &&
538 ret != LDAP_RES_SEARCH_ENTRY)
543 errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld);
548 entry = ldap_first_entry(LDAPLMAP.ldap_ld, LDAPLMAP.ldap_res);
554 ** We may have gotten an LDAP_RES_SEARCH_RESULT response
555 ** with an error inside it, so we have to extract that
556 ** with ldap_parse_result(). This can happen when talking
557 ** to an LDAP proxy whose backend has gone down.
560 save_errno = ldap_parse_result(LDAPLMAP.ldap_ld,
561 LDAPLMAP.ldap_res, &rc, NULL,
562 NULL, NULL, NULL, 0);
563 if (save_errno == LDAP_SUCCESS)
565 if (save_errno == LDAP_SUCCESS)
578 # if !defined(LDAP_VERSION_MAX) && !defined(LDAP_OPT_SIZELIMIT)
580 ** Reset value to prevent lingering
581 ** LDAP_DECODING_ERROR due to
582 ** OpenLDAP 1.X's hack (see below)
585 LDAPLMAP.ldap_ld->ld_errno = LDAP_SUCCESS;
586 # endif /* !defined(LDAP_VERSION_MAX) !defined(LDAP_OPT_SIZELIMIT) */
589 need = NEED_FULLNAME|NEED_HOMEDIR|NEED_SHELL|NEED_UID|NEED_GID;
590 for (attr = ldap_first_attribute(LDAPLMAP.ldap_ld, entry, &ber);
592 attr = ldap_next_attribute(LDAPLMAP.ldap_ld, entry, ber))
596 vals = ldap_get_values(LDAPLMAP.ldap_ld, entry, attr);
599 errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld);
600 if (errno == LDAP_SUCCESS)
606 /* Must be an error */
612 # if !defined(LDAP_VERSION_MAX) && !defined(LDAP_OPT_SIZELIMIT)
614 ** Reset value to prevent lingering
615 ** LDAP_DECODING_ERROR due to
616 ** OpenLDAP 1.X's hack (see below)
619 LDAPLMAP.ldap_ld->ld_errno = LDAP_SUCCESS;
620 # endif /* !defined(LDAP_VERSION_MAX) !defined(LDAP_OPT_SIZELIMIT) */
622 if (vals[0] == NULL || vals[0][0] == '\0')
625 if (strcasecmp(attr, "gecos") == 0)
627 if (!bitset(NEED_FULLNAME, need) ||
628 strlen(vals[0]) >= sizeof(user->mbdb_fullname))
631 sm_pwfullname(vals[0], name, user->mbdb_fullname,
632 sizeof(user->mbdb_fullname));
633 need &= ~NEED_FULLNAME;
635 else if (strcasecmp(attr, "homeDirectory") == 0)
637 if (!bitset(NEED_HOMEDIR, need) ||
638 strlen(vals[0]) >= sizeof(user->mbdb_homedir))
641 (void) sm_strlcpy(user->mbdb_homedir, vals[0],
642 sizeof(user->mbdb_homedir));
643 need &= ~NEED_HOMEDIR;
645 else if (strcasecmp(attr, "loginShell") == 0)
647 if (!bitset(NEED_SHELL, need) ||
648 strlen(vals[0]) >= sizeof(user->mbdb_shell))
651 (void) sm_strlcpy(user->mbdb_shell, vals[0],
652 sizeof(user->mbdb_shell));
655 else if (strcasecmp(attr, "uidNumber") == 0)
659 if (!bitset(NEED_UID, need))
662 for (p = vals[0]; *p != '\0'; p++)
664 /* allow negative numbers */
665 if (p == vals[0] && *p == '-')
667 /* but not simply '-' */
668 if (*(p + 1) == '\0')
671 else if (!isascii(*p) || !isdigit(*p))
674 user->mbdb_uid = atoi(vals[0]);
677 else if (strcasecmp(attr, "gidNumber") == 0)
681 if (!bitset(NEED_GID, need))
684 for (p = vals[0]; *p != '\0'; p++)
686 /* allow negative numbers */
687 if (p == vals[0] && *p == '-')
689 /* but not simply '-' */
690 if (*(p + 1) == '\0')
693 else if (!isascii(*p) || !isdigit(*p))
696 user->mbdb_gid = atoi(vals[0]);
701 ldap_value_free(vals);
705 errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld);
708 ** We check errno != LDAP_DECODING_ERROR since
709 ** OpenLDAP 1.X has a very ugly *undocumented*
710 ** hack of returning this error code from
711 ** ldap_next_attribute() if the library freed the
712 ** ber attribute. See:
713 ** http://www.openldap.org/lists/openldap-devel/9901/msg00064.html
716 if (errno != LDAP_SUCCESS &&
717 errno != LDAP_DECODING_ERROR)
719 /* Must be an error */
732 if (LDAPLMAP.ldap_res != NULL)
734 ldap_msgfree(LDAPLMAP.ldap_res);
735 LDAPLMAP.ldap_res = NULL;
741 (void) sm_strlcpy(user->mbdb_name, name,
742 sizeof(user->mbdb_name));
756 ** MBDB_LDAP_TERMINATE -- terminate connection to the mailbox database
766 mbdb_ldap_terminate()
768 sm_ldap_close(&LDAPLMAP);
770 #endif /* LDAPMAP && _LDAP_EXAMPLE_ */