#include "commands.h"
#include "sig.h"
#include "auto_qmail.h"
#include "qmail.h"
#include "readwrite.h"
#include "timeoutread.h"
#include "timeoutwrite.h"
#include "stralloc.h"
#include "substdio.h"
#include "config.h"
#include "env.h"
#include "exit.h"
#include "error.h"
#include "str.h"
#include "mess822.h"
#include "tai.h"
#include "caltime.h"
#include "cdb.h"

int timeout = 1200;

int safewrite(fd,buf,len) int fd; char *buf; int len;
{
  int r;
  r = timeoutwrite(timeout,fd,buf,len);
  if (r <= 0) _exit(1);
  return r;
}

char ssoutbuf[512];
substdio ssout = SUBSTDIO_FDBUF(safewrite,1,ssoutbuf,sizeof ssoutbuf);

void flush() { substdio_flush(&ssout); }
void out(s) char *s; { substdio_puts(&ssout,s); }

void die_read() { _exit(1); }
void nomem() { out("451 out of memory (#4.3.0)\r\n"); flush(); _exit(1); }
void die_config() { out("451 unable to read configuration (#4.3.0)\r\n"); flush(); _exit(1); }
void smtp_quit() { out("221 ofmipd.local\r\n"); flush(); _exit(0); }
void smtp_help() { out("214 qmail home page: http://pobox.com/~djb/qmail.html\r\n"); }
void smtp_noop() { out("250 ok\r\n"); }
void smtp_vrfy() { out("252 send some mail, i'll try my best\r\n"); }
void smtp_unimpl() { out("502 unimplemented (#5.5.1)\r\n"); }
void err_syntax() { out("555 syntax error (#5.5.4)\r\n"); }
void err_wantmail() { out("503 MAIL first (#5.5.1)\r\n"); }
void err_wantrcpt() { out("503 RCPT first (#5.5.1)\r\n"); }
void err_qqt() { out("451 qqt failure (#4.3.0)\r\n"); }
void err_cdb() { out("451 unable to read cdb (#4.3.0)\r\n"); }

config_str rewrite = CONFIG_STR;
stralloc idappend = {0};

stralloc addr = {0}; /* will be 0-terminated, if addrparse returns 1 */
stralloc rwaddr = {0};

int addrparse(arg)
char *arg;
{
  int i;
  char ch;
  char terminator;
  int flagesc;
  int flagquoted;
 
  terminator = '>';
  i = str_chr(arg,'<');
  if (arg[i])
    arg += i + 1;
  else { /* partner should go read rfc 821 */
    terminator = ' ';
    arg += str_chr(arg,':');
    if (*arg == ':') ++arg;
    while (*arg == ' ') ++arg;
  }

  if (*arg == '@') while (*arg) if (*arg++ == ':') break;

  if (!stralloc_copys(&addr,"")) nomem();
  flagesc = 0;
  flagquoted = 0;
  for (i = 0;ch = arg[i];++i) { /* copy arg to addr, stripping quotes */
    if (flagesc) {
      if (!stralloc_append(&addr,&ch)) nomem();
      flagesc = 0;
    }
    else {
      if (!flagquoted && (ch == terminator)) break;
      switch(ch) {
        case '\\': flagesc = 1; break;
        case '"': flagquoted = !flagquoted; break;
        default: if (!stralloc_append(&addr,&ch)) nomem();
      }
    }
  }

  if (!rewritehost_addr(&rwaddr,addr.s,addr.len,config_data(&rewrite))) nomem();

  return rwaddr.len < 900;
}

char *fncdb;
int fdcdb;
stralloc cdbresult = {0};

int seenmail = 0;
char *name; /* defined if seenmail; points into cdbresult */

stralloc mailfrom = {0};
stralloc rcptto = {0};

