/* $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 iitab; ihash 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"; }