Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

#include "common.h"
#include "spam.h"

int     cflag;
int     debug;
int     hflag;
int     nflag;
int     sflag;
int     tflag;
int     vflag;
Biobuf  bin, bout, *cout;

        /* file names */
char    patfile[128];
char    linefile[128];
char    holdqueue[128];
char    copydir[128];

char    header[Hdrsize+2];
char    cmd[1024];
char    **qname;
char    **qdir;
char    *sender;
String  *recips;

char*   canon(Biobuf*, char*, char*, int*);
int     matcher(char*, Pattern*, char*, Resub*);
int     matchaction(int, char*, Resub*);
Biobuf  *opencopy(char*);
Biobuf  *opendump(char*);
char    *qmail(char**, char*, int, Biobuf*);
void    saveline(char*, char*, Resub*);
int     optoutofspamfilter(char*);

void
usage(void)
{
        fprint(2, "missing or bad arguments to qer\n");
        exits("usage");
}

void
regerror(char *s)
{
        fprint(2, "scanmail: %s\n", s);
}

void *
Malloc(long n)
{
        void *p;

        p = malloc(n);
        if(p == 0)
                exits("malloc");
        return p;
}

void*
Realloc(void *p, ulong n)
{
        p = realloc(p, n);
        if(p == 0)
                exits("realloc");
        return p;
}

void
main(int argc, char *argv[])
{
        int i, n, nolines, optout;
        char **args, **a, *cp, *buf;
        char body[Bodysize+2];
        Resub match[1];
        Biobuf *bp;

        optout = 1;
        a = args = Malloc((argc+1)*sizeof(char*));
        sprint(patfile, "%s/patterns", UPASLIB);
        sprint(linefile, "%s/lines", UPASLOG);
        sprint(holdqueue, "%s/queue.hold", SPOOL);
        sprint(copydir, "%s/copy", SPOOL);

        *a++ = argv[0];
        for(argc--, argv++; argv[0] && argv[0][0] == '-'; argc--, argv++){
                switch(argv[0][1]){
                case 'c':                       /* save copy of message */
                        cflag = 1;
                        break;
                case 'd':                       /* debug */
                        debug++;
                        *a++ = argv[0];
                        break;
                case 'h':                       /* queue held messages by sender domain */
                        hflag = 1;              /* -q flag must be set also */
                        break;
                case 'n':                       /* NOHOLD mode */
                        nflag = 1;
                        break;
                case 'p':                       /* pattern file */
                        if(argv[0][2] || argv[1] == 0)
                                usage();
                        argc--;
                        argv++;
                        strecpy(patfile, patfile+sizeof patfile, *argv);
                        break;
                case 'q':                       /* queue name */
                        if(argv[0][2] ||  argv[1] == 0)
                                usage();
                        *a++ = argv[0];
                        argc--;
                        argv++;
                        qname = a;
                        *a++ = argv[0];
                        break;
                case 's':                       /* save copy of dumped message */
                        sflag = 1;
                        break;
                case 't':                       /* test mode - don't log match
                                                 * and write message to /dev/null
                                                 */
                        tflag = 1;
                        break;
                case 'v':                       /* vebose - print matches */
                        vflag = 1;
                        break;
                default:
                        *a++ = argv[0];
                        break;
                }
        }

        if(argc < 3)
                usage();

        Binit(&bin, 0, OREAD);
        bp = Bopen(patfile, OREAD);
        if(bp){
                parsepats(bp);
                Bterm(bp);
        }
        qdir = a;
        sender = argv[2];

                /* copy the rest of argv, acummulating the recipients as we go */
        for(i = 0; argv[i]; i++){
                *a++ = argv[i];
                if(i < 4)       /* skip queue, 'mail', sender, dest sys */
                        continue;
                        /* recipients and smtp flags - skip the latter*/
                if(strcmp(argv[i], "-g") == 0){
                        *a++ = argv[++i];
                        continue;
                }
                if(recips)
                        s_append(recips, ", ");
                else
                        recips = s_new();
                s_append(recips, argv[i]);
                if(optout && !optoutofspamfilter(argv[i]))
                        optout = 0;
        }
        *a = 0;
                /* construct a command string for matching */
        snprint(cmd, sizeof(cmd)-1, "%s %s", sender, s_to_c(recips));
        cmd[sizeof(cmd)-1] = 0;
        for(cp = cmd; *cp; cp++)
                *cp = tolower(*cp);

                /* canonicalize a copy of the header and body.
                 * buf points to orginal message and n contains
                 * number of bytes of original message read during
                 * canonicalization.
                 */
        *body = 0;
        *header = 0;
        buf = canon(&bin, header+1, body+1, &n);
        if (buf == 0)
                exits("read");

                /* if all users opt out, don't try matches */
        if(optout){
                if(cflag)
                        cout = opencopy(sender);
                exits(qmail(args, buf, n, cout));
        }

                /* Turn off line logging, if command line matches */
        nolines = matchaction(Lineoff, cmd, match);

        for(i = 0; patterns[i].action; i++){
                        /* Lineoff patterns were already done above */
                if(i == Lineoff)
                        continue;
                        /* don't apply "Line" patterns if excluded above */
                if(nolines && i == SaveLine)
                        continue;
                        /* apply patterns to the sender/recips, header and body */
                if(matchaction(i, cmd, match))
                        break;
                if(matchaction(i, header+1, match))
                        break;
                if(i == HoldHeader)
                        continue;
                if(matchaction(i, body+1, match))
                        break;
        }
        if(cflag && patterns[i].action == 0)    /* no match found - save msg */
                cout = opencopy(sender);

        exits(qmail(args, buf, n, cout));
}

