nightmaremail

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

qmail-smtpd.c (13654B)


      1 #include "sig.h"
      2 #include "readwrite.h"
      3 #include "stralloc.h"
      4 #include "substdio.h"
      5 #include "alloc.h"
      6 #include "auto_qmail.h"
      7 #include "control.h"
      8 #include "datetime.h"
      9 #include "received.h"
     10 #include "constmap.h"
     11 #include "error.h"
     12 #include "ipme.h"
     13 #include "ip.h"
     14 #include "qmail.h"
     15 #include "str.h"
     16 #include "fmt.h"
     17 #include "scan.h"
     18 #include "byte.h"
     19 #include "case.h"
     20 #include "env.h"
     21 #include "now.h"
     22 #include "exit.h"
     23 #include "rcpthosts.h"
     24 #include "realrcptto.h"
     25 #include "timeoutread.h"
     26 #include "timeoutwrite.h"
     27 #include "commands.h"
     28 #include "dns.h"
     29 
     30 #define MAXHOPS 100
     31 unsigned int databytes = 0;
     32 int timeout = 1200;
     33 
     34 GEN_SAFE_TIMEOUTWRITE(safewrite,timeout,fd,_exit(1))
     35 
     36 char ssoutbuf[512];
     37 substdio ssout = SUBSTDIO_FDBUF(safewrite,1,ssoutbuf,sizeof(ssoutbuf));
     38 
     39 void flush() { substdio_flush(&ssout); }
     40 void out(s) char *s; { substdio_puts(&ssout,s); }
     41 
     42 void die_read() { _exit(1); }
     43 void die_alarm() { out("451 4.4.2 Timeout\r\n"); flush(); _exit(1); }
     44 void die_nomem() { out("421 4.3.0 Out of memory\r\n"); flush(); _exit(1); }
     45 void die_control() { out("421 4.3.0 Unable to read controls\r\n"); flush(); _exit(1); }
     46 void die_ipme() { out("421 4.3.0 Unable to figure out my IP addresses\r\n"); flush(); _exit(1); }
     47 void die_ip6me() { out("421 4.3.0 Unable to figure out my IPv6 addresses\r\n"); flush(); _exit(1); }
     48 void straynewline() { out("451 You sent a stray newline. See https://cr.yp.to/docs/smtplf.html.\r\n"); flush(); _exit(1); }
     49 
     50 void err_bmf() { out("553 5.7.1 Sorry, your envelope sender is in my badmailfrom list.\r\n"); }
     51 void err_nogateway() { out("553 5.7.1 Sorry, that domain isn't in my list of allowed rcpthosts\r\n"); }
     52 void err_unimpl(char *arg) { out("502 5.5.1 Unimplemented\r\n"); }
     53 void err_syntax() { out("555 5.5.4 Syntax error\r\n"); }
     54 void err_wantmail() { out("503 5.5.1 You must execute MAIL first\r\n"); }
     55 void err_wantrcpt() { out("503 5.5.1 You must execute RCPT first\r\n"); }
     56 void die_cdb() { out("421 4.3.0 Unable to read cdb user database\r\n"); flush(); _exit(1); }
     57 void die_sys() { out("421 4.3.0 Unable to read system user database\r\n"); flush(); _exit(1); }
     58 void err_noop(char *arg) { out("250 As requested, no operation performed.\r\n"); }
     59 void err_vrfy(char *arg) { out("252 Send something and we'll give it a shot.\r\n"); }
     60 void err_qqt() { out("451 4.3.0 qqt failure\r\n"); }
     61 void die_dnsbl(char *arg)
     62 {
     63   out("421 your ip is currently blacklisted, try to auth first ("); out(arg); out(")\r\n");
     64   flush();
     65   _exit(1);
     66 }
     67 
     68 stralloc greeting = {0};
     69 
     70 void smtp_greet(code) char *code;
     71 {
     72   substdio_puts(&ssout,code);
     73   substdio_put(&ssout,greeting.s,greeting.len);
     74 }
     75 void smtp_help(char *arg)
     76 {
     77   out("214 NightmareMail home page: <https://github.com./SystemLeningrad/mxf> MXF is based on notqmail. notqmail home page: <https://notqmail.org>\r\n");
     78 }
     79 void smtp_quit(char *arg)
     80 {
     81   smtp_greet("221 "); out("\r\n"); flush(); _exit(0);
     82 }
     83 
     84 char *remoteip;
     85 char *remotehost;
     86 char *remoteinfo;
     87 char *local;
     88 char *relayclient;
     89 char *dnsblskip;
     90 
     91 stralloc helohost = {0};
     92 char *fakehelo; /* pointer into helohost, or 0 */
     93 
     94 void dohelo(char *arg) {
     95   if (!stralloc_copys(&helohost,arg)) die_nomem(); 
     96   if (!stralloc_0(&helohost)) die_nomem(); 
     97   fakehelo = case_diffs(remotehost,helohost.s) ? helohost.s : 0;
     98 }
     99 
    100 int liphostok = 0;
    101 stralloc liphost = {0};
    102 int bmfok = 0;
    103 stralloc bmf = {0};
    104 struct constmap mapbmf;
    105 
    106 void setup()
    107 {
    108   char *x;
    109   unsigned long u;
    110  
    111   if (control_init() == -1) die_control();
    112   if (control_rldef(&greeting,"control/smtpgreeting",1,NULL) != 1)
    113     die_control();
    114   liphostok = control_rldef(&liphost,"control/localiphost",1,NULL);
    115   if (liphostok == -1) die_control();
    116   if (control_readint(&timeout,"control/timeoutsmtpd") == -1) die_control();
    117   if (timeout <= 0) timeout = 1;
    118 
    119   if (rcpthosts_init() == -1) die_control();
    120 
    121   bmfok = control_readfile(&bmf,"control/badmailfrom",0);
    122   if (bmfok == -1) die_control();
    123   if (bmfok)
    124     if (!constmap_init(&mapbmf,bmf.s,bmf.len,0)) die_nomem();
    125 
    126   // manually merged from prj's realrcptto patch
    127   realrcptto_init();
    128 
    129   if (control_readint(&databytes,"control/databytes") == -1) die_control();
    130   x = env_get("DATABYTES");
    131   if (x) { scan_ulong(x,&u); databytes = u; }
    132   if (!(databytes + 1)) --databytes;
    133  
    134   remoteip = env_get("TCPREMOTEIP");
    135   if (!remoteip) remoteip = "unknown";
    136   local = env_get("TCPLOCALHOST");
    137   if (!local) local = env_get("TCPLOCALIP");
    138   if (!local) local = "unknown";
    139   remotehost = env_get("TCPREMOTEHOST");
    140   if (!remotehost) remotehost = "unknown";
    141   remoteinfo = env_get("TCPREMOTEINFO");
    142   relayclient = env_get("RELAYCLIENT");
    143   dnsblskip = env_get("DNSBLSKIP");
    144   dohelo(remotehost);
    145 }
    146 
    147 extern void realrcptto_init();
    148 extern void realrcptto_start();
    149 extern int realrcptto();
    150 extern int realrcptto_deny();
    151 
    152 stralloc addr = {0}; /* will be 0-terminated, if addrparse returns 1 */
    153 
    154 int addrparse(char *arg)
    155 {
    156   int i;
    157   char ch;
    158   char terminator;
    159   struct ip_address ip;
    160   int flagesc;
    161   int flagquoted;
    162 
    163   terminator = '>';
    164   i = str_chr(arg,'<');
    165   if (arg[i])
    166     arg += i + 1;
    167   else { /* partner should go read rfc 821 */
    168     terminator = ' ';
    169     arg += str_chr(arg,':');
    170     if (*arg == ':') ++arg;
    171     while (*arg == ' ') ++arg;
    172   }
    173 
    174   /* strip source route */
    175   if (*arg == '@') while (*arg) if (*arg++ == ':') break;
    176 
    177   if (!stralloc_copys(&addr,"")) die_nomem();
    178   flagesc = 0;
    179   flagquoted = 0;
    180   for (i = 0;(ch = arg[i]);++i) { /* copy arg to addr, stripping quotes */
    181     if (flagesc) {
    182       if (!stralloc_append(&addr,&ch)) die_nomem();
    183       flagesc = 0;
    184     }
    185     else {
    186       if (!flagquoted && (ch == terminator)) break;
    187       switch(ch) {
    188         case '\\': flagesc = 1; break;
    189         case '"': flagquoted = !flagquoted; break;
    190         default: if (!stralloc_append(&addr,&ch)) die_nomem();
    191       }
    192     }
    193   }
    194   /* could check for termination failure here, but why bother? */
    195   if (!stralloc_append(&addr,"")) die_nomem();
    196 
    197   if (liphostok) {
    198     i = byte_rchr(addr.s,addr.len,'@');
    199     if (i < addr.len) /* if not, partner should go read rfc 821 */
    200       if (addr.s[i + 1] == '[')
    201         if (!addr.s[i + 1 + ip_scanbracket(addr.s + i + 1,&ip)])
    202           if (ipme_is(&ip)) {
    203             addr.len = i + 1;
    204             if (!stralloc_cat(&addr,&liphost)) die_nomem();
    205             if (!stralloc_0(&addr)) die_nomem();
    206           }
    207   }
    208 
    209   if (addr.len > 900) return 0;
    210   return 1;
    211 }
    212 
    213 int bmfcheck()
    214 {
    215   int j;
    216   if (!bmfok) return 0;
    217   if (constmap(&mapbmf,addr.s,addr.len - 1)) return 1;
    218   j = byte_rchr(addr.s,addr.len,'@');
    219   if (j < addr.len)
    220     if (constmap(&mapbmf,addr.s + j,addr.len - j - 1)) return 1;
    221   return 0;
    222 }
    223 
    224 int addrallowed()
    225 {
    226   int r;
    227   r = rcpthosts(addr.s,str_len(addr.s));
    228   if (r == -1) die_control();
    229   return r;
    230 }
    231 
    232 int flagdnsbl = 0;
    233 stralloc dnsblhost = {0};
    234 
    235 int dnsblcheck()
    236 {
    237   char *ch;
    238   static stralloc dnsblbyte = {0};
    239   static stralloc dnsblrev = {0};
    240   static ipalloc dnsblip = {0};
    241   static ip6alloc dnsblip6 = {0};
    242   static stralloc dnsbllist = {0};
    243 
    244   ch = remoteip;
    245   if(control_readfile(&dnsbllist,"control/dnsbllist",0) != 1) return 0;
    246 
    247   if (!stralloc_copys(&dnsblrev,"")) return 0;
    248   for (;;) {
    249     if (!stralloc_copys(&dnsblbyte,"")) return 0;
    250     while (ch[0] && (ch[0] != '.')) {
    251       if (!stralloc_append(&dnsblbyte,ch)) return 0;
    252       ch++;
    253     }
    254     if (!stralloc_append(&dnsblbyte,".")) return 0;
    255     if (!stralloc_cat(&dnsblbyte,&dnsblrev)) return 0;
    256     if (!stralloc_copy(&dnsblrev,&dnsblbyte)) return 0;
    257 
    258     if (!ch[0]) break;
    259     ch++;
    260   }
    261 
    262   flagdnsbl = 1;
    263   ch = dnsbllist.s;
    264   while (ch < (dnsbllist.s + dnsbllist.len)) {
    265     if (!stralloc_copy(&dnsblhost,&dnsblrev)) return 0;
    266     if (!stralloc_cats(&dnsblhost,ch)) return 0;
    267     if (!stralloc_0(&dnsblhost)) return 0;
    268 
    269     if (!dns_ip(&dnsblip,&dnsblhost)) return 1;
    270     while (*ch++);
    271   }
    272 
    273   return 0;
    274 }
    275 
    276 int seenmail = 0;
    277 int flagbarf; /* defined if seenmail */
    278 stralloc mailfrom = {0};
    279 stralloc rcptto = {0};
    280 
    281 void smtp_helo(char *arg)
    282 {
    283   smtp_greet("250 "); out("\r\n");
    284   seenmail = 0; dohelo(arg);
    285 }
    286 void smtp_ehlo(char *arg)
    287 {
    288   smtp_greet("250-"); out("\r\n250-PIPELINING\r\n250-ENHANCEDSTATUSCODES\r\n");
    289   // TODO: add startTLS cutover support
    290   out("250 8BITMIME\r\n");
    291   seenmail = 0; dohelo(arg);
    292 }
    293 void smtp_rset(char *arg)
    294 {
    295   seenmail = 0;
    296   out("250 flushed\r\n");
    297 }
    298 void smtp_mail(char *arg)
    299 {
    300   if (!addrparse(arg)) { err_syntax(); return; }
    301   flagbarf = bmfcheck();
    302   seenmail = 1;
    303   if (!stralloc_copys(&rcptto,"")) die_nomem();
    304   if (!stralloc_copys(&mailfrom,addr.s)) die_nomem();
    305   if (!stralloc_0(&mailfrom)) die_nomem();
    306   realrcptto_start();
    307   out("250 ok\r\n");
    308 }
    309 void smtp_rcpt(char *arg) {
    310   if (!seenmail) { err_wantmail(); return; }
    311   if (!addrparse(arg)) { err_syntax(); return; }
    312   if (flagbarf) { err_bmf(); return; }
    313   if (relayclient) {
    314     --addr.len;
    315     if (!stralloc_cats(&addr,relayclient)) die_nomem();
    316     if (!stralloc_0(&addr)) die_nomem();
    317   }
    318   else
    319     if (!addrallowed()) { err_nogateway(); return; }
    320   if (!realrcptto(addr.s)) {
    321     out("550 5.1.1 That user has elected not to receive emails.\r\n");
    322     return;
    323   }
    324   if (!(relayclient || dnsblskip || flagdnsbl))
    325     if (dnsblcheck()) die_dnsbl(dnsblhost.s);
    326   if (!stralloc_cats(&rcptto,"T")) die_nomem();
    327   if (!stralloc_cats(&rcptto,addr.s)) die_nomem();
    328   if (!stralloc_0(&rcptto)) die_nomem();
    329   out("250 ok\r\n");
    330 }
    331 
    332 ssize_t saferead(int fd, void *buf, size_t len)
    333 {
    334   ssize_t r;
    335   flush();
    336   r = timeoutread(timeout,fd,buf,len);
    337   if (r == -1) if (errno == error_timeout) die_alarm();
    338   if (r == 0 || r == -1) die_read();
    339   return r;
    340 }
    341 
    342 char ssinbuf[1024];
    343 substdio ssin = SUBSTDIO_FDBUF(saferead,0,ssinbuf,sizeof(ssinbuf));
    344 
    345 struct qmail qqt;
    346 unsigned int bytestooverflow = 0;
    347 
    348 void put(ch)
    349 char *ch;
    350 {
    351   if (bytestooverflow)
    352     if (!--bytestooverflow)
    353       qmail_fail(&qqt);
    354   qmail_put(&qqt,ch,1);
    355 }
    356 
    357 void blast(hops)
    358 int *hops;
    359 {
    360   char ch;
    361   int state;
    362   int flaginheader;
    363   int pos; /* number of bytes since most recent \n, if fih */
    364   int flagmaybex; /* 1 if this line might match RECEIVED, if fih */
    365   int flagmaybey; /* 1 if this line might match \r\n, if fih */
    366   int flagmaybez; /* 1 if this line might match DELIVERED, if fih */
    367  
    368   state = 1;
    369   *hops = 0;
    370   flaginheader = 1;
    371   pos = 0; flagmaybex = flagmaybey = flagmaybez = 1;
    372   for (;;) {
    373 	/* This isn't very optimized -- Amelia B */
    374     substdio_get(&ssin,&ch,1);
    375     if (flaginheader) {
    376       if (pos < 9) {
    377         if (ch != "delivered"[pos]) if (ch != "DELIVERED"[pos]) flagmaybez = 0;
    378         if (flagmaybez) if (pos == 8) ++*hops;
    379         if (pos < 8)
    380           if (ch != "received"[pos]) if (ch != "RECEIVED"[pos]) flagmaybex = 0;
    381         if (flagmaybex) if (pos == 7) ++*hops;
    382         if (pos < 2) if (ch != "\r\n"[pos]) flagmaybey = 0;
    383         if (flagmaybey) if (pos == 1) flaginheader = 0;
    384 	++pos;
    385       }
    386       if (ch == '\n') { pos = 0; flagmaybex = flagmaybey = flagmaybez = 1; }
    387     }
    388     switch(state) {
    389       case 0:
    390         if (ch == '\n') straynewline();
    391         if (ch == '\r') { state = 4; continue; }
    392         break;
    393       case 1: /* \r\n */
    394         if (ch == '\n') straynewline();
    395         if (ch == '.') { state = 2; continue; }
    396         if (ch == '\r') { state = 4; continue; }
    397         state = 0;
    398         break;
    399       case 2: /* \r\n + . */
    400         if (ch == '\n') straynewline();
    401         if (ch == '\r') { state = 3; continue; }
    402         state = 0;
    403         break;
    404       case 3: /* \r\n + .\r */
    405         if (ch == '\n') return;
    406         put(".");
    407         put("\r");
    408         if (ch == '\r') { state = 4; continue; }
    409         state = 0;
    410         break;
    411       case 4: /* + \r */
    412         if (ch == '\n') { state = 1; break; }
    413         if (ch != '\r') { put("\r"); state = 0; }
    414     }
    415     put(&ch);
    416   }
    417 }
    418 
    419 char accept_buf[FMT_ULONG];
    420 void acceptmessage(qp) unsigned long qp;
    421 {
    422   datetime_sec when;
    423   when = now();
    424   out("250 ok ");
    425   accept_buf[fmt_ulong(accept_buf,(unsigned long) when)] = 0;
    426   out(accept_buf);
    427   out(" qp ");
    428   accept_buf[fmt_ulong(accept_buf,qp)] = 0;
    429   out(accept_buf);
    430   out("\r\n");
    431 }
    432 
    433 void smtp_data(char *arg) {
    434   int hops;
    435   unsigned long qp;
    436   char *qqx;
    437  
    438   if (!seenmail) { err_wantmail(); return; }
    439   if (!rcptto.len) { err_wantrcpt(); return; }
    440   if (realrcptto_deny()) { out("554 5.1.1 sorry, no mailbox here by that name.\r\n"); return; }
    441   seenmail = 0;
    442   if (databytes) bytestooverflow = databytes + 1;
    443   if (qmail_open(&qqt) == -1) { err_qqt(); return; }
    444   qp = qmail_qp(&qqt);
    445   out("354 go ahead\r\n");
    446  
    447   received(&qqt,"SMTP",local,remoteip,remotehost,remoteinfo,fakehelo);
    448   blast(&hops);
    449   hops = (hops >= MAXHOPS);
    450   if (hops) qmail_fail(&qqt);
    451   qmail_from(&qqt,mailfrom.s);
    452   qmail_put(&qqt,rcptto.s,rcptto.len);
    453  
    454   qqx = qmail_close(&qqt);
    455   if (!*qqx) { acceptmessage(qp); return; }
    456   if (hops) { out("554 5.4.6 too many hops, this message is looping\r\n"); return; }
    457   if (databytes) if (!bytestooverflow) { out("552 5.3.4 sorry, that message size exceeds my databytes limit\r\n"); return; }
    458   if (*qqx == 'D') out("554 "); else out("451 ");
    459   out(qqx + 1);
    460   out("\r\n");
    461 }
    462 
    463 struct commands smtpcommands[] = {
    464   { "rcpt", smtp_rcpt, 0 }
    465 , { "mail", smtp_mail, 0 }
    466 , { "data", smtp_data, flush }
    467 , { "quit", smtp_quit, flush }
    468 , { "helo", smtp_helo, flush }
    469 , { "ehlo", smtp_ehlo, flush }
    470 , { "rset", smtp_rset, 0 }
    471 , { "help", smtp_help, flush }
    472 , { "noop", err_noop, flush }
    473 , { "vrfy", err_vrfy, flush }
    474 , { 0, err_unimpl, flush }
    475 } ;
    476 
    477 int main(void)
    478 {
    479   sig_pipeignore();
    480   if (chdir(auto_qmail) == -1) die_control();
    481   setup();
    482   if (ipme_init() != 1) die_ipme();
    483   if (ip6me_init() != 1) die_ip6me();
    484   smtp_greet("220 ");
    485   out(" ESMTP\r\n");
    486   if (commands(&ssin,&smtpcommands) == 0) die_read();
    487   die_nomem();
    488 }