void smtp_helo(arg) char *arg;
{
  seenmail = 0;
  out("250 ofmipd.local\r\n");
}
void smtp_ehlo(arg) char *arg;
{
  seenmail = 0;
  out("250-ofmipd.local\r\n250-PIPELINING\r\n250 8BITMIME\r\n");
}
void smtp_rset()
{
  seenmail = 0;
  out("250 flushed\r\n");
}
void smtp_mail(arg) char *arg;
{
  if (!addrparse(arg)) { err_syntax(); return; }

  name = 0;
  if (fncdb) {
    uint32 dlen;
    int r;

    r = cdb_seek(fdcdb,rwaddr.s,rwaddr.len,&dlen);
    if (r == -1) { err_cdb(); return; }
    if (r) {
      if (!stralloc_ready(&cdbresult,(unsigned int) dlen)) nomem();
      cdbresult.len = dlen;
      name = cdbresult.s;
      if (cdb_bread(fdcdb,name,cdbresult.len) == -1) { err_cdb(); return; }
      r = byte_chr(name,cdbresult.len,'\0');
      if (r == cdbresult.len) { err_cdb(); return; }
      if (!stralloc_copyb(&rwaddr,cdbresult.s + r + 1,cdbresult.len - r - 1)) nomem();
    }
  }

  if (!stralloc_copy(&mailfrom,&rwaddr)) nomem();
  if (!stralloc_0(&mailfrom)) nomem();
  if (!stralloc_copys(&rcptto,"")) nomem();
  seenmail = 1;
  out("250 ok\r\n");
}
void smtp_rcpt(arg) char *arg; {
  if (!seenmail) { err_wantmail(); return; }
  if (!addrparse(arg)) { err_syntax(); return; }
  if (!stralloc_0(&rwaddr)) nomem();
  if (!stralloc_cats(&rcptto,"T")) nomem();
  if (!stralloc_cats(&rcptto,rwaddr.s)) nomem();
  if (!stralloc_0(&rcptto)) nomem();
  out("250 ok\r\n");
}

struct qmail qqt;
void put(buf,len) char *buf; int len; { qmail_put(&qqt,buf,len); }
void puts(buf) char *buf; { qmail_puts(&qqt,buf); }

stralloc tmp = {0};
stralloc tmp2 = {0};

void rewritelist(list)
stralloc *list;
{
  if (!rewritehost_list(&tmp,list->s,list->len,config_data(&rewrite))) nomem();
  if (!stralloc_copy(list,&tmp)) nomem();
}

void putlist(name,list)
char *name;
stralloc *list;
{
  if (!list->len) return;
  if (!mess822_quotelist(&tmp,list)) nomem();
  if (!mess822_fold(&tmp2,&tmp,name,78)) nomem();
  put(tmp2.s,tmp2.len);
}

mess822_time datastart;
stralloc datastamp = {0};

mess822_time date;
stralloc to = {0};
stralloc cc = {0};
stralloc nrudt = {0};
stralloc from = {0};
stralloc headersender = {0};
stralloc replyto = {0};
stralloc mailreplyto = {0};
stralloc followupto = {0};

stralloc msgid = {0};
stralloc top = {0};
stralloc bottom = {0};

