/* * Copyright (c) 2001-2003,2009 Sendmail, Inc. and its suppliers. * All rights reserved. * * By using this file, you agree to the terms and conditions set * forth in the LICENSE file which can be found at the top level of * the sendmail distribution. */ #include SM_RCSID("@(#)$Id: mbdb.c,v 1.41 2009/06/19 22:02:26 guenther Exp $") #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include # ifdef EX_OK # undef EX_OK /* for SVr4.2 SMP */ # endif /* EX_OK */ #include #if LDAPMAP # if _LDAP_EXAMPLE_ # include # endif /* _LDAP_EXAMPLE_ */ #endif /* LDAPMAP */ typedef struct { char *mbdb_typename; int (*mbdb_initialize) __P((char *)); int (*mbdb_lookup) __P((char *name, SM_MBDB_T *user)); void (*mbdb_terminate) __P((void)); } SM_MBDB_TYPE_T; static int mbdb_pw_initialize __P((char *)); static int mbdb_pw_lookup __P((char *name, SM_MBDB_T *user)); static void mbdb_pw_terminate __P((void)); #if LDAPMAP # if _LDAP_EXAMPLE_ static struct sm_ldap_struct LDAPLMAP; static int mbdb_ldap_initialize __P((char *)); static int mbdb_ldap_lookup __P((char *name, SM_MBDB_T *user)); static void mbdb_ldap_terminate __P((void)); # endif /* _LDAP_EXAMPLE_ */ #endif /* LDAPMAP */ static SM_MBDB_TYPE_T SmMbdbTypes[] = { { "pw", mbdb_pw_initialize, mbdb_pw_lookup, mbdb_pw_terminate }, #if LDAPMAP # if _LDAP_EXAMPLE_ { "ldap", mbdb_ldap_initialize, mbdb_ldap_lookup, mbdb_ldap_terminate }, # endif /* _LDAP_EXAMPLE_ */ #endif /* LDAPMAP */ { NULL, NULL, NULL, NULL } }; static SM_MBDB_TYPE_T *SmMbdbType = &SmMbdbTypes[0]; /* ** SM_MBDB_INITIALIZE -- specify which mailbox database to use ** ** If this function is not called, then the "pw" implementation ** is used by default; this implementation uses getpwnam(). ** ** Parameters: ** mbdb -- Which mailbox database to use. ** The argument has the form "name" or "name.arg". ** "pw" means use getpwnam(). ** ** Results: ** EX_OK on success, or an EX_* code on failure. */ int sm_mbdb_initialize(mbdb) char *mbdb; { size_t namelen; int err; char *name; char *arg; SM_MBDB_TYPE_T *t; SM_REQUIRE(mbdb != NULL); name = mbdb; arg = strchr(mbdb, '.'); if (arg == NULL) namelen = strlen(name); else { namelen = arg - name; ++arg; } for (t = SmMbdbTypes; t->mbdb_typename != NULL; ++t) { if (strlen(t->mbdb_typename) == namelen && strncmp(name, t->mbdb_typename, namelen) == 0) { err = EX_OK; if (t->mbdb_initialize != NULL) err = t->mbdb_initialize(arg); if (err == EX_OK) SmMbdbType = t; return err; } } return EX_UNAVAILABLE; } /* ** SM_MBDB_TERMINATE -- terminate connection to the mailbox database ** ** Because this function closes any cached file descriptors that ** are being held open for the connection to the mailbox database, ** it should be called for security reasons prior to dropping privileges ** and execing another process. ** ** Parameters: ** none. ** ** Results: ** none. */ void sm_mbdb_terminate() { if (SmMbdbType->mbdb_terminate != NULL) SmMbdbType->mbdb_terminate(); } /* ** SM_MBDB_LOOKUP -- look up a local mail recipient, given name ** ** Parameters: ** name -- name of local mail recipient ** user -- pointer to structure to fill in on success ** ** Results: ** On success, fill in *user and return EX_OK. ** If the user does not exist, return EX_NOUSER. ** If a temporary failure (eg, a network failure) occurred, ** return EX_TEMPFAIL. Otherwise return EX_OSERR. */ int sm_mbdb_lookup(name, user) char *name; SM_MBDB_T *user; { int ret = EX_NOUSER; if (SmMbdbType->mbdb_lookup != NULL) ret = SmMbdbType->mbdb_lookup(name, user); return ret; } /* ** SM_MBDB_FROMPW -- copy from struct pw to SM_MBDB_T ** ** Parameters: ** user -- destination user information structure ** pw -- source passwd structure ** ** Results: ** none. */ void sm_mbdb_frompw(user, pw) SM_MBDB_T *user; struct passwd *pw; { SM_REQUIRE(user != NULL); (void) sm_strlcpy(user->mbdb_name, pw->pw_name, sizeof(user->mbdb_name)); user->mbdb_uid = pw->pw_uid; user->mbdb_gid = pw->pw_gid; sm_pwfullname(pw->pw_gecos, pw->pw_name, user->mbdb_fullname, sizeof(user->mbdb_fullname)); (void) sm_strlcpy(user->mbdb_homedir, pw->pw_dir, sizeof(user->mbdb_homedir)); (void) sm_strlcpy(user->mbdb_shell, pw->pw_shell, sizeof(user->mbdb_shell)); } /* ** SM_PWFULLNAME -- build full name of user from pw_gecos field. ** ** This routine interprets the strange entry that would appear ** in the GECOS field of the password file. ** ** Parameters: ** gecos -- name to build. ** user -- the login name of this user (for &). ** buf -- place to put the result. ** buflen -- length of buf. ** ** Returns: ** none. */ #if _FFR_HANDLE_ISO8859_GECOS static char Latin1ToASCII[128] = { 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 33, 99, 80, 36, 89, 124, 36, 34, 99, 97, 60, 45, 45, 114, 45, 111, 42, 50, 51, 39, 117, 80, 46, 44, 49, 111, 62, 42, 42, 42, 63, 65, 65, 65, 65, 65, 65, 65, 67, 69, 69, 69, 69, 73, 73, 73, 73, 68, 78, 79, 79, 79, 79, 79, 88, 79, 85, 85, 85, 85, 89, 80, 66, 97, 97, 97, 97, 97, 97, 97, 99, 101, 101, 101, 101, 105, 105, 105, 105, 100, 110, 111, 111, 111, 111, 111, 47, 111, 117, 117, 117, 117, 121, 112, 121 }; #endif /* _FFR_HANDLE_ISO8859_GECOS */ void sm_pwfullname(gecos, user, buf, buflen) register char *gecos; char *user; char *buf; size_t buflen; { register char *p; register char *bp = buf; if (*gecos == '*') gecos++; /* copy gecos, interpolating & to be full name */ for (p = gecos; *p != '\0' && *p != ',' && *p != ';' && *p != '%'; p++) { if (bp >= &buf[buflen - 1]) { /* buffer overflow -- just use login name */ (void) sm_strlcpy(buf, user, buflen); return; } if (*p == '&') { /* interpolate full name */ (void) sm_strlcpy(bp, user, buflen - (bp - buf)); *bp = toupper(*bp); bp += strlen(bp); } else { #if _FFR_HANDLE_ISO8859_GECOS if ((unsigned char) *p >= 128) *bp++ = Latin1ToASCII[(unsigned char) *p - 128]; else #endif /* _FFR_HANDLE_ISO8859_GECOS */ *bp++ = *p; } } *bp = '\0'; } /* ** /etc/passwd implementation. */ /* ** MBDB_PW_INITIALIZE -- initialize getpwnam() version ** ** Parameters: ** arg -- unused. ** ** Results: ** EX_OK. */ /* ARGSUSED0 */ static int mbdb_pw_initialize(arg) char *arg; { return EX_OK; } /* ** MBDB_PW_LOOKUP -- look up a local mail recipient, given name ** ** Parameters: ** name -- name of local mail recipient ** user -- pointer to structure to fill in on success ** ** Results: ** On success, fill in *user and return EX_OK. ** Failure: EX_NOUSER. */ static int mbdb_pw_lookup(name, user) char *name; SM_MBDB_T *user; { struct passwd *pw; #ifdef HESIOD /* DEC Hesiod getpwnam accepts numeric strings -- short circuit it */ { char *p; for (p = name; *p != '\0'; p++) if (!isascii(*p) || !isdigit(*p)) break; if (*p == '\0') return EX_NOUSER; } #endif /* HESIOD */ errno = 0; pw = getpwnam(name); if (pw == NULL) { #if 0 /* ** getpwnam() isn't advertised as setting errno. ** In fact, under FreeBSD, non-root getpwnam() on ** non-existant users returns NULL with errno = EPERM. ** This test won't work. */ switch (errno) { case 0: return EX_NOUSER; case EIO: return EX_OSERR; default: return EX_TEMPFAIL; } #endif /* 0 */ return EX_NOUSER; } sm_mbdb_frompw(user, pw); return EX_OK; } /* ** MBDB_PW_TERMINATE -- terminate connection to the mailbox database ** ** Parameters: ** none. ** ** Results: ** none. */ static void mbdb_pw_terminate() { endpwent(); } #if LDAPMAP # if _LDAP_EXAMPLE_ /* ** LDAP example implementation based on RFC 2307, "An Approach for Using ** LDAP as a Network Information Service": ** ** ( nisSchema.1.0 NAME 'uidNumber' ** DESC 'An integer uniquely identifying a user in an ** administrative domain' ** EQUALITY integerMatch SYNTAX 'INTEGER' SINGLE-VALUE ) ** ** ( nisSchema.1.1 NAME 'gidNumber' ** DESC 'An integer uniquely identifying a group in an ** administrative domain' ** EQUALITY integerMatch SYNTAX 'INTEGER' SINGLE-VALUE ) ** ** ( nisSchema.1.2 NAME 'gecos' ** DESC 'The GECOS field; the common name' ** EQUALITY caseIgnoreIA5Match ** SUBSTRINGS caseIgnoreIA5SubstringsMatch ** SYNTAX 'IA5String' SINGLE-VALUE ) ** ** ( nisSchema.1.3 NAME 'homeDirectory' ** DESC 'The absolute path to the home directory' ** EQUALITY caseExactIA5Match ** SYNTAX 'IA5String' SINGLE-VALUE ) ** ** ( nisSchema.1.4 NAME 'loginShell' ** DESC 'The path to the login shell' ** EQUALITY caseExactIA5Match ** SYNTAX 'IA5String' SINGLE-VALUE ) ** ** ( nisSchema.2.0 NAME 'posixAccount' SUP top AUXILIARY ** DESC 'Abstraction of an account with POSIX attributes' ** MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory ) ** MAY ( userPassword $ loginShell $ gecos $ description ) ) ** */ # define MBDB_LDAP_LABEL "MailboxDatabase" # ifndef MBDB_LDAP_FILTER # define MBDB_LDAP_FILTER "(&(objectClass=posixAccount)(uid=%0))" # endif /* MBDB_LDAP_FILTER */ # ifndef MBDB_DEFAULT_LDAP_BASEDN # define MBDB_DEFAULT_LDAP_BASEDN NULL # endif /* MBDB_DEFAULT_LDAP_BASEDN */ # ifndef MBDB_DEFAULT_LDAP_SERVER # define MBDB_DEFAULT_LDAP_SERVER NULL # endif /* MBDB_DEFAULT_LDAP_SERVER */ /* ** MBDB_LDAP_INITIALIZE -- initialize LDAP version ** ** Parameters: ** arg -- LDAP specification ** ** Results: ** EX_OK on success, or an EX_* code on failure. */ static int mbdb_ldap_initialize(arg) char *arg; { sm_ldap_clear(&LDAPLMAP); LDAPLMAP.ldap_base = MBDB_DEFAULT_LDAP_BASEDN; LDAPLMAP.ldap_host = MBDB_DEFAULT_LDAP_SERVER; LDAPLMAP.ldap_filter = MBDB_LDAP_FILTER; /* Only want one match */ LDAPLMAP.ldap_sizelimit = 1; /* interpolate new ldap_base and ldap_host from arg if given */ if (arg != NULL && *arg != '\0') { char *new; char *sep; size_t len; len = strlen(arg) + 1; new = sm_malloc(len); if (new == NULL) return EX_TEMPFAIL; (void) sm_strlcpy(new, arg, len); sep = strrchr(new, '@'); if (sep != NULL) { *sep++ = '\0'; LDAPLMAP.ldap_host = sep; } LDAPLMAP.ldap_base = new; } return EX_OK; } /* ** MBDB_LDAP_LOOKUP -- look up a local mail recipient, given name ** ** Parameters: ** name -- name of local mail recipient ** user -- pointer to structure to fill in on success ** ** Results: ** On success, fill in *user and return EX_OK. ** Failure: EX_NOUSER. */ #define NEED_FULLNAME 0x01 #define NEED_HOMEDIR 0x02 #define NEED_SHELL 0x04 #define NEED_UID 0x08 #define NEED_GID 0x10 static int mbdb_ldap_lookup(name, user) char *name; SM_MBDB_T *user; { int msgid; int need; int ret; int save_errno; LDAPMessage *entry; BerElement *ber; char *attr = NULL; if (strlen(name) >= sizeof(user->mbdb_name)) { errno = EINVAL; return EX_NOUSER; } if (LDAPLMAP.ldap_filter == NULL) { /* map not initialized, but don't have arg here */ errno = EFAULT; return EX_TEMPFAIL; } if (LDAPLMAP.ldap_pid != getpid()) { /* re-open map in this child process */ LDAPLMAP.ldap_ld = NULL; } if (LDAPLMAP.ldap_ld == NULL) { /* map not open, try to open now */ if (!sm_ldap_start(MBDB_LDAP_LABEL, &LDAPLMAP)) return EX_TEMPFAIL; } sm_ldap_setopts(LDAPLMAP.ldap_ld, &LDAPLMAP); msgid = sm_ldap_search(&LDAPLMAP, name); if (msgid == -1) { save_errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld) + E_LDAPBASE; # ifdef LDAP_SERVER_DOWN if (errno == LDAP_SERVER_DOWN) { /* server disappeared, try reopen on next search */ sm_ldap_close(&LDAPLMAP); } # endif /* LDAP_SERVER_DOWN */ errno = save_errno; return EX_TEMPFAIL; } /* Get results */ ret = ldap_result(LDAPLMAP.ldap_ld, msgid, 1, (LDAPLMAP.ldap_timeout.tv_sec == 0 ? NULL : &(LDAPLMAP.ldap_timeout)), &(LDAPLMAP.ldap_res)); if (ret != LDAP_RES_SEARCH_RESULT && ret != LDAP_RES_SEARCH_ENTRY) { if (ret == 0) errno = ETIMEDOUT; else errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld); ret = EX_TEMPFAIL; goto abort; } entry = ldap_first_entry(LDAPLMAP.ldap_ld, LDAPLMAP.ldap_res); if (entry == NULL) { int rc; /* ** We may have gotten an LDAP_RES_SEARCH_RESULT response ** with an error inside it, so we have to extract that ** with ldap_parse_result(). This can happen when talking ** to an LDAP proxy whose backend has gone down. */ save_errno = ldap_parse_result(LDAPLMAP.ldap_ld, LDAPLMAP.ldap_res, &rc, NULL, NULL, NULL, NULL, 0); if (save_errno == LDAP_SUCCESS) save_errno = rc; if (save_errno == LDAP_SUCCESS) { errno = ENOENT; ret = EX_NOUSER; } else { errno = save_errno; ret = EX_TEMPFAIL; } goto abort; } # if !defined(LDAP_VERSION_MAX) && !defined(LDAP_OPT_SIZELIMIT) /* ** Reset value to prevent lingering ** LDAP_DECODING_ERROR due to ** OpenLDAP 1.X's hack (see below) */ LDAPLMAP.ldap_ld->ld_errno = LDAP_SUCCESS; # endif /* !defined(LDAP_VERSION_MAX) !defined(LDAP_OPT_SIZELIMIT) */ ret = EX_OK; need = NEED_FULLNAME|NEED_HOMEDIR|NEED_SHELL|NEED_UID|NEED_GID; for (attr = ldap_first_attribute(LDAPLMAP.ldap_ld, entry, &ber); attr != NULL; attr = ldap_next_attribute(LDAPLMAP.ldap_ld, entry, ber)) { char **vals; vals = ldap_get_values(LDAPLMAP.ldap_ld, entry, attr); if (vals == NULL) { errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld); if (errno == LDAP_SUCCESS) { ldap_memfree(attr); continue; } /* Must be an error */ errno += E_LDAPBASE; ret = EX_TEMPFAIL; goto abort; } # if !defined(LDAP_VERSION_MAX) && !defined(LDAP_OPT_SIZELIMIT) /* ** Reset value to prevent lingering ** LDAP_DECODING_ERROR due to ** OpenLDAP 1.X's hack (see below) */ LDAPLMAP.ldap_ld->ld_errno = LDAP_SUCCESS; # endif /* !defined(LDAP_VERSION_MAX) !defined(LDAP_OPT_SIZELIMIT) */ if (vals[0] == NULL || vals[0][0] == '\0') goto skip; if (strcasecmp(attr, "gecos") == 0) { if (!bitset(NEED_FULLNAME, need) || strlen(vals[0]) >= sizeof(user->mbdb_fullname)) goto skip; sm_pwfullname(vals[0], name, user->mbdb_fullname, sizeof(user->mbdb_fullname)); need &= ~NEED_FULLNAME; } else if (strcasecmp(attr, "homeDirectory") == 0) { if (!bitset(NEED_HOMEDIR, need) || strlen(vals[0]) >= sizeof(user->mbdb_homedir)) goto skip; (void) sm_strlcpy(user->mbdb_homedir, vals[0], sizeof(user->mbdb_homedir)); need &= ~NEED_HOMEDIR; } else if (strcasecmp(attr, "loginShell") == 0) { if (!bitset(NEED_SHELL, need) || strlen(vals[0]) >= sizeof(user->mbdb_shell)) goto skip; (void) sm_strlcpy(user->mbdb_shell, vals[0], sizeof(user->mbdb_shell)); need &= ~NEED_SHELL; } else if (strcasecmp(attr, "uidNumber") == 0) { char *p; if (!bitset(NEED_UID, need)) goto skip; for (p = vals[0]; *p != '\0'; p++) { /* allow negative numbers */ if (p == vals[0] && *p == '-') { /* but not simply '-' */ if (*(p + 1) == '\0') goto skip; } else if (!isascii(*p) || !isdigit(*p)) goto skip; } user->mbdb_uid = atoi(vals[0]); need &= ~NEED_UID; } else if (strcasecmp(attr, "gidNumber") == 0) { char *p; if (!bitset(NEED_GID, need)) goto skip; for (p = vals[0]; *p != '\0'; p++) { /* allow negative numbers */ if (p == vals[0] && *p == '-') { /* but not simply '-' */ if (*(p + 1) == '\0') goto skip; } else if (!isascii(*p) || !isdigit(*p)) goto skip; } user->mbdb_gid = atoi(vals[0]); need &= ~NEED_GID; } skip: ldap_value_free(vals); ldap_memfree(attr); } errno = sm_ldap_geterrno(LDAPLMAP.ldap_ld); /* ** We check errno != LDAP_DECODING_ERROR since ** OpenLDAP 1.X has a very ugly *undocumented* ** hack of returning this error code from ** ldap_next_attribute() if the library freed the ** ber attribute. See: ** http://www.openldap.org/lists/openldap-devel/9901/msg00064.html */ if (errno != LDAP_SUCCESS && errno != LDAP_DECODING_ERROR) { /* Must be an error */ errno += E_LDAPBASE; ret = EX_TEMPFAIL; goto abort; } abort: save_errno = errno; if (attr != NULL) { ldap_memfree(attr); attr = NULL; } if (LDAPLMAP.ldap_res != NULL) { ldap_msgfree(LDAPLMAP.ldap_res); LDAPLMAP.ldap_res = NULL; } if (ret == EX_OK) { if (need == 0) { (void) sm_strlcpy(user->mbdb_name, name, sizeof(user->mbdb_name)); save_errno = 0; } else { ret = EX_NOUSER; save_errno = EINVAL; } } errno = save_errno; return ret; } /* ** MBDB_LDAP_TERMINATE -- terminate connection to the mailbox database ** ** Parameters: ** none. ** ** Results: ** none. */ static void mbdb_ldap_terminate() { sm_ldap_close(&LDAPLMAP); } # endif /* _LDAP_EXAMPLE_ */ #endif /* LDAPMAP */