/* $Id: rcptcheck.C,v 1.19 2006/04/01 07:47:10 dm Exp $ */
/*
*
* Copyright (C) 2004 David Mazieres (dm@uun.org)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*
*/
#include "asmtpd.h"
domain_map dmap;
struct alias_map : public map_base {
str path () { return opt->alias_file; }
str lookup (str name);
};
rxx &
map_base::linerx ()
{
static rxx aliasrx ("^\\s*([^\\x00-\\x20\\x7f:]+)\\s*:"
"\\s*([^\\x00-\\x20\\x7f]*)\\s*$");
return aliasrx;
}
bool
map_base::load ()
{
for (;;) {
struct stat sb;
if (stat (path (), &sb) < 0) {
latest = 0;
loadno = 0;
if (smtpd::tmperr (errno)) {
warn << path () << ": " << strerror (errno) << "\n";
return false;
}
table.deleteall ();
return true;
}
if (opt->configno == loadno && latest == max (sb.st_mtime, sb.st_ctime))
return true;
warn << "loading " << path () << "\n";
loadno = 0;
table.deleteall ();
int fd = open (path (), O_RDONLY);
if (fd < 0)
return !smtpd::tmperr (errno);
suio buf;
u_int lineno = 0;
while (buf.input (fd) > 0)
while (str line = suio_getline (&buf)) {
lineno++;
rxx &aliasrx = linerx ();
static rxx blankrx ("^\\s*$");
if (aliasrx.match (line)) {
str k = mytolower (aliasrx[1]);
if (table[k])
warn << path () << ":" << lineno
<< ": overriding previous entry for " << k << "\n";
//warn << k << " -> " << aliasrx[2] << "\n";
table.insert (k, aliasrx[2]);
}
else if (!blankrx.match (line))
warn << path () << ":" << lineno << ": syntax error\n";
}
if (buf.resid ())
warn << path () << ": ignoring incomplete last line\n";
struct stat sb2;
errno = 0;
fstat (fd, &sb2);
close (fd);
if (errno) {
warn << path () << ": " << strerror (errno) << "\n";
continue;
}
if (sb.st_dev != sb2.st_dev || sb.st_ino != sb2.st_ino
|| sb.st_mtime != sb2.st_mtime || sb.st_ctime != sb2.st_ctime) {
warn << path () << ": file changed while reading\n";
continue;
}
latest = max (sb.st_ctime, sb.st_mtime);
loadno = opt->configno;
return true;
}
}
str
alias_map::lookup (str orig)
{
if (!load ())
return NULL;
str *to = table[mytolower (orig)];
if (to && to->len ())
return *to;
if (!opt->separator)
return orig;
str name (orig), base (name), ext;
for (int i = 0; i < 20;) {
const char *p = strrchr (base, opt->separator);
if (!p || p == base.cstr ())
return name;
if (ext)
ext = strbuf ("%s%c", p + 1, opt->separator) << ext;
else
ext = p + 1;
base = substr (base, 0, p - base.cstr ());
if ((to = table[mytolower (base)])) {
i++;
base = name = strbuf ("%s%c%s", to->cstr (),
opt->separator, ext.cstr ());
ext = NULL;
}
}
maybe_warn (strbuf () << "possible loop during alias expansion for "
<< orig << "\n");
return orig;
}
bool
domain_map::lookup (str *avuser, str recip)
{
if (!load ())
return false;
str local = extract_local (recip);
str domain = mytolower (extract_domain (recip));
str *to = table[domain];
if (to && to->len ()) {
if ((*to)[to->len () - 1] == opt->separator)
*avuser = strbuf () << *to << local;
else
*avuser = *to;
}
else if (to)
*avuser = local;
else
*avuser = NULL;
return true;
}
static alias_map amap;
localcheck::localcheck (smtpd *s, str r, char m, cbs c)
: smtp (s), recip (r), cb (c), depth (0), indefault (false)
{
switch (m) {
case 'r':
mode = 'r';
if (opt->user_rcpt) {
try_user = true;
unknown_user = "unknown";
fallback_user = "default";
}
else {
try_user = false;
fallback_user = "default";
}
break;
case 'R':
mode = 'r';
try_user = false;
fallback_user = "secondary";
break;
case 'm':
mode = 'm';
try_user = opt->user_mail;
fallback_user = "relay";
break;
case 'M':
mode = 'm';
try_user = false;
fallback_user = "relay";
break;
default:
panic ("localcheck: bad mode %c\n", mode);
}
}
void
localcheck::reply (str res, str bodycmd)
{
#if 0
if (mode != 'r' && mode != 'R')
(*cb) (res);
else
#endif
if (bodycmd)
(*cb) (smtp->bodycheck (execuser, bodycmd, smtpd::okstr));
else if (!res || res[0] == '2')
(*cb) (smtp->bodycheck (NULL, NULL, res));
else
(*cb) (res);
delete this;
}
void
localcheck::init ()
{
str addr;
switch (mode) {
case 'r':
addr = recip;
break;
case 'm':
addr = smtp->get_from ();
break;
}
if (try_user && !dmap.lookup (&avuser, addr)) {
reply ("451 temporary error processing domain file\r\n");
return;
}
if (try_user) {
if (!avuser)
avuser = extract_local (recip);
if (avuser) {
avenge ();
return;
}
}
dodefault ();
}
void
localcheck::avenge ()
{
if (!avuser || !(avuser = amap.lookup (avuser))) {
reply ("451 temporary error processing alias file\r\n");
return;
}
if (!loop.insert (mytolower (avuser)) || ++depth >= 20) {
warn << "loop while checking rcpt " << recip << "\n";
reply (NULL);
return;
}
avenge_1 ();
}
void
localcheck::avenge_1 ()
{
str user = avuser;
str ext;
if (opt->separator)
if (const char *p = strchr (user, opt->separator)) {
ext = mytolower (p + 1);
user = substr (user, 0, p - user.cstr ());
}
struct passwd *pw = validuser (user, user != opt->av_user->pw_name);
if (!pw) {
execuser = NULL;
if (unknown_user)
avif::alloc (opt->av_user, smtp, recip, 's', NULL, unknown_user, avuser,
wrap (this, &localcheck::avenge_2),
modeenv ());
else
dodefault ();
}
else {
execuser = pw->pw_name;
avcount *avc = avcount::get (pw->pw_uid);
if (avc->acquire ())
avif::alloc (pw, smtp, recip, mode, avc, ext, avuser,
wrap (this, &localcheck::avenge_2),
modeenv ());
else
avc->waiters.push_back (wrap (this, &localcheck::avenge_1));
}
}
void
localcheck::dodefault ()
{
if (indefault || !fallback_user) {
if (mode == 'r' || mode == 'R')
(*cb) (smtp->bodycheck (NULL, NULL, NULL));
else
(*cb) (NULL);
delete this;
}
else {
indefault = true;
execuser = NULL;
avif::alloc (opt->av_user, smtp, recip, 's', NULL, fallback_user, avuser,
wrap (this, &localcheck::avenge_2),
modeenv ());
}
}
void
localcheck::avenge_2 (avif::disp_t disp, str r)
{
switch (disp) {
case avif::NEXT:
dodefault ();
break;;
case avif::DONE:
reply (r);
break;
case avif::REDIR:
{
if (!validate_local (r)) {
warn << "bad redirect from " << avuser << " -> " << r << "\n";
dodefault ();
return;
}
str newuser = r;
if (opt->separator)
if (const char *p = strchr (newuser, opt->separator))
newuser = substr (newuser, 0, p - newuser.cstr ());
if (execuser
&& strcasecmp (execuser, newuser)
&& strcmp (execuser, opt->av_user->pw_name)
&& strcmp (newuser, opt->av_user->pw_name)) {
warn << "bad redirect from " << avuser << " -> " << r << "\n";
dodefault ();
}
else {
avuser = r;
avenge ();
}
}
break;
case avif::BODY:
reply (NULL, r);
break;
}
}
str
localcheck::modeenv ()
{
static str rcpt ("AVENGER_MODE=rcpt");
static str mail ("AVENGER_MODE=mail");
switch (mode) {
case 'r':
return rcpt;
case 'm':
return mail;
default:
panic ("localcheck::modeenv: bad mode %c\n", mode);
}
}
void
rcptcheck (smtpd *s, str recip, char mode, cbs cb)
{
localcheck *lc = New localcheck (s, recip, mode, cb);
lc->init ();
}
syntax highlighted by Code2HTML, v. 0.9.1