char*
qmail(char **argv, char *buf, int n, Biobuf *cout)
{
        Waitmsg *status;
        int i, pid, pipefd[2];
        char path[512];
        Biobuf *bp;

        pid = 0;
        if(tflag == 0){
                if(pipe(pipefd) < 0)
                        exits("pipe");
                pid = fork();
                if(pid == 0){
                        dup(pipefd[0], 0);
                        for(i = sysfiles(); i >= 3; i--)
                                close(i);
                        snprint(path, sizeof(path), "%s/qer", UPASBIN);
                        *argv=path;
                        exec(path, argv);
                        exits("exec");
                }
                Binit(&bout, pipefd[1], OWRITE);
                bp = &bout;
        } else
                bp = Bopen("/dev/null", OWRITE);

        while(n > 0){
                Bwrite(bp, buf, n);
                if(cout)
                        Bwrite(cout, buf, n);
                n = Bread(&bin, buf, sizeof(buf)-1);
        }
        Bterm(bp);
        if(cout)
                Bterm(cout);
        if(tflag)
                return 0;

        close(pipefd[1]);
        close(pipefd[0]);
        for(;;){
                status = wait();
                if(status == nil || status->pid == pid)
                        break;
                free(status);
        }
        if(status == nil)
                strcpy(buf, "wait failed");
        else{
                strcpy(buf, status->msg);
                free(status);
        }
        return buf;
}

char*
canon(Biobuf *bp, char *header, char *body, int *n)
{
        int hsize;
        char *raw;

        hsize = 0;
        *header = 0;
        *body = 0;
        raw = readmsg(bp, &hsize, n);
        if(raw){
                if(convert(raw, raw+hsize, header, Hdrsize, 0))
                        conv64(raw+hsize, raw+*n, body, Bodysize);      /* base64 */
                else
                        convert(raw+hsize, raw+*n, body, Bodysize, 1);  /* text */
        }
        return raw;
}

int
matchaction(int action, char *message, Resub *m)
{
        char *name;
        Pattern *p;

        if(message == 0 || *message == 0)
                return 0;

        name = patterns[action].action;
        p = patterns[action].strings;
        if(p)
                if(matcher(name, p, message, m))
                        return 1;

        for(p = patterns[action].regexps; p; p = p->next)
                if(matcher(name, p, message, m))
                        return 1;
        return 0;
}

