/* $Id: quota.C,v 1.13 2005/10/19 21:38:06 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"
#include "rawnet.h"
ihash<const in_addr, ipinfo, &ipinfo::addr, &ipinfo::link> iitab;
ihash<const str, userinfo, &userinfo::user, &userinfo::link> uitab;
void
run_cmd (const char *cmd, const char *arg1, const char *arg2)
{
const char *av[] = { cmd, arg1, arg2, NULL };
pid_t pid = spawn (cmd, av);
if (pid < 0)
warn ("%s: %m\n", cmd);
else
waitpid (pid, NULL, 0);
}
lazycb_t *quota_maintenance_cb;
static void
quota_maintenance ()
{
iitab.traverse (&ipinfo::maybe_delete);
uitab.traverse (&userinfo::maybe_delete);
}
quota::quota ()
: last (timenow)
{
if (!quota_maintenance_cb)
quota_maintenance_cb = lazycb (1200, wrap (quota_maintenance));
}
void
quota::decay (bool del)
{
if (last > timenow)
last = timenow;
u_int interval = (timenow - last) / (3600 / msgmult);
last += interval * (3600 / msgmult);
if (interval || del)
do_decay (del, interval);
}
ipinfo::ipinfo (in_addr a)
: addr (a), filt (false), ncon (0), ndeliv (0), nerr (0),
trp (NULL), netpath_time (0), nhops (0)
{
iitab.insert (this);
}
ipinfo::~ipinfo ()
{
iitab.remove (this);
setfilter (false);
/* This shouldn't happen because the count should be bumped for new
* clients. If it did happen, we could drop a callback because of
* netpath_addcb. */
assert (!trp);
}
ipinfo *
ipinfo::lookup (struct in_addr a, bool create)
{
if (struct ipinfo *ii = iitab[a])
return ii;
if (create)
return New ipinfo (a);
return NULL;
}
void
ipinfo::setfilter (bool newfilt)
{
if (filt == newfilt || terminated)
return;
filt = newfilt;
if (opt->smtp_filter && !terminated)
run_cmd (opt->smtp_filter, filt ? "add" : "del", inet_ntoa (addr));
}
void
ipinfo::setfilter ()
{
setfilter (ncon >= opt->con_max_per_ip
|| nerr + msgmult > opt->err_max_per_ip * msgmult
/* || ndeliv + msgmult > opt->msg_max_per_ip * msgmult */
);
}
void
ipinfo::do_netpath (in_addr src)
{
if (!opt->netpath || trp || timenow < netpath_time + 60)
return;
sockaddr_in ss;
bzero (&ss, sizeof (ss));
ss.sin_family = AF_INET;
ss.sin_addr = src;
sockaddr_in dst;
bzero (&dst, sizeof (dst));
dst.sin_family = AF_INET;
dst.sin_port = htons (0);
dst.sin_addr = addr;
trp = ::netpath (&dst , 0, wrap (this, &ipinfo::netpath_cb), &ss);
}
void
ipinfo::netpath_cb (int total_hops, in_addr *ap, int ac)
{
trp = NULL;
if (total_hops > 0)
nhops = total_hops;
else
nhops = -1;
if (ac > 0) {
strbuf sb;
for (int i = 0; i < ac; i++) {
if (i)
sb << " ";
sb << inet_ntoa (ap[i]);
}
netpath = sb;
}
else
netpath = NULL;
netpath_time = timenow;
}
void
ipinfo::do_decay (bool del, u_int interval)
{
decay_var (ndeliv, opt->msg_rate_per_ip, interval);
decay_var (nerr, opt->err_rate_per_ip, interval);
setfilter ();
if (del && !ncon && !ndeliv && !nerr && !trp)
delete this;
}
static str deliverr ("421 too much load, please back off\r\n");
static str errerr ("421 too many errors, please back off\r\n");
str
ipinfo::rcpt ()
{
if (!check_var (ndeliv, opt->msg_max_per_ip))
return deliverr;
ndeliv += msgmult;
return NULL;
}
str
ipinfo::status ()
{
decay ();
if (check_var (nerr, opt->err_max_per_ip))
return NULL;
error ();
return errerr;
}
str
ipinfo::addcon ()
{
if (!check_var (nerr, opt->err_max_per_ip))
// Don't decay or bump error -- just wait for next maintenance
return errerr;
else if (str st = status ())
return st;
else if (ncon >= opt->con_max_per_ip) {
static str conerr ("421 too many open connections\r\n");
error ();
return conerr;
}
ncon++;
return NULL;
}
userinfo::userinfo (str u)
: user (u), ndeliv (0)
{
uitab.insert (this);
}
userinfo::~userinfo ()
{
uitab.remove (this);
}
void
userinfo::do_decay (bool del, u_int interval)
{
decay_var (ndeliv, opt->msg_rate_per_user, interval);
if (del && !ndeliv)
delete this;
}
userinfo *
userinfo::lookup (str u, bool create)
{
if (struct userinfo *ui = uitab[u])
return ui;
if (create)
return New userinfo (u);
return NULL;
}
str
userinfo::rcpt ()
{
if (!check_var (ndeliv, opt->msg_max_per_user))
return deliverr;
ndeliv += msgmult;
return NULL;
}
void
clear_filters ()
{
for (ipinfo *ii = iitab.first (); ii; ii = iitab.next (ii))
if (ii->filt && opt->smtp_filter)
run_cmd (opt->smtp_filter, "del", inet_ntoa (ii->addr));
}
void
quota_dump (const strbuf &sb)
{
quota_maintenance ();
sb << "250----------------------------------------\r\n"
<< "250-IP address F #conn #rcpt #errs\r\n";
for (ipinfo *ii = iitab.first (); ii; ii = iitab.next (ii)) {
ii->decay ();
strbuf line ("250-%-15s %c % 4d % 4d % 4d\r\n",
inet_ntoa (ii->addr), ii->filt ? '*' : ' ',
ii->ncon, ii->ndeliv/ipinfo::msgmult,
ii->nerr/ipinfo::msgmult);
sb << line;
}
sb << "250----------------------------------------\r\n"
<< "250-Sending entity #rcpt\r\n";
for (userinfo *ui = uitab.first (); ui; ui = uitab.next (ui)) {
ui->decay ();
sb.fmt ("250-%-26s % 4d\r\n", ui->user.cstr (),
ui->ndeliv/userinfo::msgmult);
}
sb << "250 ---------------------------------------\r\n";
}
syntax highlighted by Code2HTML, v. 0.9.1