accept more TLS versions, ignore zero return
[ghsmtp.git] / jnk / smtp.rl
blob71a006de8e45fa2ba2481dc6d60383a9d545907d
1 #include "Session.hpp"
3 using std::experimental::string_view;
4 using std::string;
6 constexpr size_t BUFSIZE = 10 * 4 * 1024;
8 %%{
9 machine smtp;
11 action mb_loc_beg {
12   mb_loc_beg = fpc;
15 action mb_loc_end {
16   CHECK_NOTNULL(mb_loc_beg);
18   mb_loc_end = fpc;
19   mb_loc = string(mb_loc_beg, mb_loc_end - mb_loc_beg);
21   mb_loc_beg = nullptr;
22   mb_loc_end = nullptr;
25 action mb_dom_beg {
26   mb_dom_beg = fpc;
29 action mb_dom_end {
30   CHECK_NOTNULL(mb_dom_beg);
32   mb_dom_end = fpc;
33   mb_dom = string(mb_dom_beg, mb_dom_end - mb_dom_beg);
35   mb_dom_beg = nullptr;
36   mb_dom_end = nullptr;
39 action magic_postmaster {
40   mb_loc = string("Postmaster");
41   mb_dom.clear();
44 action key_end {
45   param.first += static_cast<char>(::toupper(fc));
48 action val_end {
49   param.second += fc;
52 action param {
53   parameters.insert(param);
54   param.first.clear();
55   param.second.clear();
58 # action last {
59 #   last = true;
60 # }
62 # action chunk_size_beg {
63 #   chunk_sz_beg = fpc;
64 # }
66 # action chunk_size_end {
67 #   chunk_sz_end = fpc;
68 #   chunk_sz = stoll(string(chunk_sz_beg, chunk_sz_end - chunk_sz_beg));
69 #   chunk_sz_beg = nullptr;
70 #   chunk_sz_end = nullptr;
71 # }
73 #############################################################################
75 UTF8_tail = 0x80..0xBF;
77 UTF8_1 = 0x00..0x7F;
79 UTF8_2 = 0xC2..0xDF UTF8_tail;
81 UTF8_3 = (0xE0 0xA0..0xBF UTF8_tail)
82        | (0xE1..0xEC UTF8_tail{2})
83        | (0xED 0x80..0x9F UTF8_tail)
84        | (0xEE..0xEF UTF8_tail{2})
85        ;
87 UTF8_4 = (0xF0 0x90..0xBF UTF8_tail{2})
88        | (0xF1..0xF3 UTF8_tail{3})
89        | (0xF4 0x80..0x8F UTF8_tail{2})
90        ;
92 # UTF8_char = UTF8_1 | UTF8_2 | UTF8_3 | UTF8_4;
94 UTF8_non_ascii = UTF8_2 | UTF8_3 | UTF8_4;
96 # various definitions from RFC 5234
98 CR = 0x0D;
99 LF = 0x0A;
101 CRLF = CR LF;
103 SP = 0x20;
104 HTAB = 0x09;
106 WSP = SP | HTAB;
108 Let_dig = alpha | digit;
110 Ldh_str = (alpha | digit | '-')* Let_dig;
112 U_Let_dig = alpha | digit | UTF8_non_ascii;
114 U_Ldh_str = (alpha | digit | '-' | UTF8_non_ascii)* U_Let_dig;
116 U_label = U_Let_dig U_Ldh_str?;
118 label = Let_dig Ldh_str?;
120 sub_domain = label | U_label;
122 Domain = sub_domain ('.' sub_domain)*;
124 snum = ('2' '5' '0'..'5')
125      | ('2' '0'..'4' digit)
126      | ('0'..'1' digit{1,2})
127      | digit{1,2}
128      ;
130 IPv4_address_literal = snum ('.' snum){3};
132 IPv6_hex = xdigit{1,4};
134 IPv6_full = IPv6_hex (':' IPv6_hex){7};
136 IPv6_comp = (IPv6_hex (':' IPv6_hex){0,5})? '::' (IPv6_hex (':' IPv6_hex){0,5})?;
138 IPv6v4_full = IPv6_hex (':' IPv6_hex){5} ':' IPv4_address_literal;
140 IPv6v4_comp = (IPv6_hex (':' IPv6_hex){0,3})? '::' (IPv6_hex (':' IPv6_hex){0,3} ':')? IPv4_address_literal;
142 IPv6_addr = IPv6_full | IPv6_comp | IPv6v4_full | IPv6v4_comp;
144 IPv6_address_literal = 'IPv6:' IPv6_addr;
146 dcontent = graph - '\[' - '\\' - '\]';   # 33..90 | 94..126
148 standardized_tag = Ldh_str;
150 General_address_literal = standardized_tag ':' dcontent{1};
152 # See rfc 5321 Section 4.1.3
153 address_literal = '[' (IPv4_address_literal |
154                   IPv6_address_literal | General_address_literal) ']';
156 At_domain = '@' Domain;
158 A_d_l = At_domain (',' At_domain)*;
160 qtextSMTP = print - '"' - '\\' | UTF8_non_ascii;
162 quoted_pairSMTP = '\\' print;
164 QcontentSMTP = qtextSMTP | quoted_pairSMTP;
166 Quoted_string = '"' QcontentSMTP* '"';
168 atext = alpha | digit |
169         '!' | '#' |
170         '$' | '%' |
171         '&' | "'" |
172         '*' | '+' |
173         '-' | '/' |
174         '=' | '?' |
175         '^' | '_' |
176         '`' | '{' |
177         '|' | '}' |
178         '~' |
179         UTF8_non_ascii;
181 # obs_FWS = WSP+ (CRLF WSP+)*;
183 # FWS = ((WSP* CRLF)? WSP+) | obs_FWS;
185 # ccontent = ctext | quoted_pair | comment;
186 # comment = '(' (FWS? ccontent)* FWS? ')';
187 # CFWS = ((FWS? comment)+ FWS?) | FWS;
189 # Atom = cfws atext+ cfws;
191 Atom = atext+;
193 Dot_string = Atom ('.'  Atom)*;
195 Local_part = Dot_string | Quoted_string;
197 Mailbox = Local_part >mb_loc_beg %mb_loc_end '@'
198           ((Domain | address_literal) >mb_dom_beg %mb_dom_end);
200 Path = "<" ((A_d_l ":")? Mailbox) ">";
202 Reverse_path = Path | "<>";
204 Forward_path = Path | "<Postmaster>"i %magic_postmaster;
206 esmtp_keyword = ((alpha | digit) (alpha | digit | '-')*) @key_end;
208 esmtp_value = ((graph - '=' | UTF8_non_ascii)+) @val_end;
210 esmtp_param = (esmtp_keyword ('=' esmtp_value)?) %param;
212 Mail_parameters = esmtp_param (' ' esmtp_param)*;
214 Rcpt_parameters = esmtp_param (' ' esmtp_param)*;
216 String = Atom | Quoted_string;
218 chunk_size = digit+;
220 #############################################################################
222 data := |*
224  /[^\.\r\n]/ /[^\r\n]/* CRLF =>
226    auto len = te - ts - 2; // minus crlf
227    msg.out().write(ts, len);
228    msg.out() << '\n';
229    msg_bytes += len + 1;
230  };
232  '.' /[^\r\n]/+ CRLF =>
234    auto len = te - ts - 3; // minus crlf and leading '.'
235    msg.out().write(ts + 1, len);
236    msg.out() << '\n';
237    msg_bytes += len + 1;
238  };
240  CRLF =>
242    msg.out() << '\n';
243    msg_bytes++;
244  };
246  '.' CRLF =>
248    session.data_msg_done(msg);
249    if (msg_bytes > Config::size) {
250      LOG(WARNING) << "message size " << msg_bytes << " exceeds maximium of " << Config::size;
251    }
252    fgoto main;
253  };
257 #............................................................................
259 main := |*
261  "AUTH LOGIN"i CRLF =>
263    session.error("AUTH not supported");
264  };
266  "EHLO"i SP (Domain | address_literal) CRLF =>
268    char const* beg = ts + 5;
269    char const* end = te - 2;
270    session.ehlo(string(beg, end - beg));
271  };
273  "HELO"i SP Domain CRLF =>
275    char const* beg = ts + 5;
276    char const* end = te - 2;
277    session.helo(string(beg, end - beg));
278  };
280  # optional space not specified by RFC 5321
281  "MAIL FROM:"i SP? Reverse_path (SP Mail_parameters)? CRLF =>
283    session.mail_from(Mailbox(mb_loc, mb_dom), parameters);
285    mb_loc.clear();
286    mb_dom.clear();
287    parameters.clear();
288  };
290  # optional space not specified by RFC 5321
291  "RCPT TO:"i SP? Forward_path (SP Rcpt_parameters)? CRLF =>
293    session.rcpt_to(Mailbox(mb_loc, mb_dom), parameters);
295    mb_loc.clear();
296    mb_dom.clear();
297    parameters.clear();
298  };
300  "DATA"i CRLF =>
302    if (session.data_start()) {
303      session.data_msg(msg);
304      msg_bytes = 0;
305      LOG(INFO) << "calling data\n";
306      fgoto data;
307    }
308  };
310 # "BDAT"i SP (chunk_size >chunk_size_beg %chunk_size_end) (SP "LAST"i @last)? CRLF =>
311 # {
312 #   LOG(INFO) << "BDAT " << chunk_sz << (last ? " LAST" : "");
314 #   // eat data from our buffer
315 #   auto space = pe - ts;
316 #   LOG(INFO) << "space == " << space;
318 #   if (last) {
319 #   }
320 #   chunk_sz = 0;
321 # };
323  "RSET"i CRLF =>
325 //   last = false;
326    session.rset();
327  };
329  "NOOP"i (SP String)? CRLF =>
331    session.noop();
332  };
334  "VRFY"i (SP String)? CRLF =>
336    session.vrfy();
337  };
339  "HELP"i (SP String)? CRLF =>
341    session.help();
342  };
344  "STARTTLS"i CRLF =>
346    session.starttls();
347  };
349  "QUIT"i CRLF =>
351    session.quit();
352  };
358 %% write data nofinal;
360 //...........................................................................
362 void scanner(Session& session)
364   Message msg;
365   size_t msg_bytes{0};
367 //  char const* chunk_sz_beg{nullptr};
368 //  char const* chunk_sz_end{nullptr};
369 //  size_t chunk_sz{0};
371   char const* mb_loc_beg{nullptr};
372   char const* mb_loc_end{nullptr};
373   char const* mb_dom_beg{nullptr};
374   char const* mb_dom_end{nullptr};
376 //  bool last{false};
378   std::string mb_loc;
379   std::string mb_dom;
381   std::pair<string, string> param;
382   std::unordered_map<string, string> parameters;
384   static char buf[BUFSIZE];
386   char* ts = nullptr;
387   char* te = nullptr;
388   char* pe = nullptr;
390   size_t have = 0;
391   bool done = false;
393 #ifndef __clang__
394 #pragma GCC diagnostic ignored "-Wunused-but-set-variable"
395 #endif
397   char const* eof = nullptr;
398   int cs, act;
400   %% write init;
402   while (!done) {
403     std::streamsize space = BUFSIZE - have;
405     if (space == 0) {
406       // We've used up the entire buffer storing an already-parsed token
407       // prefix that must be preserved.
408       session.error("out of buffer space");
409       LOG(FATAL) << "out of buffer space";
410     }
412     char* p = buf + have;
413     std::streamsize len = session.read(p, space);
415     if (len == -1) { // EOF processing
416       pe = p;
417       len = 0;
418     } else {
419       pe = p + len;
420       eof = nullptr;
421     }
423     // Check if this is the end of file.
424     if (len == 0) {
425       if (have == 0) {
426         LOG(INFO) << "no more input";
427         std::exit(EXIT_SUCCESS);
428       }
429       eof = pe;
430       LOG(INFO) << "done";
431       done = true;
432     }
434     LOG(INFO) << "exec \'" << string_view(buf, have + len) << "'";
436 #pragma GCC diagnostic push
437 #pragma GCC diagnostic ignored "-Wold-style-cast"
439     %% write exec;
441 #pragma GCC diagnostic pop
443     if (cs == smtp_error) {
444       session.error("parse error");
445       break;
446     }
448     if (ts == nullptr) {
449       have = 0;
450     }
451     else {
452       // There is a prefix to preserve, shift it over.
453       have = pe - ts;
454       CHECK_NE(have, 0ul);
455       memmove(buf, ts, have);
457       // adjust ptrs
458       auto delta = ts - buf;
460       mb_loc_beg -= delta;
461       mb_loc_end -= delta;
462       mb_dom_beg -= delta;
463       mb_dom_end -= delta;
465       te = buf + (te - ts);
466       ts = buf;
467     }
468   }
471 int main(int argc, char const* argv[])
473   close(2); // hackage to stop glog from spewing
475   std::ios::sync_with_stdio(false);
477   auto logdir = getenv("GOOGLE_LOG_DIR");
478   if (logdir) {
479     boost::system::error_code ec;
480     boost::filesystem::create_directories(logdir, ec); // ignore errors;
481   }
482   google::InitGoogleLogging(argv[0]);
484   Session session;
485   session.greeting();
487   scanner(session);
488   LOG(INFO) << "scanner returned";