mess822_header h = MESS822_HEADER;
mess822_action a[] = {
  { "date", 0, 0, 0, 0, &date }
, { "to", 0, 0, 0, &to, 0 }
, { "cc", 0, 0, 0, &cc, 0 }
, { "notice-requested-upon-delivery-to", 0, 0, 0, &nrudt, 0 }
, { "from", 0, 0, 0, &from, 0 }
, { "sender", 0, 0, 0, &headersender, 0 }
, { "reply-to", 0, 0, 0, &replyto, 0 }
, { "mail-reply-to", 0, 0, 0, &mailreplyto, 0 }
, { "mail-followup-to", 0, 0, 0, &followupto, 0 }
, { "message-id", 0, &msgid, 0, 0, 0 }
, { "received", 0, &top, 0, 0, 0 }
, { "delivered-to", 0, &top, 0, 0, 0 }
, { "errors-to", 0, &top, 0, 0, 0 }
, { "return-receipt-to", 0, &top, 0, 0, 0 }
, { "resent-sender", 0, &top, 0, 0, 0 }
, { "resent-from", 0, &top, 0, 0, 0 }
, { "resent-reply-to", 0, &top, 0, 0, 0 }
, { "resent-to", 0, &top, 0, 0, 0 }
, { "resent-cc", 0, &top, 0, 0, 0 }
, { "resent-bcc", 0, &top, 0, 0, 0 }
, { "resent-date", 0, &top, 0, 0, 0 }
, { "resent-message-id", 0, &top, 0, 0, 0 }
, { "bcc", 0, 0, 0, 0, 0 }
, { "return-path", 0, 0, 0, 0, 0 }
, { "apparently-to", 0, 0, 0, 0, 0 }
, { "content-length", 0, 0, 0, 0, 0 }
, { 0, 0, &bottom, 0, 0, 0 }
} ;

void finishheader()
{
  if (!mess822_end(&h)) nomem();

  if (name) from.len = 0;

  rewritelist(&to);
  rewritelist(&cc);
  rewritelist(&nrudt);
  rewritelist(&from);
  rewritelist(&headersender);
  rewritelist(&replyto);
  rewritelist(&mailreplyto);
  rewritelist(&followupto);

  put(top.s,top.len);

  if (!date.known) date = datastart;
  if (!mess822_date(&tmp,&date)) nomem();
  puts("Date: ");
  put(tmp.s,tmp.len);
  puts("\n");

  if (!msgid.len) {
    static int idcounter = 0;

    if (!stralloc_copys(&msgid,"Message-ID: <")) nomem();
    if (!stralloc_catlong(&msgid,date.ct.date.year)) nomem();
    if (!stralloc_catint0(&msgid,date.ct.date.month,2)) nomem();
    if (!stralloc_catint0(&msgid,date.ct.date.day,2)) nomem();
    if (!stralloc_catint0(&msgid,date.ct.hour,2)) nomem();
    if (!stralloc_catint0(&msgid,date.ct.minute,2)) nomem();
    if (!stralloc_catint0(&msgid,date.ct.second,2)) nomem();
    if (!stralloc_cats(&msgid,".")) nomem();
    if (!stralloc_catint(&msgid,++idcounter)) nomem();
    if (!stralloc_cat(&msgid,&idappend)) nomem();
    if (!stralloc_cats(&msgid,">\n")) nomem();
  }
  put(msgid.s,msgid.len);

  putlist("From: ",&from);
  if (!from.len) {
    puts("From: ");
    if (!mess822_quote(&tmp,mailfrom.s,name)) nomem();
    put(tmp.s,tmp.len);
    puts("\n");
  }

  putlist("Sender: ",&headersender);
  putlist("Reply-To: ",&replyto);
  putlist("Mail-Reply-To: ",&mailreplyto);
  putlist("Mail-Followup-To: ",&followupto);
  if (!to.len && !cc.len)
    puts("Cc: recipient list not shown: ;\n");
  putlist("To: ",&to);
  putlist("Cc: ",&cc);
  putlist("Notice-Requested-Upon-Delivery-To: ",&nrudt);

  put(bottom.s,bottom.len);
}

int saferead(fd,buf,len) int fd; char *buf; int len;
{
  int r;
  flush();
  r = timeoutread(timeout,fd,buf,len);
  if (r <= 0) die_read();
  return r;
}

char ssinbuf[1024];
substdio ssin = SUBSTDIO_FDBUF(saferead,0,ssinbuf,sizeof ssinbuf);

stralloc line = {0};
int match;