int
matcher(char *action, Pattern *p, char *message, Resub *m)
{
        char *cp;
        String *s;

        for(cp = message; matchpat(p, cp, m); cp = m->ep){
                switch(p->action){
                case SaveLine:
                        if(vflag)
                                xprint(2, action, m);
                        saveline(linefile, sender, m);
                        break;
                case HoldHeader:
                case Hold:
                        if(nflag)
                                continue;
                        if(vflag)
                                xprint(2, action, m);
                        *qdir = holdqueue;
                        if(hflag && qname){
                                cp = strchr(sender, '!');
                                if(cp){
                                        *cp = 0;
                                        *qname = strdup(sender);
                                        *cp = '!';
                                } else
                                        *qname = strdup(sender);
                        }
                        return 1;
                case Dump:
                        if(vflag)
                                xprint(2, action, m);
                        *(m->ep) = 0;
                        if(!tflag){
                                s = s_new();
                                s_append(s, sender);
                                s = unescapespecial(s);
                                syslog(0, "smtpd", "Dumped %s [%s] to %s", s_to_c(s), m->sp,
                                        s_to_c(s_restart(recips)));
                                s_free(s);
                        }
                        tflag = 1;
                        if(sflag)
                                cout = opendump(sender);
                        return 1;
                default:
                        break;
                }
        }
        return 0;
}

void
saveline(char *file, char *sender, Resub *rp)
{
        char *p, *q;
        int i, c;
        Biobuf *bp;

        if(rp->sp == 0 || rp->ep == 0)
                return;
                /* back up approx 20 characters to whitespace */
        for(p = rp->sp, i = 0; *p && i < 20; i++, p--)
                        ;
        while(*p && *p != ' ')
                p--;
        p++;

                /* grab about 20 more chars beyond the end of the match */
        for(q = rp->ep, i = 0; *q && i < 20; i++, q++)
                        ;
        while(*q && *q != ' ')
                q++;

        c = *q;
        *q = 0;
        bp = sysopen(file, "al", 0644);
        if(bp){
                Bprint(bp, "%s-> %s\n", sender, p);
                Bterm(bp);
        }
        else if(debug)
                fprint(2, "can't save line: (%s) %s\n", sender, p);
        *q = c;
}

Biobuf*
opendump(char *sender)
{
        int i;
        ulong h;
        char buf[512];
        Biobuf *b;
        char *cp;

        cp = ctime(time(0));
        cp[7] = 0;
        cp[10] = 0;
        if(cp[8] == ' ')
                sprint(buf, "%s/queue.dump/%s%c", SPOOL, cp+4, cp[9]);
        else
                sprint(buf, "%s/queue.dump/%s%c%c", SPOOL, cp+4, cp[8], cp[9]);
        cp = buf+strlen(buf);
        if(access(buf, 0) < 0 && sysmkdir(buf, 0777) < 0){
                syslog(0, "smtpd", "couldn't dump mail from %s: %r", sender);
                return 0;
        }

        h = 0;
        while(*sender)
                h = h*257 + *sender++;
        for(i = 0; i < 50; i++){
                h += lrand();
                sprint(cp, "/%lud", h);
                b = sysopen(buf, "wlc", 0644);
                if(b){
                        if(vflag)
                                fprint(2, "saving in %s\n", buf);
                        return b;
                }
        }
        return 0;
}

Biobuf*
opencopy(char *sender)
{
        int i;
        ulong h;
        char buf[512];
        Biobuf *b;

        h = 0;
        while(*sender)
                h = h*257 + *sender++;
        for(i = 0; i < 50; i++){
                h += lrand();
                sprint(buf, "%s/%lud", copydir, h);
                b = sysopen(buf, "wlc", 0600);
                if(b)
                        return b;
        }
        return 0;
}

int
optoutofspamfilter(char *addr)
{
        char *p, *f;
        int rv;

        p = strchr(addr, '!');
        if(p)
                p++;
        else
                p = addr;

        rv = 0;
        f = smprint("/mail/box/%s/nospamfiltering", p);
        if(f != nil){
                rv = access(f, 0)==0;
                free(f);
        }

        return rv;
}