#include <sys/types.h>
#include <sys/stat.h>
#include "subgetopt.h"
#include "stralloc.h"
#include "substdio.h"
#include "subfd.h"
#include "getline.h"
#include "token822.h"
#include "parse.h"
#include "env.h"
#include "case.h"
#include "exit.h"
#include "alloc.h"
#include "open.h"
#include "error.h"
#include "sig.h"
#include "readwrite.h"
#include "qqtalk.h"
#include "strset.h"
#include "control.h"
#include "conf-mailhome.h"
#include "conf-home.h"
#include "cdb.h"

char printbuf[1024];
static struct substdio print = SUBSTDIO_FDBUF(write,1,printbuf,1024);

void perm() { _exit(1); }
void temp() { _exit(111); }

void die_nomem() {
 substdio_putsflush(subfderr,"qmsmac: fatal: out of memory\n"); temp(); }
void die_parse() {
 substdio_putsflush(subfderr,"qmsmac: fatal: parse problem\n"); temp(); }
void die_opendb() {
 substdio_putsflush(subfderr,"qmsmac: fatal: unable to open etc/compiled\n");
 temp(); }
void die_readdb() {
 substdio_putsflush(subfderr,"qmsmac: fatal: unable to read etc/compiled\n");
 temp(); }
void die_readctl() {
 substdio_putsflush(subfderr,"qmsmac: fatal: trouble reading controls\n");
 temp(); }
void die_chdir() {
 substdio_putsflush(subfderr,"qmsmac: fatal: unable to chdir\n"); temp(); }
void die_fork() {
 substdio_putsflush(subfderr,"qmsmac: fatal: unable to fork\n"); temp(); }
void die_readmess() {
 substdio_putsflush(subfderr,"qmsmac: fatal: unable to read message\n"); temp(); }
void die_qqtemp() {
 substdio_putsflush(subfderr,"qmsmac: fatal: temporary qmail-queue error\n"); temp(); }
void die_openincl(fn) char *fn; {
 substdio_puts(subfderr,"qmsmac: fatal: unable to open include file ");
 substdio_puts(subfderr,fn);
 substdio_puts(subfderr,"\n");
 substdio_flush(subfderr);
 temp();
}
void die_readincl(fn) char *fn; {
 if (errno == error_nomem) die_nomem();
 substdio_puts(subfderr,"qmsmac: fatal: trouble reading include file ");
 substdio_puts(subfderr,fn);
 substdio_puts(subfderr,"\n");
 substdio_flush(subfderr);
 temp();
}
void die_permincl(fn) char *fn; {
 substdio_puts(subfderr,"qmsmac: fatal: need mode at least 0444 on include file ");
 substdio_puts(subfderr,fn);
 substdio_puts(subfderr,"\n");
 substdio_flush(subfderr);
 temp();
}
void die_parseincl(sa) stralloc *sa; {
 substdio_puts(subfderr,"qmsmac: fatal: unable to parse the following line");
 substdio_put(subfderr,sa->s,sa->len);
 substdio_flush(subfderr);
 temp();
}
void perm_norecip() {
 substdio_putsflush(subfderr,"qmsmac: fatal: RECIPIENT not set\n"); perm(); }
void perm_nosender() {
 substdio_putsflush(subfderr,"qmsmac: fatal: SENDER not set\n"); perm(); }
void perm_nodtline() {
 substdio_putsflush(subfderr,"qmsmac: fatal: DTLINE not set\n"); perm(); }
void perm_bounce() {
 substdio_putsflush(subfderr,"qmsmac: fatal: no such address\n"); perm(); }
void perm_qqperm() {
 substdio_putsflush(subfderr,"qmsmac: fatal: permanent qmail-queue error\n"); perm(); }

struct qqtalk qqt;

stralloc output = {0};
stralloc todo = {0};
stralloc files = {0};

strset expandedset;

stralloc foo = {0};
stralloc newsender = {0};

substdio ssin;
substdio ssout;
char inbuf[1024];
char outbuf[16];