void blast()
{
  int flagheader = 1;
  int i;

  if (!mess822_begin(&h,a)) nomem();

  for (;;) {
    if (getln(&ssin,&line,&match,'\n') == -1) die_read();
    if (!match) die_read();

    --line.len;
    if (line.len && (line.s[line.len - 1] == '\r')) --line.len;
    if (line.len && (line.s[0] == '.')) {
      --line.len;
      if (!line.len) break;
      for (i = 0;i < line.len;++i) line.s[i] = line.s[i + 1];
    }
    line.s[line.len++] = '\n';

    if (flagheader)
      if (!mess822_ok(&line)) {
        finishheader();
	flagheader = 0;
	if (line.len > 1) put("\n",1);
      }
    if (!flagheader)
      put(line.s,line.len);
    else
      if (!mess822_line(&h,&line)) nomem();
  }

  if (flagheader)
    finishheader();
}

stralloc received = {0};

void smtp_data() {
  struct tai now;
  char *qqx;

  tai_now(&now);
  caltime_utc(&datastart.ct,&now,(int *) 0,(int *) 0);
  datastart.known = 1;
  if (!mess822_date(&datastamp,&datastart)) nomem();
 
  if (!seenmail) { err_wantmail(); return; }
  if (!rcptto.len) { err_wantrcpt(); return; }
  seenmail = 0;
  if (qmail_open(&qqt) == -1) { err_qqt(); return; }
  out("354 go ahead\r\n");
 
  qmail_put(&qqt,received.s,received.len);
  qmail_put(&qqt,datastamp.s,datastamp.len);
  qmail_puts(&qqt,"\n");
  blast();
  qmail_from(&qqt,mailfrom.s);
  qmail_put(&qqt,rcptto.s,rcptto.len);
 
  qqx = qmail_close(&qqt);
  if (!*qqx) { out("250 ok\r\n"); return; }
  if (*qqx == 'D') out("554 "); else out("451 ");
  out(qqx + 1);
  out("\r\n");
}

void safecats(out,in)
stralloc *out;
char *in;
{
  char ch;
  while (ch = *in++) {
    if (ch < 33) ch = '?';
    if (ch > 126) ch = '?';
    if (ch == '(') ch = '?';
    if (ch == ')') ch = '?';
    if (ch == '@') ch = '?';
    if (ch == '\\') ch = '?';
    if (!stralloc_append(out,&ch)) nomem();
  }
}

void received_init()
{
  char *x;

  if (!stralloc_copys(&received,"Received: (ofmipd ")) nomem();
  x = env_get("TCPREMOTEINFO");
  if (x) {
    safecats(&received,x);
    if (!stralloc_append(&received,"@")) nomem();
  }
  x = env_get("TCPREMOTEIP");
  if (!x) x = "unknown";
  safecats(&received,x);
  if (!stralloc_cats(&received,"); ")) nomem();
}

struct commands smtpcommands[] = {
  { "rcpt", smtp_rcpt, 0 }
, { "mail", smtp_mail, 0 }
, { "data", smtp_data, flush }
, { "quit", smtp_quit, flush }
, { "helo", smtp_helo, flush }
, { "ehlo", smtp_ehlo, flush }
, { "rset", smtp_rset, 0 }
, { "help", smtp_help, flush }
, { "noop", smtp_noop, flush }
, { "vrfy", smtp_vrfy, flush }
, { 0, smtp_unimpl, flush }
} ;

void main(argc,argv)
int argc;
char **argv;
{
  sig_pipeignore();

  fncdb = argv[1];
  if (fncdb) {
    fdcdb = open_read(fncdb);
    if (fdcdb == -1) die_config();
  }

  received_init();
  if (leapsecs_init() == -1) die_config();
  if (chdir(auto_qmail) == -1) die_config();
  if (rwhconfig(&rewrite,&idappend) == -1) die_config();

  out("220 ofmipd.local ESMTP\r\n");
  commands(&ssin,&smtpcommands);
  nomem();
}


syntax highlighted by Code2HTML, v. 0.9.1