diff options
Diffstat (limited to 'sendmail/src/udb.c')
-rw-r--r-- | sendmail/src/udb.c | 1314 |
1 files changed, 1314 insertions, 0 deletions
diff --git a/sendmail/src/udb.c b/sendmail/src/udb.c new file mode 100644 index 0000000..29470e7 --- /dev/null +++ b/sendmail/src/udb.c @@ -0,0 +1,1314 @@ +/* + * Copyright (c) 1998-2003, 2006 Sendmail, Inc. and its suppliers. + * All rights reserved. + * Copyright (c) 1983, 1995-1997 Eric P. Allman. All rights reserved. + * Copyright (c) 1988, 1993 + * The Regents of the University of California. 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 <sendmail.h> +#include "map.h" + +#if USERDB +SM_RCSID("@(#)$Id: udb.c,v 8.164 2006/12/19 19:49:51 ca Exp $ (with USERDB)") +#else /* USERDB */ +SM_RCSID("@(#)$Id: udb.c,v 8.164 2006/12/19 19:49:51 ca Exp $ (without USERDB)") +#endif /* USERDB */ + +#if USERDB + +#include <sm/sendmail.h> +# if NEWDB +# include "sm/bdb.h" +# else /* NEWDB */ +# define DBT struct _data_base_thang_ +DBT +{ + void *data; /* pointer to data */ + size_t size; /* length of data */ +}; +# endif /* NEWDB */ + +/* +** UDB.C -- interface between sendmail and Berkeley User Data Base. +** +** This depends on the 4.4BSD db package. +*/ + + +struct udbent +{ + char *udb_spec; /* string version of spec */ + int udb_type; /* type of entry */ + pid_t udb_pid; /* PID of process which opened db */ + char *udb_default; /* default host for outgoing mail */ + union + { +# if NETINET || NETINET6 + /* type UE_REMOTE -- do remote call for lookup */ + struct + { + SOCKADDR _udb_addr; /* address */ + int _udb_timeout; /* timeout */ + } udb_remote; +# define udb_addr udb_u.udb_remote._udb_addr +# define udb_timeout udb_u.udb_remote._udb_timeout +# endif /* NETINET || NETINET6 */ + + /* type UE_FORWARD -- forward message to remote */ + struct + { + char *_udb_fwdhost; /* name of forward host */ + } udb_forward; +# define udb_fwdhost udb_u.udb_forward._udb_fwdhost + +# if NEWDB + /* type UE_FETCH -- lookup in local database */ + struct + { + char *_udb_dbname; /* pathname of database */ + DB *_udb_dbp; /* open database ptr */ + } udb_lookup; +# define udb_dbname udb_u.udb_lookup._udb_dbname +# define udb_dbp udb_u.udb_lookup._udb_dbp +# endif /* NEWDB */ + } udb_u; +}; + +# define UDB_EOLIST 0 /* end of list */ +# define UDB_SKIP 1 /* skip this entry */ +# define UDB_REMOTE 2 /* look up in remote database */ +# define UDB_DBFETCH 3 /* look up in local database */ +# define UDB_FORWARD 4 /* forward to remote host */ +# define UDB_HESIOD 5 /* look up via hesiod */ + +# define MAXUDBENT 10 /* maximum number of UDB entries */ + + +struct udb_option +{ + char *udbo_name; + char *udbo_val; +}; + +# if HESIOD +static int hes_udb_get __P((DBT *, DBT *)); +# endif /* HESIOD */ +static char *udbmatch __P((char *, char *, SM_RPOOL_T *)); +static int _udbx_init __P((ENVELOPE *)); +static int _udb_parsespec __P((char *, struct udb_option [], int)); + +/* +** UDBEXPAND -- look up user in database and expand +** +** Parameters: +** a -- address to expand. +** sendq -- pointer to head of sendq to put the expansions in. +** aliaslevel -- the current alias nesting depth. +** e -- the current envelope. +** +** Returns: +** EX_TEMPFAIL -- if something "odd" happened -- probably due +** to accessing a file on an NFS server that is down. +** EX_OK -- otherwise. +** +** Side Effects: +** Modifies sendq. +*/ + +static struct udbent UdbEnts[MAXUDBENT + 1]; +static bool UdbInitialized = false; + +int +udbexpand(a, sendq, aliaslevel, e) + register ADDRESS *a; + ADDRESS **sendq; + int aliaslevel; + register ENVELOPE *e; +{ + int i; + DBT key; + DBT info; + bool breakout; + register struct udbent *up; + int keylen; + int naddrs; + char *user; + char keybuf[MAXUDBKEY]; + + memset(&key, '\0', sizeof(key)); + memset(&info, '\0', sizeof(info)); + + if (tTd(28, 1)) + sm_dprintf("udbexpand(%s)\n", a->q_paddr); + + /* make certain we are supposed to send to this address */ + if (!QS_IS_SENDABLE(a->q_state)) + return EX_OK; + e->e_to = a->q_paddr; + + /* on first call, locate the database */ + if (!UdbInitialized) + { + if (_udbx_init(e) == EX_TEMPFAIL) + return EX_TEMPFAIL; + } + + /* short circuit the process if no chance of a match */ + if (UdbSpec == NULL || UdbSpec[0] == '\0') + return EX_OK; + + /* extract user to do userdb matching on */ + user = a->q_user; + + /* short circuit name begins with '\\' since it can't possibly match */ + /* (might want to treat this as unquoted instead) */ + if (user[0] == '\\') + return EX_OK; + + /* if name begins with a colon, it indicates our metadata */ + if (user[0] == ':') + return EX_OK; + + keylen = sm_strlcpyn(keybuf, sizeof(keybuf), 2, user, ":maildrop"); + + /* if name is too long, assume it won't match */ + if (keylen >= sizeof(keybuf)) + return EX_OK; + + /* build actual database key */ + + breakout = false; + for (up = UdbEnts; !breakout; up++) + { + int usersize; + int userleft; + char userbuf[MEMCHUNKSIZE]; +# if HESIOD && HES_GETMAILHOST + char pobuf[MAXNAME]; +# endif /* HESIOD && HES_GETMAILHOST */ +# if defined(NEWDB) && DB_VERSION_MAJOR > 1 + DBC *dbc = NULL; +# endif /* defined(NEWDB) && DB_VERSION_MAJOR > 1 */ + + user = userbuf; + userbuf[0] = '\0'; + usersize = sizeof(userbuf); + userleft = sizeof(userbuf) - 1; + + /* + ** Select action based on entry type. + ** + ** On dropping out of this switch, "class" should + ** explain the type of the data, and "user" should + ** contain the user information. + */ + + switch (up->udb_type) + { +# if NEWDB + case UDB_DBFETCH: + key.data = keybuf; + key.size = keylen; + if (tTd(28, 80)) + sm_dprintf("udbexpand: trying %s (%d) via db\n", + keybuf, keylen); +# if DB_VERSION_MAJOR < 2 + i = (*up->udb_dbp->seq)(up->udb_dbp, &key, &info, R_CURSOR); +# else /* DB_VERSION_MAJOR < 2 */ + i = 0; + if (dbc == NULL && +# if DB_VERSION_MAJOR > 2 || DB_VERSION_MINOR >= 6 + (errno = (*up->udb_dbp->cursor)(up->udb_dbp, + NULL, &dbc, 0)) != 0) +# else /* DB_VERSION_MAJOR > 2 || DB_VERSION_MINOR >= 6 */ + (errno = (*up->udb_dbp->cursor)(up->udb_dbp, + NULL, &dbc)) != 0) +# endif /* DB_VERSION_MAJOR > 2 || DB_VERSION_MINOR >= 6 */ + i = -1; + if (i != 0 || dbc == NULL || + (errno = dbc->c_get(dbc, &key, + &info, DB_SET)) != 0) + i = 1; +# endif /* DB_VERSION_MAJOR < 2 */ + if (i > 0 || info.size <= 0) + { + if (tTd(28, 2)) + sm_dprintf("udbexpand: no match on %s (%d)\n", + keybuf, keylen); +# if DB_VERSION_MAJOR > 1 + if (dbc != NULL) + { + (void) dbc->c_close(dbc); + dbc = NULL; + } +# endif /* DB_VERSION_MAJOR > 1 */ + break; + } + if (tTd(28, 80)) + sm_dprintf("udbexpand: match %.*s: %.*s\n", + (int) key.size, (char *) key.data, + (int) info.size, (char *) info.data); + + a->q_flags &= ~QSELFREF; + while (i == 0 && key.size == keylen && + memcmp(key.data, keybuf, keylen) == 0) + { + char *p; + + if (bitset(EF_VRFYONLY, e->e_flags)) + { + a->q_state = QS_VERIFIED; +# if DB_VERSION_MAJOR > 1 + if (dbc != NULL) + { + (void) dbc->c_close(dbc); + dbc = NULL; + } +# endif /* DB_VERSION_MAJOR > 1 */ + return EX_OK; + } + + breakout = true; + if (info.size >= userleft - 1) + { + char *nuser; + int size = MEMCHUNKSIZE; + + if (info.size > MEMCHUNKSIZE) + size = info.size; + nuser = sm_malloc_x(usersize + size); + + memmove(nuser, user, usersize); + if (user != userbuf) + sm_free(user); /* XXX */ + user = nuser; + usersize += size; + userleft += size; + } + p = &user[strlen(user)]; + if (p != user) + { + *p++ = ','; + userleft--; + } + memmove(p, info.data, info.size); + p[info.size] = '\0'; + userleft -= info.size; + + /* get the next record */ +# if DB_VERSION_MAJOR < 2 + i = (*up->udb_dbp->seq)(up->udb_dbp, &key, &info, R_NEXT); +# else /* DB_VERSION_MAJOR < 2 */ + i = 0; + if ((errno = dbc->c_get(dbc, &key, + &info, DB_NEXT)) != 0) + i = 1; +# endif /* DB_VERSION_MAJOR < 2 */ + } + +# if DB_VERSION_MAJOR > 1 + if (dbc != NULL) + { + (void) dbc->c_close(dbc); + dbc = NULL; + } +# endif /* DB_VERSION_MAJOR > 1 */ + + /* if nothing ever matched, try next database */ + if (!breakout) + break; + + message("expanded to %s", user); + if (LogLevel > 10) + sm_syslog(LOG_INFO, e->e_id, + "expand %.100s => %s", + e->e_to, + shortenstring(user, MAXSHORTSTR)); + naddrs = sendtolist(user, a, sendq, aliaslevel + 1, e); + if (naddrs > 0 && !bitset(QSELFREF, a->q_flags)) + { + if (tTd(28, 5)) + { + sm_dprintf("udbexpand: QS_EXPANDED "); + printaddr(sm_debug_file(), a, false); + } + a->q_state = QS_EXPANDED; + } + if (i < 0) + { + syserr("udbexpand: db-get %.*s stat %d", + (int) key.size, (char *) key.data, i); + return EX_TEMPFAIL; + } + + /* + ** If this address has a -request address, reflect + ** it into the envelope. + */ + + memset(&key, '\0', sizeof(key)); + memset(&info, '\0', sizeof(info)); + (void) sm_strlcpyn(keybuf, sizeof(keybuf), 2, a->q_user, + ":mailsender"); + keylen = strlen(keybuf); + key.data = keybuf; + key.size = keylen; + +# if DB_VERSION_MAJOR < 2 + i = (*up->udb_dbp->get)(up->udb_dbp, &key, &info, 0); +# else /* DB_VERSION_MAJOR < 2 */ + i = errno = (*up->udb_dbp->get)(up->udb_dbp, NULL, + &key, &info, 0); +# endif /* DB_VERSION_MAJOR < 2 */ + if (i != 0 || info.size <= 0) + break; + a->q_owner = sm_rpool_malloc_x(e->e_rpool, + info.size + 1); + memmove(a->q_owner, info.data, info.size); + a->q_owner[info.size] = '\0'; + + /* announce delivery; NORECEIPT bit set later */ + if (e->e_xfp != NULL) + { + (void) sm_io_fprintf(e->e_xfp, SM_TIME_DEFAULT, + "Message delivered to mailing list %s\n", + a->q_paddr); + } + e->e_flags |= EF_SENDRECEIPT; + a->q_flags |= QDELIVERED|QEXPANDED; + break; +# endif /* NEWDB */ + +# if HESIOD + case UDB_HESIOD: + key.data = keybuf; + key.size = keylen; + if (tTd(28, 80)) + sm_dprintf("udbexpand: trying %s (%d) via hesiod\n", + keybuf, keylen); + /* look up the key via hesiod */ + i = hes_udb_get(&key, &info); + if (i < 0) + { + syserr("udbexpand: hesiod-get %.*s stat %d", + (int) key.size, (char *) key.data, i); + return EX_TEMPFAIL; + } + else if (i > 0 || info.size <= 0) + { +# if HES_GETMAILHOST + struct hes_postoffice *hp; +# endif /* HES_GETMAILHOST */ + + if (tTd(28, 2)) + sm_dprintf("udbexpand: no match on %s (%d)\n", + (char *) keybuf, (int) keylen); +# if HES_GETMAILHOST + if (tTd(28, 8)) + sm_dprintf(" ... trying hes_getmailhost(%s)\n", + a->q_user); + hp = hes_getmailhost(a->q_user); + if (hp == NULL) + { + if (hes_error() == HES_ER_NET) + { + syserr("udbexpand: hesiod-getmail %s stat %d", + a->q_user, hes_error()); + return EX_TEMPFAIL; + } + if (tTd(28, 2)) + sm_dprintf("hes_getmailhost(%s): %d\n", + a->q_user, hes_error()); + break; + } + if (strlen(hp->po_name) + strlen(hp->po_host) > + sizeof(pobuf) - 2) + { + if (tTd(28, 2)) + sm_dprintf("hes_getmailhost(%s): expansion too long: %.30s@%.30s\n", + a->q_user, + hp->po_name, + hp->po_host); + break; + } + info.data = pobuf; + (void) sm_snprintf(pobuf, sizeof(pobuf), + "%s@%s", hp->po_name, hp->po_host); + info.size = strlen(info.data); +# else /* HES_GETMAILHOST */ + break; +# endif /* HES_GETMAILHOST */ + } + if (tTd(28, 80)) + sm_dprintf("udbexpand: match %.*s: %.*s\n", + (int) key.size, (char *) key.data, + (int) info.size, (char *) info.data); + a->q_flags &= ~QSELFREF; + + if (bitset(EF_VRFYONLY, e->e_flags)) + { + a->q_state = QS_VERIFIED; + return EX_OK; + } + + breakout = true; + if (info.size >= usersize) + user = sm_malloc_x(info.size + 1); + memmove(user, info.data, info.size); + user[info.size] = '\0'; + + message("hesioded to %s", user); + if (LogLevel > 10) + sm_syslog(LOG_INFO, e->e_id, + "hesiod %.100s => %s", + e->e_to, + shortenstring(user, MAXSHORTSTR)); + naddrs = sendtolist(user, a, sendq, aliaslevel + 1, e); + + if (naddrs > 0 && !bitset(QSELFREF, a->q_flags)) + { + if (tTd(28, 5)) + { + sm_dprintf("udbexpand: QS_EXPANDED "); + printaddr(sm_debug_file(), a, false); + } + a->q_state = QS_EXPANDED; + } + + /* + ** If this address has a -request address, reflect + ** it into the envelope. + */ + + (void) sm_strlcpyn(keybuf, sizeof(keybuf), 2, a->q_user, + ":mailsender"); + keylen = strlen(keybuf); + key.data = keybuf; + key.size = keylen; + i = hes_udb_get(&key, &info); + if (i != 0 || info.size <= 0) + break; + a->q_owner = sm_rpool_malloc_x(e->e_rpool, + info.size + 1); + memmove(a->q_owner, info.data, info.size); + a->q_owner[info.size] = '\0'; + break; +# endif /* HESIOD */ + + case UDB_REMOTE: + /* not yet implemented */ + break; + + case UDB_FORWARD: + if (bitset(EF_VRFYONLY, e->e_flags)) + { + a->q_state = QS_VERIFIED; + return EX_OK; + } + i = strlen(up->udb_fwdhost) + strlen(a->q_user) + 1; + if (i >= usersize) + { + usersize = i + 1; + user = sm_malloc_x(usersize); + } + (void) sm_strlcpyn(user, usersize, 3, + a->q_user, "@", up->udb_fwdhost); + message("expanded to %s", user); + a->q_flags &= ~QSELFREF; + naddrs = sendtolist(user, a, sendq, aliaslevel + 1, e); + if (naddrs > 0 && !bitset(QSELFREF, a->q_flags)) + { + if (tTd(28, 5)) + { + sm_dprintf("udbexpand: QS_EXPANDED "); + printaddr(sm_debug_file(), a, false); + } + a->q_state = QS_EXPANDED; + } + breakout = true; + break; + + case UDB_EOLIST: + breakout = true; + break; + + default: + /* unknown entry type */ + break; + } + /* XXX if an exception occurs, there is a storage leak */ + if (user != userbuf) + sm_free(user); /* XXX */ + } + return EX_OK; +} +/* +** UDBSENDER -- return canonical external name of sender, given local name +** +** Parameters: +** sender -- the name of the sender on the local machine. +** rpool -- resource pool from which to allocate result +** +** Returns: +** The external name for this sender, if derivable from the +** database. Storage allocated from rpool. +** NULL -- if nothing is changed from the database. +** +** Side Effects: +** none. +*/ + +char * +udbsender(sender, rpool) + char *sender; + SM_RPOOL_T *rpool; +{ + return udbmatch(sender, "mailname", rpool); +} +/* +** UDBMATCH -- match user in field, return result of lookup. +** +** Parameters: +** user -- the name of the user. +** field -- the field to lookup. +** rpool -- resource pool from which to allocate result +** +** Returns: +** The external name for this sender, if derivable from the +** database. Storage allocated from rpool. +** NULL -- if nothing is changed from the database. +** +** Side Effects: +** none. +*/ + +static char * +udbmatch(user, field, rpool) + char *user; + char *field; + SM_RPOOL_T *rpool; +{ + register char *p; + register struct udbent *up; + int i; + int keylen; + DBT key, info; + char keybuf[MAXUDBKEY]; + + if (tTd(28, 1)) + sm_dprintf("udbmatch(%s, %s)\n", user, field); + + if (!UdbInitialized) + { + if (_udbx_init(CurEnv) == EX_TEMPFAIL) + return NULL; + } + + /* short circuit if no spec */ + if (UdbSpec == NULL || UdbSpec[0] == '\0') + return NULL; + + /* short circuit name begins with '\\' since it can't possibly match */ + if (user[0] == '\\') + return NULL; + + /* long names can never match and are a pain to deal with */ + i = strlen(field); + if (i < sizeof("maildrop")) + i = sizeof("maildrop"); + if ((strlen(user) + i) > sizeof(keybuf) - 4) + return NULL; + + /* names beginning with colons indicate metadata */ + if (user[0] == ':') + return NULL; + + /* build database key */ + (void) sm_strlcpyn(keybuf, sizeof(keybuf), 3, user, ":", field); + keylen = strlen(keybuf); + + for (up = UdbEnts; up->udb_type != UDB_EOLIST; up++) + { + /* + ** Select action based on entry type. + */ + + switch (up->udb_type) + { +# if NEWDB + case UDB_DBFETCH: + memset(&key, '\0', sizeof(key)); + memset(&info, '\0', sizeof(info)); + key.data = keybuf; + key.size = keylen; +# if DB_VERSION_MAJOR < 2 + i = (*up->udb_dbp->get)(up->udb_dbp, &key, &info, 0); +# else /* DB_VERSION_MAJOR < 2 */ + i = errno = (*up->udb_dbp->get)(up->udb_dbp, NULL, + &key, &info, 0); +# endif /* DB_VERSION_MAJOR < 2 */ + if (i != 0 || info.size <= 0) + { + if (tTd(28, 2)) + sm_dprintf("udbmatch: no match on %s (%d) via db\n", + keybuf, keylen); + continue; + } + + p = sm_rpool_malloc_x(rpool, info.size + 1); + memmove(p, info.data, info.size); + p[info.size] = '\0'; + if (tTd(28, 1)) + sm_dprintf("udbmatch ==> %s\n", p); + return p; +# endif /* NEWDB */ + +# if HESIOD + case UDB_HESIOD: + key.data = keybuf; + key.size = keylen; + i = hes_udb_get(&key, &info); + if (i != 0 || info.size <= 0) + { + if (tTd(28, 2)) + sm_dprintf("udbmatch: no match on %s (%d) via hesiod\n", + keybuf, keylen); + continue; + } + + p = sm_rpool_malloc_x(rpool, info.size + 1); + memmove(p, info.data, info.size); + p[info.size] = '\0'; + if (tTd(28, 1)) + sm_dprintf("udbmatch ==> %s\n", p); + return p; +# endif /* HESIOD */ + } + } + + if (strcmp(field, "mailname") != 0) + return NULL; + + /* + ** Nothing yet. Search again for a default case. But only + ** use it if we also have a forward (:maildrop) pointer already + ** in the database. + */ + + /* build database key */ + (void) sm_strlcpyn(keybuf, sizeof(keybuf), 2, user, ":maildrop"); + keylen = strlen(keybuf); + + for (up = UdbEnts; up->udb_type != UDB_EOLIST; up++) + { + switch (up->udb_type) + { +# if NEWDB + case UDB_DBFETCH: + /* get the default case for this database */ + if (up->udb_default == NULL) + { + memset(&key, '\0', sizeof(key)); + memset(&info, '\0', sizeof(info)); + key.data = ":default:mailname"; + key.size = strlen(key.data); +# if DB_VERSION_MAJOR < 2 + i = (*up->udb_dbp->get)(up->udb_dbp, + &key, &info, 0); +# else /* DB_VERSION_MAJOR < 2 */ + i = errno = (*up->udb_dbp->get)(up->udb_dbp, + NULL, &key, + &info, 0); +# endif /* DB_VERSION_MAJOR < 2 */ + if (i != 0 || info.size <= 0) + { + /* no default case */ + up->udb_default = ""; + continue; + } + + /* save the default case */ + up->udb_default = sm_pmalloc_x(info.size + 1); + memmove(up->udb_default, info.data, info.size); + up->udb_default[info.size] = '\0'; + } + else if (up->udb_default[0] == '\0') + continue; + + /* we have a default case -- verify user:maildrop */ + memset(&key, '\0', sizeof(key)); + memset(&info, '\0', sizeof(info)); + key.data = keybuf; + key.size = keylen; +# if DB_VERSION_MAJOR < 2 + i = (*up->udb_dbp->get)(up->udb_dbp, &key, &info, 0); +# else /* DB_VERSION_MAJOR < 2 */ + i = errno = (*up->udb_dbp->get)(up->udb_dbp, NULL, + &key, &info, 0); +# endif /* DB_VERSION_MAJOR < 2 */ + if (i != 0 || info.size <= 0) + { + /* nope -- no aliasing for this user */ + continue; + } + + /* they exist -- build the actual address */ + i = strlen(user) + strlen(up->udb_default) + 2; + p = sm_rpool_malloc_x(rpool, i); + (void) sm_strlcpyn(p, i, 3, user, "@", up->udb_default); + if (tTd(28, 1)) + sm_dprintf("udbmatch ==> %s\n", p); + return p; +# endif /* NEWDB */ + +# if HESIOD + case UDB_HESIOD: + /* get the default case for this database */ + if (up->udb_default == NULL) + { + key.data = ":default:mailname"; + key.size = strlen(key.data); + i = hes_udb_get(&key, &info); + + if (i != 0 || info.size <= 0) + { + /* no default case */ + up->udb_default = ""; + continue; + } + + /* save the default case */ + up->udb_default = sm_pmalloc_x(info.size + 1); + memmove(up->udb_default, info.data, info.size); + up->udb_default[info.size] = '\0'; + } + else if (up->udb_default[0] == '\0') + continue; + + /* we have a default case -- verify user:maildrop */ + key.data = keybuf; + key.size = keylen; + i = hes_udb_get(&key, &info); + if (i != 0 || info.size <= 0) + { + /* nope -- no aliasing for this user */ + continue; + } + + /* they exist -- build the actual address */ + i = strlen(user) + strlen(up->udb_default) + 2; + p = sm_rpool_malloc_x(rpool, i); + (void) sm_strlcpyn(p, i, 3, user, "@", up->udb_default); + if (tTd(28, 1)) + sm_dprintf("udbmatch ==> %s\n", p); + return p; + break; +# endif /* HESIOD */ + } + } + + /* still nothing.... too bad */ + return NULL; +} +/* +** UDB_MAP_LOOKUP -- look up arbitrary entry in user database map +** +** Parameters: +** map -- the map being queried. +** name -- the name to look up. +** av -- arguments to the map lookup. +** statp -- to get any error status. +** +** Returns: +** NULL if name not found in map. +** The rewritten name otherwise. +*/ + +/* ARGSUSED3 */ +char * +udb_map_lookup(map, name, av, statp) + MAP *map; + char *name; + char **av; + int *statp; +{ + char *val; + char *key; + char *SM_NONVOLATILE result = NULL; + char keybuf[MAXNAME + 1]; + + if (tTd(28, 20) || tTd(38, 20)) + sm_dprintf("udb_map_lookup(%s, %s)\n", map->map_mname, name); + + if (bitset(MF_NOFOLDCASE, map->map_mflags)) + { + key = name; + } + else + { + int keysize = strlen(name); + + if (keysize > sizeof(keybuf) - 1) + keysize = sizeof(keybuf) - 1; + memmove(keybuf, name, keysize); + keybuf[keysize] = '\0'; + makelower(keybuf); + key = keybuf; + } + val = udbmatch(key, map->map_file, NULL); + if (val == NULL) + return NULL; + SM_TRY + if (bitset(MF_MATCHONLY, map->map_mflags)) + result = map_rewrite(map, name, strlen(name), NULL); + else + result = map_rewrite(map, val, strlen(val), av); + SM_FINALLY + sm_free(val); + SM_END_TRY + return result; +} +/* +** _UDBX_INIT -- parse the UDB specification, opening any valid entries. +** +** Parameters: +** e -- the current envelope. +** +** Returns: +** EX_TEMPFAIL -- if it appeared it couldn't get hold of a +** database due to a host being down or some similar +** (recoverable) situation. +** EX_OK -- otherwise. +** +** Side Effects: +** Fills in the UdbEnts structure from UdbSpec. +*/ + +# define MAXUDBOPTS 27 + +static int +_udbx_init(e) + ENVELOPE *e; +{ + int ents = 0; + register char *p; + register struct udbent *up; + + if (UdbInitialized) + return EX_OK; + +# ifdef UDB_DEFAULT_SPEC + if (UdbSpec == NULL) + UdbSpec = UDB_DEFAULT_SPEC; +# endif /* UDB_DEFAULT_SPEC */ + + p = UdbSpec; + up = UdbEnts; + while (p != NULL) + { + char *spec; + int l; + struct udb_option opts[MAXUDBOPTS + 1]; + + while (*p == ' ' || *p == '\t' || *p == ',') + p++; + if (*p == '\0') + break; + spec = p; + p = strchr(p, ','); + if (p != NULL) + *p++ = '\0'; + + if (ents >= MAXUDBENT) + { + syserr("Maximum number of UDB entries exceeded"); + break; + } + + /* extract options */ + (void) _udb_parsespec(spec, opts, MAXUDBOPTS); + + /* + ** Decode database specification. + ** + ** In the sendmail tradition, the leading character + ** defines the semantics of the rest of the entry. + ** + ** @hostname -- forward email to the indicated host. + ** This should be the last in the list, + ** since it always matches the input. + ** /dbname -- search the named database on the local + ** host using the Berkeley db package. + ** Hesiod -- search the named database with BIND + ** using the MIT Hesiod package. + */ + + switch (*spec) + { + case '@': /* forward to remote host */ + up->udb_type = UDB_FORWARD; + up->udb_pid = CurrentPid; + up->udb_fwdhost = spec + 1; + ents++; + up++; + break; + +# if HESIOD + case 'h': /* use hesiod */ + case 'H': + if (sm_strcasecmp(spec, "hesiod") != 0) + goto badspec; + up->udb_type = UDB_HESIOD; + up->udb_pid = CurrentPid; + ents++; + up++; + break; +# endif /* HESIOD */ + +# if NEWDB + case '/': /* look up remote name */ + l = strlen(spec); + if (l > 3 && strcmp(&spec[l - 3], ".db") == 0) + { + up->udb_dbname = spec; + } + else + { + up->udb_dbname = sm_pmalloc_x(l + 4); + (void) sm_strlcpyn(up->udb_dbname, l + 4, 2, + spec, ".db"); + } + errno = 0; +# if DB_VERSION_MAJOR < 2 + up->udb_dbp = dbopen(up->udb_dbname, O_RDONLY, + 0644, DB_BTREE, NULL); +# else /* DB_VERSION_MAJOR < 2 */ + { + int flags = DB_RDONLY; +# if DB_VERSION_MAJOR > 2 + int ret; +# endif /* DB_VERSION_MAJOR > 2 */ + + SM_DB_FLAG_ADD(flags); + up->udb_dbp = NULL; +# if DB_VERSION_MAJOR > 2 + ret = db_create(&up->udb_dbp, NULL, 0); + if (ret != 0) + { + (void) up->udb_dbp->close(up->udb_dbp, + 0); + up->udb_dbp = NULL; + } + else + { + ret = up->udb_dbp->open(up->udb_dbp, + DBTXN + up->udb_dbname, + NULL, + DB_BTREE, + flags, + 0644); + if (ret != 0) + { +#ifdef DB_OLD_VERSION + if (ret == DB_OLD_VERSION) + ret = EINVAL; +#endif /* DB_OLD_VERSION */ + (void) up->udb_dbp->close(up->udb_dbp, 0); + up->udb_dbp = NULL; + } + } + errno = ret; +# else /* DB_VERSION_MAJOR > 2 */ + errno = db_open(up->udb_dbname, DB_BTREE, + flags, 0644, NULL, + NULL, &up->udb_dbp); +# endif /* DB_VERSION_MAJOR > 2 */ + } +# endif /* DB_VERSION_MAJOR < 2 */ + if (up->udb_dbp == NULL) + { + if (tTd(28, 1)) + { + int save_errno = errno; + +# if DB_VERSION_MAJOR < 2 + sm_dprintf("dbopen(%s): %s\n", +# else /* DB_VERSION_MAJOR < 2 */ + sm_dprintf("db_open(%s): %s\n", +# endif /* DB_VERSION_MAJOR < 2 */ + up->udb_dbname, + sm_errstring(errno)); + errno = save_errno; + } + if (errno != ENOENT && errno != EACCES) + { + if (LogLevel > 2) + sm_syslog(LOG_ERR, e->e_id, +# if DB_VERSION_MAJOR < 2 + "dbopen(%s): %s", +# else /* DB_VERSION_MAJOR < 2 */ + "db_open(%s): %s", +# endif /* DB_VERSION_MAJOR < 2 */ + up->udb_dbname, + sm_errstring(errno)); + up->udb_type = UDB_EOLIST; + if (up->udb_dbname != spec) + sm_free(up->udb_dbname); /* XXX */ + goto tempfail; + } + if (up->udb_dbname != spec) + sm_free(up->udb_dbname); /* XXX */ + break; + } + if (tTd(28, 1)) + { +# if DB_VERSION_MAJOR < 2 + sm_dprintf("_udbx_init: dbopen(%s)\n", +# else /* DB_VERSION_MAJOR < 2 */ + sm_dprintf("_udbx_init: db_open(%s)\n", +# endif /* DB_VERSION_MAJOR < 2 */ + up->udb_dbname); + } + up->udb_type = UDB_DBFETCH; + up->udb_pid = CurrentPid; + ents++; + up++; + break; +# endif /* NEWDB */ + + default: +# if HESIOD +badspec: +# endif /* HESIOD */ + syserr("Unknown UDB spec %s", spec); + break; + } + } + up->udb_type = UDB_EOLIST; + + if (tTd(28, 4)) + { + for (up = UdbEnts; up->udb_type != UDB_EOLIST; up++) + { + switch (up->udb_type) + { + case UDB_REMOTE: + sm_dprintf("REMOTE: addr %s, timeo %d\n", + anynet_ntoa((SOCKADDR *) &up->udb_addr), + up->udb_timeout); + break; + + case UDB_DBFETCH: +# if NEWDB + sm_dprintf("FETCH: file %s\n", + up->udb_dbname); +# else /* NEWDB */ + sm_dprintf("FETCH\n"); +# endif /* NEWDB */ + break; + + case UDB_FORWARD: + sm_dprintf("FORWARD: host %s\n", + up->udb_fwdhost); + break; + + case UDB_HESIOD: + sm_dprintf("HESIOD\n"); + break; + + default: + sm_dprintf("UNKNOWN\n"); + break; + } + } + } + + UdbInitialized = true; + errno = 0; + return EX_OK; + + /* + ** On temporary failure, back out anything we've already done + */ + + tempfail: +# if NEWDB + for (up = UdbEnts; up->udb_type != UDB_EOLIST; up++) + { + if (up->udb_type == UDB_DBFETCH) + { +# if DB_VERSION_MAJOR < 2 + (*up->udb_dbp->close)(up->udb_dbp); +# else /* DB_VERSION_MAJOR < 2 */ + errno = (*up->udb_dbp->close)(up->udb_dbp, 0); +# endif /* DB_VERSION_MAJOR < 2 */ + if (tTd(28, 1)) + sm_dprintf("_udbx_init: db->close(%s)\n", + up->udb_dbname); + } + } +# endif /* NEWDB */ + return EX_TEMPFAIL; +} + +static int +_udb_parsespec(udbspec, opt, maxopts) + char *udbspec; + struct udb_option opt[]; + int maxopts; +{ + register char *spec; + register char *spec_end; + register int optnum; + + spec_end = strchr(udbspec, ':'); + for (optnum = 0; optnum < maxopts && (spec = spec_end) != NULL; optnum++) + { + register char *p; + + while (isascii(*spec) && isspace(*spec)) + spec++; + spec_end = strchr(spec, ':'); + if (spec_end != NULL) + *spec_end++ = '\0'; + + opt[optnum].udbo_name = spec; + opt[optnum].udbo_val = NULL; + p = strchr(spec, '='); + if (p != NULL) + opt[optnum].udbo_val = ++p; + } + return optnum; +} +/* +** _UDBX_CLOSE -- close all file based UDB entries. +** +** Parameters: +** none +** +** Returns: +** none +*/ +void +_udbx_close() +{ + struct udbent *up; + + if (!UdbInitialized) + return; + + for (up = UdbEnts; up->udb_type != UDB_EOLIST; up++) + { + if (up->udb_pid != CurrentPid) + continue; + +# if NEWDB + if (up->udb_type == UDB_DBFETCH) + { +# if DB_VERSION_MAJOR < 2 + (*up->udb_dbp->close)(up->udb_dbp); +# else /* DB_VERSION_MAJOR < 2 */ + errno = (*up->udb_dbp->close)(up->udb_dbp, 0); +# endif /* DB_VERSION_MAJOR < 2 */ + } + if (tTd(28, 1)) + sm_dprintf("_udbx_init: db->close(%s)\n", + up->udb_dbname); +# endif /* NEWDB */ + } +} + +# if HESIOD + +static int +hes_udb_get(key, info) + DBT *key; + DBT *info; +{ + char *name, *type; + char **hp; + char kbuf[MAXUDBKEY + 1]; + + if (sm_strlcpy(kbuf, key->data, sizeof(kbuf)) >= sizeof(kbuf)) + return 0; + name = kbuf; + type = strrchr(name, ':'); + if (type == NULL) + return 1; + *type++ = '\0'; + if (strchr(name, '@') != NULL) + return 1; + + if (tTd(28, 1)) + sm_dprintf("hes_udb_get(%s, %s)\n", name, type); + + /* make the hesiod query */ +# ifdef HESIOD_INIT + if (HesiodContext == NULL && hesiod_init(&HesiodContext) != 0) + return -1; + hp = hesiod_resolve(HesiodContext, name, type); +# else /* HESIOD_INIT */ + hp = hes_resolve(name, type); +# endif /* HESIOD_INIT */ + *--type = ':'; +# ifdef HESIOD_INIT + if (hp == NULL) + return 1; + if (*hp == NULL) + { + hesiod_free_list(HesiodContext, hp); + if (errno == ECONNREFUSED || errno == EMSGSIZE) + return -1; + return 1; + } +# else /* HESIOD_INIT */ + if (hp == NULL || hp[0] == NULL) + { + /* network problem or timeout */ + if (hes_error() == HES_ER_NET) + return -1; + + return 1; + } +# endif /* HESIOD_INIT */ + else + { + /* + ** If there are multiple matches, just return the + ** first one. + ** + ** XXX These should really be returned; for example, + ** XXX it is legal for :maildrop to be multi-valued. + */ + + info->data = hp[0]; + info->size = (size_t) strlen(info->data); + } + + if (tTd(28, 80)) + sm_dprintf("hes_udb_get => %s\n", *hp); + + return 0; +} +# endif /* HESIOD */ + +#else /* USERDB */ + +int +udbexpand(a, sendq, aliaslevel, e) + ADDRESS *a; + ADDRESS **sendq; + int aliaslevel; + ENVELOPE *e; +{ + return EX_OK; +} + +#endif /* USERDB */ |