#include "common.h"
#include <regexp.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, "usage: scanmail [-cdhnstv] [-p pattern] [-q queuename] sender dest sys\n");
       exits("usage");
}

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

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

       p = malloc(n);
       if(p == nil)
               sysfatal("malloc: %r");
       return p;
}

void*
Realloc(void *p, ulong n)
{
       p = realloc(p, n);
       if(p == nil)
               sysfatal("malloc: %r");
       setrealloctag(p, getcallerpc(&p));
       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*));
       snprint(patfile, sizeof patfile, "%s/patterns", UPASLIB);
       snprint(linefile, sizeof linefile, "%s/lines", UPASLOG);
       snprint(holdqueue, sizeof holdqueue, "%s/queue.hold", SPOOL);
       snprint(copydir, sizeof 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] == ' ')
               snprint(buf, sizeof buf, "%s/queue.dump/%s%c", SPOOL, cp+4, cp[9]);
       else
               snprint(buf, sizeof 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();
               seprint(cp, buf+sizeof buf, "/%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();
               snprint(buf, sizeof 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;
}