udb.c revision 49218d4f8e4d84d1c08aeb267bcf6e451f2056dc
/*
* Copyright (c) 1998-2003 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.
*
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <sendmail.h>
#if USERDB
#else /* USERDB */
#endif /* USERDB */
#if USERDB
# if NEWDB
# else /* NEWDB */
# define DBT struct _data_base_thang_
{
void *data; /* pointer to 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 */
char *udb_default; /* default host for outgoing mail */
union
{
/* type UE_REMOTE -- do remote call for lookup */
struct
{
int _udb_timeout; /* timeout */
} udb_remote;
# endif /* NETINET || NETINET6 */
/* type UE_FORWARD -- forward message to remote */
struct
{
char *_udb_fwdhost; /* name of forward host */
} udb_forward;
# if NEWDB
/* type UE_FETCH -- lookup in local database */
struct
{
char *_udb_dbname; /* pathname of database */
} udb_lookup;
# endif /* NEWDB */
} udb_u;
};
# define UDB_EOLIST 0 /* end of list */
struct udb_option
{
char *udbo_name;
char *udbo_val;
};
# if HESIOD
# endif /* HESIOD */
/*
** 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 bool UdbInitialized = false;
int
register ADDRESS *a;
int aliaslevel;
register ENVELOPE *e;
{
int i;
bool breakout;
int keylen;
int naddrs;
char *user;
/* make certain we are supposed to send to this address */
if (!QS_IS_SENDABLE(a->q_state))
return EX_OK;
/* 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 */
return EX_OK;
/* extract user to do userdb matching on */
/* 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;
/* if name is too long, assume it won't match */
return EX_OK;
/* build actual database key */
breakout = false;
{
int usersize;
int userleft;
char userbuf[MEMCHUNKSIZE];
# if HESIOD && HES_GETMAILHOST
# endif /* HESIOD && HES_GETMAILHOST */
# endif /* defined(NEWDB) && DB_VERSION_MAJOR > 1 */
userbuf[0] = '\0';
/*
** 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.
*/
{
# if NEWDB
case UDB_DBFETCH:
sm_dprintf("udbexpand: trying %s (%d) via db\n",
# if DB_VERSION_MAJOR < 2
# else /* DB_VERSION_MAJOR < 2 */
i = 0;
# else /* DB_VERSION_MAJOR > 2 || DB_VERSION_MINOR >= 6 */
# endif /* DB_VERSION_MAJOR > 2 || DB_VERSION_MINOR >= 6 */
i = -1;
i = 1;
# endif /* DB_VERSION_MAJOR < 2 */
{
sm_dprintf("udbexpand: no match on %s (%d)\n",
# if DB_VERSION_MAJOR > 1
{
}
# endif /* DB_VERSION_MAJOR > 1 */
break;
}
sm_dprintf("udbexpand: match %.*s: %.*s\n",
{
char *p;
{
a->q_state = QS_VERIFIED;
# if DB_VERSION_MAJOR > 1
{
}
# endif /* DB_VERSION_MAJOR > 1 */
return EX_OK;
}
breakout = true;
{
char *nuser;
int size = MEMCHUNKSIZE;
}
if (p != user)
{
*p++ = ',';
userleft--;
}
/* get the next record */
# if DB_VERSION_MAJOR < 2
# else /* DB_VERSION_MAJOR < 2 */
i = 0;
i = 1;
# endif /* DB_VERSION_MAJOR < 2 */
}
# if DB_VERSION_MAJOR > 1
{
}
# endif /* DB_VERSION_MAJOR > 1 */
/* if nothing ever matched, try next database */
if (!breakout)
break;
if (LogLevel > 10)
"expand %.100s => %s",
e->e_to,
{
{
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",
return EX_TEMPFAIL;
}
/*
** If this address has a -request address, reflect
** it into the envelope.
*/
":mailsender");
# if DB_VERSION_MAJOR < 2
# else /* DB_VERSION_MAJOR < 2 */
# endif /* DB_VERSION_MAJOR < 2 */
break;
/* announce delivery; NORECEIPT bit set later */
{
"Message delivered to mailing list %s\n",
a->q_paddr);
}
e->e_flags |= EF_SENDRECEIPT;
break;
# endif /* NEWDB */
# if HESIOD
case UDB_HESIOD:
sm_dprintf("udbexpand: trying %s (%d) via hesiod\n",
/* look up the key via hesiod */
if (i < 0)
{
syserr("udbexpand: hesiod-get %.*s stat %d",
return EX_TEMPFAIL;
}
{
# if HES_GETMAILHOST
struct hes_postoffice *hp;
# endif /* HES_GETMAILHOST */
sm_dprintf("udbexpand: no match on %s (%d)\n",
# if HES_GETMAILHOST
sm_dprintf(" ... trying hes_getmailhost(%s)\n",
a->q_user);
{
if (hes_error() == HES_ER_NET)
{
syserr("udbexpand: hesiod-getmail %s stat %d",
return EX_TEMPFAIL;
}
sm_dprintf("hes_getmailhost(%s): %d\n",
break;
}
sizeof pobuf - 2)
{
sm_dprintf("hes_getmailhost(%s): expansion too long: %.30s@%.30s\n",
a->q_user,
break;
}
# else /* HES_GETMAILHOST */
break;
# endif /* HES_GETMAILHOST */
}
sm_dprintf("udbexpand: match %.*s: %.*s\n",
{
a->q_state = QS_VERIFIED;
return EX_OK;
}
breakout = true;
if (LogLevel > 10)
"hesiod %.100s => %s",
e->e_to,
{
{
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.
*/
":mailsender");
break;
break;
# endif /* HESIOD */
case UDB_REMOTE:
/* not yet implemented */
break;
case UDB_FORWARD:
{
a->q_state = QS_VERIFIED;
return EX_OK;
}
if (i >= usersize)
{
usersize = i + 1;
}
{
{
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 */
}
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 *
char *sender;
{
}
/*
** 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 *
char *user;
char *field;
{
register char *p;
int i;
int keylen;
if (!UdbInitialized)
{
return NULL;
}
/* short circuit if no spec */
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 */
if (i < sizeof "maildrop")
i = sizeof "maildrop";
return NULL;
/* names beginning with colons indicate metadata */
if (user[0] == ':')
return NULL;
/* build database key */
{
/*
** Select action based on entry type.
*/
{
# if NEWDB
case UDB_DBFETCH:
# if DB_VERSION_MAJOR < 2
# else /* DB_VERSION_MAJOR < 2 */
# endif /* DB_VERSION_MAJOR < 2 */
{
sm_dprintf("udbmatch: no match on %s (%d) via db\n",
continue;
}
sm_dprintf("udbmatch ==> %s\n", p);
return p;
# endif /* NEWDB */
# if HESIOD
case UDB_HESIOD:
{
sm_dprintf("udbmatch: no match on %s (%d) via hesiod\n",
continue;
}
sm_dprintf("udbmatch ==> %s\n", p);
return p;
# endif /* HESIOD */
}
}
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 */
{
{
# if NEWDB
case UDB_DBFETCH:
/* get the default case for this database */
{
# if DB_VERSION_MAJOR < 2
# else /* DB_VERSION_MAJOR < 2 */
&info, 0);
# endif /* DB_VERSION_MAJOR < 2 */
{
/* no default case */
continue;
}
/* save the default case */
}
continue;
/* we have a default case -- verify user:maildrop */
# if DB_VERSION_MAJOR < 2
# else /* DB_VERSION_MAJOR < 2 */
# endif /* DB_VERSION_MAJOR < 2 */
{
/* nope -- no aliasing for this user */
continue;
}
/* they exist -- build the actual address */
p = sm_rpool_malloc_x(rpool, i);
sm_dprintf("udbmatch ==> %s\n", p);
return p;
# endif /* NEWDB */
# if HESIOD
case UDB_HESIOD:
/* get the default case for this database */
{
{
/* no default case */
continue;
}
/* save the default case */
}
continue;
/* we have a default case -- verify user:maildrop */
{
/* nope -- no aliasing for this user */
continue;
}
/* they exist -- build the actual address */
p = sm_rpool_malloc_x(rpool, i);
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 *
char *name;
char **av;
int *statp;
{
char *val;
char *key;
{
}
else
{
}
return NULL;
else
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;
if (UdbInitialized)
return EX_OK;
# ifdef UDB_DEFAULT_SPEC
# endif /* UDB_DEFAULT_SPEC */
p = UdbSpec;
while (p != NULL)
{
char *spec;
int l;
while (*p == ' ' || *p == '\t' || *p == ',')
p++;
if (*p == '\0')
break;
spec = p;
p = strchr(p, ',');
if (p != NULL)
*p++ = '\0';
{
syserr("Maximum number of UDB entries exceeded");
break;
}
/* extract options */
/*
** 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 */
ents++;
up++;
break;
# if HESIOD
case 'h': /* use hesiod */
case 'H':
goto badspec;
ents++;
up++;
break;
# endif /* HESIOD */
# if NEWDB
case '/': /* look up remote name */
{
}
else
{
spec, ".db");
}
errno = 0;
# if DB_VERSION_MAJOR < 2
# else /* DB_VERSION_MAJOR < 2 */
{
# if DB_VERSION_MAJOR > 2
int ret;
# endif /* DB_VERSION_MAJOR > 2 */
# if DB_VERSION_MAJOR > 2
if (ret != 0)
{
0);
}
else
{
up->udb_dbname,
NULL,
0644);
if (ret != 0)
{
#ifdef DB_OLD_VERSION
if (ret == DB_OLD_VERSION)
#endif /* DB_OLD_VERSION */
}
}
# else /* DB_VERSION_MAJOR > 2 */
# endif /* DB_VERSION_MAJOR > 2 */
}
# endif /* DB_VERSION_MAJOR < 2 */
{
{
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,
errno = save_errno;
}
{
if (LogLevel > 2)
# 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,
goto tempfail;
}
break;
}
{
# 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);
}
ents++;
up++;
break;
# endif /* NEWDB */
default:
# if HESIOD
# endif /* HESIOD */
break;
}
}
{
{
{
case UDB_REMOTE:
sm_dprintf("REMOTE: addr %s, timeo %d\n",
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
*/
# if NEWDB
{
{
# if DB_VERSION_MAJOR < 2
# else /* DB_VERSION_MAJOR < 2 */
# endif /* DB_VERSION_MAJOR < 2 */
sm_dprintf("_udbx_init: db->close(%s)\n",
up->udb_dbname);
}
}
# endif /* NEWDB */
return EX_TEMPFAIL;
}
static int
char *udbspec;
struct udb_option opt[];
int maxopts;
{
register char *spec;
register char *spec_end;
register int optnum;
{
register char *p;
spec++;
*spec_end++ = '\0';
if (p != NULL)
}
return optnum;
}
/*
** _UDBX_CLOSE -- close all file based UDB entries.
**
** Parameters:
** none
**
** Returns:
** none
*/
void
{
if (!UdbInitialized)
return;
{
continue;
# if NEWDB
{
# if DB_VERSION_MAJOR < 2
# else /* DB_VERSION_MAJOR < 2 */
# endif /* DB_VERSION_MAJOR < 2 */
}
sm_dprintf("_udbx_init: db->close(%s)\n",
up->udb_dbname);
# endif /* NEWDB */
}
}
# if HESIOD
static int
{
char **hp;
return 0;
return 1;
*type++ = '\0';
return 1;
/* make the hesiod query */
# ifdef HESIOD_INIT
return -1;
# else /* HESIOD_INIT */
# endif /* HESIOD_INIT */
*--type = ':';
# ifdef HESIOD_INIT
return 1;
{
return -1;
return 1;
}
# else /* HESIOD_INIT */
{
/* 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.
*/
}
return 0;
}
# endif /* HESIOD */
#else /* USERDB */
int
ADDRESS *a;
int aliaslevel;
ENVELOPE *e;
{
return EX_OK;
}
#endif /* USERDB */