+#include "sgetopt.h"
+#include "substdio.h"
+#include "readwrite.h"
+#include "stralloc.h"
+#include "getln.h"
+#include "strerr.h"
+#include "error.h"
+#include "exit.h"
+#include "open.h"
+#include "wait.h"
+#include "seek.h"
+#include "env.h"
+#include "str.h"
+#include "fmt.h"
+#include "token822.h"
+#include "control.h"
+#include "qmail.h"
+#include "auto_qmail.h"
+
+#define FATAL "dot-forward: fatal: "
+#define INFO "dot-forward: info: "
+
+void die_nomem()
+{ strerr_die2x(111,FATAL,"out of memory"); }
+void die_control()
+{ strerr_die2sys(111,FATAL,"unable to read controls: "); }
+void die_qq()
+{ strerr_die2sys(111,FATAL,"unable to run qq: "); }
+void die_readmess()
+{ strerr_die2sys(111,FATAL,"unable to read message: "); }
+
+stralloc line = {0};
+
+void die_parse()
+{
+ if (!stralloc_0(&line)) die_nomem();
+ strerr_die3x(111,FATAL,"unable to parse this line: ",line.s);
+}
+
+int flagdoit = 1;
+int flagacted;
+int flagdirect;
+
+char *ufline;
+char *rpline;
+char *dtline;
+char *sender;
+char *user;
+int userlen;
+char *host;
+int hostlen;
+
+char messbuf[1024];
+substdio ssmess;
+char childbuf[1024];
+substdio sschild;
+
+int blindwrite(fd,buf,len)
+int fd; char *buf; int len;
+{
+ write(fd,buf,len);
+ return len;
+}
+
+void run(cmd)
+char *cmd;
+{
+ int child;
+ int pi[2];
+ char *args[4];
+ int wstat;
+
+ if (!flagdoit) {
+ strerr_warn2("pipe through ",cmd,0);
+ return;
+ }
+
+ if (pipe(pi) == -1)
+ strerr_die2sys(111,FATAL,"unable to create pipe: ");
+
+ switch (child = fork()) {
+ case -1:
+ strerr_die2sys(111,FATAL,"unable to fork: ");
+ case 0:
+ close(pi[1]);
+ if (fd_move(0,pi[0]) == -1)
+ strerr_die2sys(111,FATAL,"unable to set fd: ");
+ args[0] = "/bin/sh"; args[1] = "-c"; args[2] = cmd; args[3] = 0;
+ sig_pipedefault();
+ execv(*args,args);
+ strerr_die2sys(111,FATAL,"unable to run /bin/sh: ");
+ }
+
+ close(pi[0]);
+
+ substdio_fdbuf(&ssmess,read,0,messbuf,sizeof messbuf);
+ substdio_fdbuf(&sschild,blindwrite,pi[1],childbuf,sizeof childbuf);
+
+ substdio_puts(&sschild,ufline);
+ substdio_puts(&sschild,rpline);
+ substdio_puts(&sschild,dtline);
+ if (substdio_copy(&sschild,&ssmess) != 0) die_readmess();
+ substdio_flush(&sschild);
+
+ close(pi[1]);
+
+ wait_pid(&wstat,child);
+ if (wait_crashed(wstat))
+ strerr_die2x(111,FATAL,"child crashed");
+
+ switch(wait_exitcode(wstat)) {
+ case 100:
+ case 64: case 65: case 70: case 76: case 77: case 78: case 112:
+ _exit(100);
+ case 0:
+ break;
+ default:
+ _exit(111);
+ }
+
+ if (seek_begin(0) == -1)
+ strerr_die2sys(111,FATAL,"unable to rewind input: ");
+}
+
+stralloc targets = {0};
+
+stralloc me = {0};
+stralloc defaulthost = {0};
+stralloc defaultdomain = {0};
+stralloc plusdomain = {0};
+
+void readcontrols()
+{
+ int r;
+ int fddir;
+
+ fddir = open_read(".");
+ if (fddir == -1)
+ strerr_die2sys(111,FATAL,"unable to open current directory: ");
+
+ if (chdir(auto_qmail) == -1)
+ strerr_die4sys(111,FATAL,"unable to chdir to ",auto_qmail,": ");
+
+ r = control_readline(&me,"control/me");
+ if (r == -1) die_control();
+ if (!r) if (!stralloc_copys(&me,"me")) die_nomem();
+
+ r = control_readline(&defaultdomain,"control/defaultdomain");
+ if (r == -1) die_control();
+ if (!r) if (!stralloc_copy(&defaultdomain,&me)) die_nomem();
+
+ r = control_readline(&defaulthost,"control/defaulthost");
+ if (r == -1) die_control();
+ if (!r) if (!stralloc_copy(&defaulthost,&me)) die_nomem();
+
+ r = control_readline(&plusdomain,"control/plusdomain");
+ if (r == -1) die_control();
+ if (!r) if (!stralloc_copy(&plusdomain,&me)) die_nomem();
+
+ if (fchdir(fddir) == -1)
+ strerr_die2sys(111,FATAL,"unable to set current directory: ");
+}
+
+stralloc cbuf = {0};
+token822_alloc toks = {0};
+token822_alloc tokaddr = {0};
+stralloc address = {0};
+
+void gotaddr()
+{
+ int i;
+ int j;
+ int flaghasat;
+
+ token822_reverse(&tokaddr);
+ if (token822_unquote(&address,&tokaddr) != 1) die_nomem();
+
+ flaghasat = 0;
+ for (i = 0;i < tokaddr.len;++i)
+ if (tokaddr.t[i].type == TOKEN822_AT)
+ flaghasat = 1;
+
+ tokaddr.len = 0;
+
+ if (!address.len) return;
+
+ if (!flaghasat)
+ if (address.len == userlen)
+ if (!case_diffb(address.s,address.len,user)) {
+ flagacted = 1;
+ flagdirect = 1;
+ return;
+ }
+
+ if (flaghasat)
+ if (address.len == userlen + 1 + hostlen)
+ if (!case_diffb(address.s,userlen,user))
+ if (address.s[userlen] == '@')
+ if (!case_diffb(address.s + userlen + 1,hostlen,host)) {
+ flagacted = 1;
+ flagdirect = 1;
+ return;
+ }
+
+ if (!flaghasat)
+ if (address.s[0] == '/') {
+ if (!stralloc_0(&address)) die_nomem();
+ strerr_die4x(111,FATAL,"file delivery ",address.s," not supported");
+ }
+
+ if (!flaghasat)
+ if (address.s[0] == '|') {
+ if (!stralloc_0(&address)) die_nomem();
+ flagacted = 1;
+ run(address.s + 1);
+ return;
+ }
+
+ if (!flaghasat) {
+ if (!stralloc_cats(&address,"@")) die_nomem();
+ if (!stralloc_cat(&address,&defaulthost)) die_nomem();
+ }
+ if (address.s[address.len - 1] == '+') {
+ address.s[address.len - 1] = '.';
+ if (!stralloc_cat(&address,&plusdomain)) die_nomem();
+ }
+ j = 0;
+ for (i = 0;i < address.len;++i) if (address.s[i] == '@') j = i;
+ for (i = j;i < address.len;++i) if (address.s[i] == '.') break;
+ if (i == address.len) {
+ if (!stralloc_cats(&address,".")) die_nomem();
+ if (!stralloc_cat(&address,&defaultdomain)) die_nomem();
+ }
+
+ if (!stralloc_0(&address)) die_nomem();
+
+ if (!stralloc_cats(&targets,"T")) die_nomem();
+ if (!stralloc_cats(&targets,address.s)) die_nomem();
+ if (!stralloc_0(&targets)) die_nomem();
+
+ if (!flagdoit)
+ strerr_warn2("forward ",address.s,0);
+}
+
+void parseline()
+{
+ int wordok;
+ struct token822 *t;
+ struct token822 *beginning;
+ int r;
+
+ r = token822_parse(&toks,&line,&cbuf);
+ if (r == -1) die_nomem();
+ if (r == 0) die_parse();
+
+ beginning = toks.t;
+ t = toks.t + toks.len;
+ wordok = 1;
+
+ if (!token822_readyplus(&tokaddr,1)) die_nomem();
+ tokaddr.len = 0;
+
+ while (t > beginning)
+ switch((--t)->type) {
+ case TOKEN822_SEMI:
+ break; /*XXX*/
+ case TOKEN822_COLON:
+ break; /*XXX*/
+ case TOKEN822_RIGHT:
+ if (tokaddr.len) gotaddr();
+ while ((t > beginning) && (t[-1].type != TOKEN822_LEFT))
+ if (!token822_append(&tokaddr,--t)) die_nomem();
+ gotaddr();
+ if (t <= beginning) die_parse();
+ --t;
+ while ((t > beginning) && ((t[-1].type == TOKEN822_COMMENT) || (t[-1].type == TOKEN822_ATOM) || (t[-1].type == TOKEN822_QUOTE) || (t[-1].type == TOKEN822_AT) || (t[-1].type == TOKEN822_DOT)))
+ --t;
+ wordok = 0;
+ continue;
+ case TOKEN822_ATOM: case TOKEN822_QUOTE: case TOKEN822_LITERAL:
+ if (!wordok) if (tokaddr.len) gotaddr();
+ wordok = 0;
+ if (!token822_append(&tokaddr,t)) die_nomem();
+ continue;
+ case TOKEN822_COMMENT:
+ /* comment is lexically a space; shouldn't affect wordok */
+ break;
+ case TOKEN822_COMMA:
+ if (tokaddr.len) gotaddr();
+ wordok = 1;
+ break;
+ default:
+ wordok = 1;
+ if (!token822_append(&tokaddr,t)) die_nomem();
+ continue;
+ }
+ if (tokaddr.len) gotaddr();
+}
+
+struct qmail qq;
+unsigned long qp;
+char *qqx;
+char strnum[FMT_ULONG];
+
+int mywrite(fd,buf,len)
+int fd; char *buf; int len;
+{
+ qmail_put(&qq,buf,len);
+ return len;
+}
+
+char qqbuf[256];
+substdio ssqq = SUBSTDIO_FDBUF(mywrite,-1,qqbuf,sizeof qqbuf);
+
+char inbuf[256];
+
+void try(fn)
+char *fn;
+{
+ int fd;
+ int match;
+ substdio ss;
+
+ fd = open_read(fn);
+ if (fd == -1) {
+ if (errno == error_noent) return;
+ strerr_die4sys(111,FATAL,"unable to open ",fn,": ");
+ }
+
+ if (!stralloc_copys(&targets,"")) die_nomem();
+ flagacted = 0;
+ flagdirect = 0;
+
+ substdio_fdbuf(&ss,read,fd,inbuf,sizeof inbuf);
+
+ for (;;) {
+ if (getln(&ss,&line,&match,'\n') == -1)
+ strerr_die4sys(111,FATAL,"unable to read ",fn,": ");
+ if (!line.len) break;
+ if (line.s[0] != '#') parseline();
+ if (!match) break;
+ }
+
+ close(fd);
+
+ if (targets.len) {
+ flagacted = 1;
+ if (flagdoit) {
+ if (qmail_open(&qq) == -1)
+ strerr_die2sys(111,FATAL,"unable to run qmail-queue: ");
+ qp = qmail_qp(&qq);
+ qmail_puts(&qq,dtline);
+
+ substdio_fdbuf(&ssmess,read,0,messbuf,sizeof messbuf);
+ if (substdio_copy(&ssqq,&ssmess) != 0) die_readmess();
+ substdio_flush(&ssqq);
+
+ qmail_from(&qq,sender);
+ qmail_put(&qq,targets.s,targets.len);
+
+ qqx = qmail_close(&qq);
+ if (*qqx == 'D')
+ strerr_die3x(100,FATAL,"unable to forward message: ",qqx + 1);
+ if (*qqx)
+ strerr_die3x(111,FATAL,"unable to forward message: ",qqx + 1);
+ strnum[fmt_ulong(strnum,qp)] = 0;
+ strerr_warn3(INFO,"qp ",strnum,0);
+ }
+ }
+
+ if (flagdirect) {
+ if (!flagdoit) strerr_warn1("direct delivery",0);
+ _exit(0);
+ }
+ if (!flagacted) {
+ if (!flagdoit) strerr_warn2("skipping empty file ",fn,0);
+ return;
+ }
+ _exit(99);
+}
+
+void main(argc,argv)
+int argc;
+char **argv;
+{
+ int opt;
+ int fddir;
+
+ sig_pipeignore();
+
+ while ((opt = getopt(argc,argv,"nN")) != opteof)
+ switch(opt) {
+ case 'n':
+ flagdoit = 0; break;
+ case 'N':
+ flagdoit = 1; break;
+ default:
+ strerr_die1x(100,"dot-forward: usage: dot-forward [ -nN ] file ...");
+ }
+ argv += optind;
+
+ ufline = env_get("UFLINE"); if (!ufline) ufline = "";
+ rpline = env_get("RPLINE"); if (!rpline) rpline = "";
+ dtline = env_get("DTLINE"); if (!dtline) dtline = "";
+ sender = env_get("NEWSENDER"); if (!sender) sender = "";
+
+ user = env_get("USER"); if (!user) user = "";
+ userlen = str_len(user);
+ host = env_get("HOST"); if (!host) host = "";
+ hostlen = str_len(host);
+
+ readcontrols();
+
+ while (*argv)
+ try(*argv++);
+
+ if (!flagdoit) strerr_warn1("direct delivery",0);
+ _exit(0);
+}