2 * Copyright (c) 2001-2003,2009 Sendmail, 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.41 2009/06/19 22:02:26 guenther 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>
38 # endif /* _LDAP_EXAMPLE_ */
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
},
68 # endif /* _LDAP_EXAMPLE_ */
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];
270 #endif /* _FFR_HANDLE_ISO8859_GECOS */
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
)
319 /* DEC Hesiod getpwnam accepts numeric strings -- short circuit it */
323 for (p
= name
; *p
!= '\0'; p
++)
324 if (!isascii(*p
) || !isdigit(*p
))
337 ** getpwnam() isn't advertised as setting errno.
338 ** In fact, under FreeBSD, non-root getpwnam() on
339 ** non-existant users returns NULL with errno = EPERM.
340 ** This test won't work.
355 sm_mbdb_frompw(user
, pw
);
360 ** MBDB_PW_TERMINATE -- terminate connection to the mailbox database
378 ** LDAP example implementation based on RFC 2307, "An Approach for Using
379 ** LDAP as a Network Information Service":
381 ** ( nisSchema.1.0 NAME 'uidNumber'
382 ** DESC 'An integer uniquely identifying a user in an
383 ** administrative domain'
384 ** EQUALITY integerMatch SYNTAX 'INTEGER' SINGLE-VALUE )
386 ** ( nisSchema.1.1 NAME 'gidNumber'
387 ** DESC 'An integer uniquely identifying a group in an
388 ** administrative domain'
389 ** EQUALITY integerMatch SYNTAX 'INTEGER' SINGLE-VALUE )
391 ** ( nisSchema.1.2 NAME 'gecos'
392 ** DESC 'The GECOS field; the common name'
393 ** EQUALITY caseIgnoreIA5Match
394 ** SUBSTRINGS caseIgnoreIA5SubstringsMatch
395 ** SYNTAX 'IA5String' SINGLE-VALUE )
397 ** ( nisSchema.1.3 NAME 'homeDirectory'
398 ** DESC 'The absolute path to the home directory'
399 ** EQUALITY caseExactIA5Match
400 ** SYNTAX 'IA5String' SINGLE-VALUE )
402 ** ( nisSchema.1.4 NAME 'loginShell'
403 ** DESC 'The path to the login shell'
404 ** EQUALITY caseExactIA5Match
405 ** SYNTAX 'IA5String' SINGLE-VALUE )
407 ** ( nisSchema.2.0 NAME 'posixAccount' SUP top AUXILIARY
408 ** DESC 'Abstraction of an account with POSIX attributes'
409 ** MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory )
410 ** MAY ( userPassword $ loginShell $ gecos $ description ) )
414 # define MBDB_LDAP_LABEL "MailboxDatabase"
416 # ifndef MBDB_LDAP_FILTER
417 # define MBDB_LDAP_FILTER "(&(objectClass=posixAccount)(uid=%0))"
418 # endif /* MBDB_LDAP_FILTER */
420 # ifndef MBDB_DEFAULT_LDAP_BASEDN
421 # define MBDB_DEFAULT_LDAP_BASEDN NULL
422 # endif /* MBDB_DEFAULT_LDAP_BASEDN */
424 # ifndef MBDB_DEFAULT_LDAP_SERVER
425 # define MBDB_DEFAULT_LDAP_SERVER NULL
426 # endif /* MBDB_DEFAULT_LDAP_SERVER */
429 ** MBDB_LDAP_INITIALIZE -- initialize LDAP version
432 ** arg -- LDAP specification
435 ** EX_OK on success, or an EX_* code on failure.
439 mbdb_ldap_initialize(arg
)
442 sm_ldap_clear(&LDAPLMAP
);
443 LDAPLMAP
.ldap_base
= MBDB_DEFAULT_LDAP_BASEDN
;
444 LDAPLMAP
.ldap_host
= MBDB_DEFAULT_LDAP_SERVER
;
445 LDAPLMAP
.ldap_filter
= MBDB_LDAP_FILTER
;
447 /* Only want one match */
448 LDAPLMAP
.ldap_sizelimit
= 1;
450 /* interpolate new ldap_base and ldap_host from arg if given */
451 if (arg
!= NULL
&& *arg
!= '\0')
457 len
= strlen(arg
) + 1;
458 new = sm_malloc(len
);
461 (void) sm_strlcpy(new, arg
, len
);
462 sep
= strrchr(new, '@');
466 LDAPLMAP
.ldap_host
= sep
;
468 LDAPLMAP
.ldap_base
= new;
475 ** MBDB_LDAP_LOOKUP -- look up a local mail recipient, given name
478 ** name -- name of local mail recipient
479 ** user -- pointer to structure to fill in on success
482 ** On success, fill in *user and return EX_OK.
483 ** Failure: EX_NOUSER.
486 #define NEED_FULLNAME 0x01
487 #define NEED_HOMEDIR 0x02
488 #define NEED_SHELL 0x04
489 #define NEED_UID 0x08
490 #define NEED_GID 0x10
493 mbdb_ldap_lookup(name
, user
)
505 if (strlen(name
) >= sizeof(user
->mbdb_name
))
511 if (LDAPLMAP
.ldap_filter
== NULL
)
513 /* map not initialized, but don't have arg here */
518 if (LDAPLMAP
.ldap_pid
!= getpid())
520 /* re-open map in this child process */
521 LDAPLMAP
.ldap_ld
= NULL
;
524 if (LDAPLMAP
.ldap_ld
== NULL
)
526 /* map not open, try to open now */
527 if (!sm_ldap_start(MBDB_LDAP_LABEL
, &LDAPLMAP
))
531 sm_ldap_setopts(LDAPLMAP
.ldap_ld
, &LDAPLMAP
);
532 msgid
= sm_ldap_search(&LDAPLMAP
, name
);
535 save_errno
= sm_ldap_geterrno(LDAPLMAP
.ldap_ld
) + E_LDAPBASE
;
536 # ifdef LDAP_SERVER_DOWN
537 if (errno
== LDAP_SERVER_DOWN
)
539 /* server disappeared, try reopen on next search */
540 sm_ldap_close(&LDAPLMAP
);
542 # endif /* LDAP_SERVER_DOWN */
548 ret
= ldap_result(LDAPLMAP
.ldap_ld
, msgid
, 1,
549 (LDAPLMAP
.ldap_timeout
.tv_sec
== 0 ? NULL
:
550 &(LDAPLMAP
.ldap_timeout
)),
551 &(LDAPLMAP
.ldap_res
));
553 if (ret
!= LDAP_RES_SEARCH_RESULT
&&
554 ret
!= LDAP_RES_SEARCH_ENTRY
)
559 errno
= sm_ldap_geterrno(LDAPLMAP
.ldap_ld
);
564 entry
= ldap_first_entry(LDAPLMAP
.ldap_ld
, LDAPLMAP
.ldap_res
);
570 ** We may have gotten an LDAP_RES_SEARCH_RESULT response
571 ** with an error inside it, so we have to extract that
572 ** with ldap_parse_result(). This can happen when talking
573 ** to an LDAP proxy whose backend has gone down.
576 save_errno
= ldap_parse_result(LDAPLMAP
.ldap_ld
,
577 LDAPLMAP
.ldap_res
, &rc
, NULL
,
578 NULL
, NULL
, NULL
, 0);
579 if (save_errno
== LDAP_SUCCESS
)
581 if (save_errno
== LDAP_SUCCESS
)
594 # if !defined(LDAP_VERSION_MAX) && !defined(LDAP_OPT_SIZELIMIT)
596 ** Reset value to prevent lingering
597 ** LDAP_DECODING_ERROR due to
598 ** OpenLDAP 1.X's hack (see below)
601 LDAPLMAP
.ldap_ld
->ld_errno
= LDAP_SUCCESS
;
602 # endif /* !defined(LDAP_VERSION_MAX) !defined(LDAP_OPT_SIZELIMIT) */
605 need
= NEED_FULLNAME
|NEED_HOMEDIR
|NEED_SHELL
|NEED_UID
|NEED_GID
;
606 for (attr
= ldap_first_attribute(LDAPLMAP
.ldap_ld
, entry
, &ber
);
608 attr
= ldap_next_attribute(LDAPLMAP
.ldap_ld
, entry
, ber
))
612 vals
= ldap_get_values(LDAPLMAP
.ldap_ld
, entry
, attr
);
615 errno
= sm_ldap_geterrno(LDAPLMAP
.ldap_ld
);
616 if (errno
== LDAP_SUCCESS
)
622 /* Must be an error */
628 # if !defined(LDAP_VERSION_MAX) && !defined(LDAP_OPT_SIZELIMIT)
630 ** Reset value to prevent lingering
631 ** LDAP_DECODING_ERROR due to
632 ** OpenLDAP 1.X's hack (see below)
635 LDAPLMAP
.ldap_ld
->ld_errno
= LDAP_SUCCESS
;
636 # endif /* !defined(LDAP_VERSION_MAX) !defined(LDAP_OPT_SIZELIMIT) */
638 if (vals
[0] == NULL
|| vals
[0][0] == '\0')
641 if (strcasecmp(attr
, "gecos") == 0)
643 if (!bitset(NEED_FULLNAME
, need
) ||
644 strlen(vals
[0]) >= sizeof(user
->mbdb_fullname
))
647 sm_pwfullname(vals
[0], name
, user
->mbdb_fullname
,
648 sizeof(user
->mbdb_fullname
));
649 need
&= ~NEED_FULLNAME
;
651 else if (strcasecmp(attr
, "homeDirectory") == 0)
653 if (!bitset(NEED_HOMEDIR
, need
) ||
654 strlen(vals
[0]) >= sizeof(user
->mbdb_homedir
))
657 (void) sm_strlcpy(user
->mbdb_homedir
, vals
[0],
658 sizeof(user
->mbdb_homedir
));
659 need
&= ~NEED_HOMEDIR
;
661 else if (strcasecmp(attr
, "loginShell") == 0)
663 if (!bitset(NEED_SHELL
, need
) ||
664 strlen(vals
[0]) >= sizeof(user
->mbdb_shell
))
667 (void) sm_strlcpy(user
->mbdb_shell
, vals
[0],
668 sizeof(user
->mbdb_shell
));
671 else if (strcasecmp(attr
, "uidNumber") == 0)
675 if (!bitset(NEED_UID
, need
))
678 for (p
= vals
[0]; *p
!= '\0'; p
++)
680 /* allow negative numbers */
681 if (p
== vals
[0] && *p
== '-')
683 /* but not simply '-' */
684 if (*(p
+ 1) == '\0')
687 else if (!isascii(*p
) || !isdigit(*p
))
690 user
->mbdb_uid
= atoi(vals
[0]);
693 else if (strcasecmp(attr
, "gidNumber") == 0)
697 if (!bitset(NEED_GID
, need
))
700 for (p
= vals
[0]; *p
!= '\0'; p
++)
702 /* allow negative numbers */
703 if (p
== vals
[0] && *p
== '-')
705 /* but not simply '-' */
706 if (*(p
+ 1) == '\0')
709 else if (!isascii(*p
) || !isdigit(*p
))
712 user
->mbdb_gid
= atoi(vals
[0]);
717 ldap_value_free(vals
);
721 errno
= sm_ldap_geterrno(LDAPLMAP
.ldap_ld
);
724 ** We check errno != LDAP_DECODING_ERROR since
725 ** OpenLDAP 1.X has a very ugly *undocumented*
726 ** hack of returning this error code from
727 ** ldap_next_attribute() if the library freed the
728 ** ber attribute. See:
729 ** http://www.openldap.org/lists/openldap-devel/9901/msg00064.html
732 if (errno
!= LDAP_SUCCESS
&&
733 errno
!= LDAP_DECODING_ERROR
)
735 /* Must be an error */
748 if (LDAPLMAP
.ldap_res
!= NULL
)
750 ldap_msgfree(LDAPLMAP
.ldap_res
);
751 LDAPLMAP
.ldap_res
= NULL
;
757 (void) sm_strlcpy(user
->mbdb_name
, name
,
758 sizeof(user
->mbdb_name
));
772 ** MBDB_LDAP_TERMINATE -- terminate connection to the mailbox database
782 mbdb_ldap_terminate()
784 sm_ldap_close(&LDAPLMAP
);
786 # endif /* _LDAP_EXAMPLE_ */