stralloc line = {0};
stralloc cbuf = {0};
token822_alloc toks = {0};

int mywrite(fd,buf,len) int fd; char *buf; int len;
{
 qqtalk_put(&qqt,buf,len);
 return len;
}

int addtodo(buf,len)
char *buf;
unsigned int len;
{
 int i;
 int j;

 i = 0;
 while (i < len)
  {
   for (j = i;j < len;++j) if (!buf[j]) break;
   if (j == len) return -1;
   switch(buf[i])
    {
     case 'R':
       if (!stralloc_cats(&todo,buf + i + 1)) die_nomem();
       if (!stralloc_0(&todo)) die_nomem();
       break;
     case 'I':
       if (!stralloc_cats(&files,buf + i + 1)) die_nomem();
       if (!stralloc_0(&files)) die_nomem();
       break;
     default:
       return -1;
    }
   i = j + 1;
  }
 return 0;
}

stralloc data = {0};

void main(argc,argv)
int argc;
char **argv;
{
 uint32 dlen;
 int fdcdb;
 char *recipient;
 char *sender;
 char *dtline;
 int r;
 int i;
 int flagfirst;
 int fd;
 struct stat st;
 int match;
 int opt;
 int flagdoit;
 char *expandee;

 sig_pipeignore();

 flagdoit = 1;
 while ((opt = sgopt(argc,argv,"nN")) != sgoptdone)
   switch(opt)
    {
     case 'n': flagdoit = 0; break;
     case 'N': flagdoit = 1; break;
    }
 argc -= sgoptind;
 argv += sgoptind;

 if (chdir(CONF_MAILHOME) == -1) die_chdir();
 if (control_init() == -1) die_readctl();
 if (control_rldef(&parse_defaultdomain,"control/defaultdomain",1,"defaultdomain") != 1) die_readctl();
 if (control_rldef(&parse_defaulthost,"control/defaulthost",1,"defaulthost") != 1) die_readctl();
 if (control_rldef(&parse_plusdomain,"control/plusdomain",1,"plusdomain") != 1) die_readctl();

 if (chdir(CONF_HOME) == -1) die_chdir();

 fdcdb = open_read("etc/compiled");
 if (fdcdb == -1) die_opendb();

 recipient = env_get("RECIPIENT");
 if (!recipient) perm_norecip();

 sender = env_get("SENDER");
 if (!sender) perm_nosender();
 dtline = env_get("DTLINE");
 if (!dtline) perm_nodtline();

 if (!stralloc_copys(&output,"")) die_nomem();
 if (!stralloc_copys(&todo,"")) die_nomem();
 if (!stralloc_copys(&files,"")) die_nomem();

 if (!strset_init(&expandedset)) die_nomem();

 if (!stralloc_cats(&todo,recipient)) die_nomem();
 if (!stralloc_0(&todo)) die_nomem();

 if (!stralloc_copys(&newsender,"owner-")) die_nomem();
 if (!stralloc_cats(&newsender,recipient)) die_nomem();

 r = cdb_seek(fdcdb,newsender.s,newsender.len,&dlen);
 if (r == -1) die_readdb();
 if (r == 1)
  {
   if (!stralloc_0(&newsender)) die_nomem();
   sender = newsender.s;
  }

 flagfirst = 1;
 while (todo.len || files.len)
  {
   if (todo.len)
    {
     i = todo.len - 1;
     while (i > 0) if (!todo.s[i - 1]) break; else --i;
     todo.len = i;

     if (strset_in(&expandedset,todo.s + i)) continue;

     expandee = alloc(str_len(todo.s + i) + 1);
     if (!expandee) die_nomem();
     str_copy(expandee,todo.s + i);
     if (!strset_add(&expandedset,expandee)) die_nomem();

     if (!stralloc_copys(&foo,"owner-")) die_nomem();
     if (!stralloc_cats(&foo,todo.s + i)) die_nomem();
     case_lowerb(foo.s,foo.len);

     r = cdb_seek(fdcdb,foo.s,foo.len,&dlen);
     if (r == -1) die_readdb();
     if (flagfirst || (r == 0))
      {
       r = cdb_seek(fdcdb,foo.s + 6,foo.len - 6,&dlen);
       if (r == -1) die_readdb();
       if (r == 0) {
	 int j;
	 j = byte_rchr(foo.s,foo.len,'@');
	 if (j < foo.len) {
	   r = cdb_seek(fdcdb,foo.s + j,foo.len - j,&dlen);
	   if (r == -1) die_readdb();
	 }
       }
       if (flagfirst && (r == 0)) perm_bounce();
       if (r == 1)
	{
	 if (!stralloc_ready(&data,(unsigned int) dlen)) die_nomem();
	 data.len = dlen;
	 if (cdb_bread(fdcdb,data.s,data.len) == -1) die_readdb();
	 if (addtodo(data.s,data.len) == -1) die_readdb(); /* malformatted */

	 flagfirst = 0;
	 continue;
	}
      }

     if (!stralloc_cats(&output,todo.s + i)) die_nomem();
     if (!stralloc_0(&output)) die_nomem();

     flagfirst = 0;
     continue;
    }
   if (files.len)
    {
     i = files.len - 1;
     while (i > 0) if (!files.s[i - 1]) break; else --i;

     fd = open_read(files.s + i);
     files.len = i;

     if (fd == -1) die_openincl(files.s + i);
     if (fstat(fd,&st) == -1) die_openincl(files.s + i);
     if ((st.st_mode & 0444) != 0444) die_permincl(files.s + i);

     substdio_fdbuf(&ssin,read,fd,inbuf,sizeof(inbuf));

     for (;;)
      {
       if (getline2(&ssin,&line,&match,'\n') == -1) die_readincl(files.s + i);
       if (!match) break;
       if (line.s[0] == '#') continue;

       r = token822_parse(&toks,&line,&cbuf);
       if (r == -1) die_nomem();
       if (r == 0) die_parseincl(&line);

       r = parse(&toks);
       if (r == 0) die_nomem();
       if (r == -1) die_parseincl(&line);

       if (parse_flagalias)
        {
         if (!stralloc_cats(&parse_recips,"R")) die_nomem();
         if (!stralloc_cat(&parse_recips,&parse_alias)) die_nomem();
         if (!stralloc_0(&parse_recips)) die_nomem();
        }

       if (addtodo(parse_recips.s,parse_recips.len) == -1)
	 die_parseincl(&line); /* not possible */
      }

     close(fd);
    }
  }
 
 if (!flagdoit)
  {
   substdio_puts(&print,"from <");
   substdio_puts(&print,sender);
   substdio_puts(&print,">\n");
   while (output.len)
    {
     i = output.len - 1;
     while (i > 0) if (!output.s[i - 1]) break; else --i;
     substdio_puts(&print,"to <");
     substdio_puts(&print,output.s + i);
     substdio_puts(&print,">\n");
     output.len = i;
    }
   substdio_flush(&print);
   _exit(0);
  }

 if (qqtalk_open(&qqt,1) == -1) die_fork();
 qqtalk_puts(&qqt,dtline);
 substdio_fdbuf(&ssin,read,0,inbuf,sizeof(inbuf));
 substdio_fdbuf(&ssout,mywrite,-1,outbuf,sizeof(outbuf));
 if (substdio_copy(&ssout,&ssin) != 0) die_readmess();
 substdio_flush(&ssout);

 qqtalk_from(&qqt,sender);
 while (output.len)
  {
   i = output.len - 1;
   while (i > 0) if (!output.s[i - 1]) break; else --i;
   qqtalk_to(&qqt,output.s + i);
   output.len = i;
  }
 switch(qqtalk_close(&qqt))
  {
   case 0: _exit(0);
   case QQT_TOOLONG: perm_qqperm();
   default: die_qqtemp();
  }
}
