no leading zeros in dotted quad notation
[ghsmtp.git] / smtp.cpp
blobe5f418d31917eb90ca0ec80fa0c2375e4b8cb85d
1 #include <gflags/gflags.h>
2 namespace gflags {
5 // These need to be at least the length of any string it's trying to match.
6 DEFINE_uint64(cmd_bfr_size, 4 * 1024, "command parser buffer size");
7 DEFINE_uint64(data_bfr_size, 64 * 1024, "data parser buffer size");
9 DEFINE_uint64(max_xfer_size, 64 * 1024, "maximum BDAT transfer size");
11 #include <seccomp.h>
13 #include <fstream>
15 #include "Session.hpp"
16 #include "esc.hpp"
17 #include "fs.hpp"
18 #include "iobuffer.hpp"
19 #include "osutil.hpp"
21 #include <cstdlib>
22 #include <memory>
24 #include <signal.h>
26 #include <tao/pegtl.hpp>
27 #include <tao/pegtl/contrib/abnf.hpp>
29 using namespace tao::pegtl;
30 using namespace tao::pegtl::abnf;
32 namespace RFC5321 {
34 struct Ctx {
35 Session session;
37 std::string mb_loc;
38 std::string mb_dom;
40 std::pair<std::string, std::string> param;
41 std::unordered_map<std::string, std::string> parameters;
43 std::streamsize chunk_size;
45 Ctx(fs::path config_path, std::function<void(void)> read_hook)
46 : session(config_path, read_hook)
51 #include "UTF8.hpp"
53 // clang-format off
55 struct quoted_pair : seq<one<'\\'>, sor<VCHAR, WSP>> {};
57 using dot = one<'.'>;
58 using colon = one<':'>;
59 using dash = one<'-'>;
61 struct u_let_dig : sor<ALPHA, DIGIT, UTF8_non_ascii> {};
63 struct u_ldh_tail : star<sor<seq<plus<one<'-'>>, u_let_dig>, u_let_dig>> {};
65 struct u_label : seq<u_let_dig, u_ldh_tail> {};
67 struct let_dig : sor<ALPHA, DIGIT> {};
69 struct ldh_tail : star<sor<seq<plus<one<'-'>>, let_dig>, let_dig>> {};
71 struct ldh_str : seq<let_dig, ldh_tail> {};
73 // struct label : seq<let_dig, opt<ldh_str>> {};
75 struct sub_domain : u_label {};
77 struct domain : list_tail<sub_domain, dot> {};
79 struct dec_octet : sor<seq<string<'2','5'>, range<'0','5'>>,
80 seq<one<'2'>, range<'0','4'>, DIGIT>,
81 seq<one<'1'>, rep<2, DIGIT>>,
82 seq<range<'1', '9'>, DIGIT>,
83 DIGIT
84 > {};
85 struct IPv4_address_literal
86 : seq<dec_octet, dot, dec_octet, dot, dec_octet, dot, dec_octet> {};
88 struct h16 : rep_min_max<1, 4, HEXDIG> {};
90 struct ls32 : sor<seq<h16, colon, h16>, IPv4_address_literal> {};
92 struct dcolon : two<':'> {};
94 struct IPv6address : sor<seq< rep<6, h16, colon>, ls32>,
95 seq< dcolon, rep<5, h16, colon>, ls32>,
96 seq<opt<h16 >, dcolon, rep<4, h16, colon>, ls32>,
97 seq<opt<h16, opt< colon, h16>>, dcolon, rep<3, h16, colon>, ls32>,
98 seq<opt<h16, rep_opt<2, colon, h16>>, dcolon, rep<2, h16, colon>, ls32>,
99 seq<opt<h16, rep_opt<3, colon, h16>>, dcolon, h16, colon, ls32>,
100 seq<opt<h16, rep_opt<4, colon, h16>>, dcolon, ls32>,
101 seq<opt<h16, rep_opt<5, colon, h16>>, dcolon, h16>,
102 seq<opt<h16, rep_opt<6, colon, h16>>, dcolon >> {};
104 struct IPv6_address_literal : seq<TAO_PEGTL_ISTRING("IPv6:"), IPv6address> {};
106 struct dcontent : ranges<33, 90, 94, 126> {};
108 struct standardized_tag : ldh_str {};
110 struct general_address_literal : seq<standardized_tag, colon, plus<dcontent>> {};
112 // See rfc 5321 Section 4.1.3
113 struct address_literal : seq<one<'['>,
114 sor<IPv4_address_literal,
115 IPv6_address_literal,
116 general_address_literal>,
117 one<']'>> {};
119 struct at_domain : seq<one<'@'>, domain> {};
121 // The qtextSMTP rule explained: it's ASCII...
122 // excluding all the control chars below SPACE
123 // 34 '"' the double quote
124 // 92 '\\' the back slash
125 // DEL
126 // So including:
127 // 32-33: ' ' and '!'
128 // 35-91: "#$%&'()*+,-./" 0-9 ":;<=>?@" A-Z '['
129 // 93-126: "]^_`" a-z "{|}~"
131 struct qtextSMTP : sor<ranges<32, 33, 35, 91, 93, 126>, UTF8_non_ascii> {};
133 struct graphic : range<32, 126> {};
135 struct quoted_pairSMTP : seq<one<'\\'>, graphic> {};
137 struct qcontentSMTP : sor<qtextSMTP, quoted_pairSMTP> {};
139 struct quoted_string : seq<one<'"'>, plus<qcontentSMTP>, one<'"'>> {};
141 // excluded from atext are the “specials”: "()<>[]:;@\\,."
143 struct atext : sor<ALPHA, DIGIT,
144 one<'!', '#',
145 '$', '%',
146 '&', '\'',
147 '*', '+',
148 '-', '/',
149 '=', '?',
150 '^', '_',
151 '`', '{',
152 '|', '}',
153 '~'>,
154 UTF8_non_ascii> {};
156 struct atom : plus<atext> {};
158 struct dot_string : list<atom, dot> {};
160 struct local_part : sor<dot_string, quoted_string> {};
162 struct non_local_part : sor<domain, address_literal> {};
164 struct mailbox : seq<local_part, one<'@'>, non_local_part> {};
166 struct path : seq<one<'<'>, mailbox, one<'>'>> {};
168 struct bounce_path : TAO_PEGTL_ISTRING("<>") {};
170 struct reverse_path : sor<path, bounce_path> {};
172 struct magic_postmaster : TAO_PEGTL_ISTRING("<Postmaster>") {};
174 struct forward_path : sor<path, magic_postmaster> {};
176 struct esmtp_keyword : seq<sor<ALPHA, DIGIT>, star<sor<ALPHA, DIGIT, dash>>> {};
178 struct esmtp_value : plus<sor<range<33, 60>, range<62, 126>, UTF8_non_ascii>> {};
180 struct esmtp_param : seq<esmtp_keyword, opt<seq<one<'='>, esmtp_value>>> {};
182 struct mail_parameters : list<esmtp_param, SP> {};
184 struct rcpt_parameters : list<esmtp_param, SP> {};
186 struct string : sor<quoted_string, atom> {};
188 struct helo : seq<TAO_PEGTL_ISTRING("HELO"),
190 sor<domain, address_literal>,
191 CRLF> {};
193 struct ehlo : seq<TAO_PEGTL_ISTRING("EHLO"),
195 sor<domain, address_literal>,
196 CRLF> {};
198 struct mail_from : seq<TAO_PEGTL_ISTRING("MAIL"),
199 TAO_PEGTL_ISTRING(" FROM:"),
200 opt<SP>, // common enough error, we'll allow it
201 reverse_path,
202 opt<seq<SP, mail_parameters>>,
203 CRLF> {};
205 struct rcpt_to : seq<TAO_PEGTL_ISTRING("RCPT"),
206 TAO_PEGTL_ISTRING(" TO:"),
207 opt<SP>, // common error
208 forward_path,
209 opt<seq<SP, rcpt_parameters>>,
210 CRLF> {};
212 struct chunk_size : plus<DIGIT> {};
214 struct last : TAO_PEGTL_ISTRING("LAST") {};
216 struct bdat : seq<TAO_PEGTL_ISTRING("BDAT"), SP, chunk_size, CRLF> {};
218 struct bdat_last : seq<TAO_PEGTL_ISTRING("BDAT"), SP, chunk_size, SP, last, CRLF> {};
220 struct data : seq<TAO_PEGTL_ISTRING("DATA"), CRLF> {};
222 // ## below: DATA sub parse ##
224 struct data_end : seq<dot, CRLF> {};
226 struct data_blank : CRLF {};
228 // This particular crud will trigger an error return with the "no bare
229 // LF" message.
231 struct data_not_end : seq<dot, LF> {};
233 struct data_also_not_end : seq<LF, dot, CRLF> {};
236 // RFC 5321 text line length section 4.5.3.1.6.
237 struct data_plain : seq<rep_min_max<1, 998, not_one<'\r', '\n'>>, CRLF> {};
239 struct data_dot : seq<one<'.'>, rep_min_max<1, 998, not_one<'\r', '\n'>>, CRLF> {};
241 struct data_long : seq<star<not_one<'\r', '\n'>>, CRLF> {};
243 struct data_line : seq<sor<data_blank,
244 data_not_end,
245 data_also_not_end,
246 data_dot,
247 data_plain,
248 data_long>,
249 discard> {};
251 struct data_grammar : until<data_end, data_line> {};
253 // ## above: DATA sub parse ##
255 struct rset : seq<TAO_PEGTL_ISTRING("RSET"), CRLF> {};
257 struct noop : seq<TAO_PEGTL_ISTRING("NOOP"), opt<seq<SP, string>>, CRLF> {};
259 struct vrfy : seq<TAO_PEGTL_ISTRING("VRFY"), opt<seq<SP, string>>, CRLF> {};
261 struct help : seq<TAO_PEGTL_ISTRING("HELP"), opt<seq<SP, string>>, CRLF> {};
263 struct starttls
264 : seq<TAO_PEGTL_ISTRING("STAR"), TAO_PEGTL_ISTRING("TTLS"), CRLF> {};
266 struct quit : seq<TAO_PEGTL_ISTRING("QUIT"), CRLF> {};
268 // Anti-AUTH support
270 // base64-char = ALPHA / DIGIT / "+" / "/"
271 // ;; Case-sensitive
273 struct base64_char : sor<ALPHA, DIGIT, one<'+', '/'>> {};
275 // base64-terminal = (2base64-char "==") / (3base64-char "=")
277 struct base64_terminal : sor<seq<rep<2, base64_char>, TAO_PEGTL_ISTRING("==")>,
278 seq<rep<3, base64_char>, one<'='>>
279 > {};
281 // base64 = base64-terminal /
282 // ( 1*(4base64-char) [base64-terminal] )
284 struct base64 : sor<base64_terminal,
285 seq<plus<rep<4, base64_char>>,
286 opt<base64_terminal>>
287 > {};
289 // initial-response= base64 / "="
291 struct initial_response : sor<base64, one<'='>> {};
293 // cancel-response = "*"
295 struct cancel_response : one<'*'> {};
297 struct UPPER_ALPHA : range<'A', 'Z'> {};
299 using HYPHEN = one<'-'>;
300 using UNDERSCORE = one<'_'>;
302 struct mech_char : sor<UPPER_ALPHA, DIGIT, HYPHEN, UNDERSCORE> {};
303 struct sasl_mech : rep_min_max<1, 20, mech_char> {};
305 // auth-command = "AUTH" SP sasl-mech [SP initial-response]
306 // *(CRLF [base64]) [CRLF cancel-response]
307 // CRLF
308 // ;; <sasl-mech> is defined in RFC 4422
310 struct auth : seq<TAO_PEGTL_ISTRING("AUTH"), SP, sasl_mech,
311 opt<seq<SP, initial_response>>,
312 // star<CRLF, opt<base64>>,
313 // opt<seq<CRLF, cancel_response>>,
314 CRLF> {};
315 // bad commands:
317 struct bogus_cmd_short : seq<rep_min_max<0, 3, not_one<'\r', '\n'>>, CRLF> {};
318 struct bogus_cmd_long : seq<rep_min_max<4, 1000, not_one<'\r', '\n'>>, CRLF> {};
320 // commands in size order
322 struct any_cmd : seq<sor<bogus_cmd_short,
323 data,
324 quit,
325 rset,
326 noop,
327 vrfy,
328 help,
329 helo,
330 ehlo,
331 bdat,
332 bdat_last,
333 starttls,
334 rcpt_to,
335 mail_from,
336 auth,
337 bogus_cmd_long>,
338 discard> {};
340 struct grammar : plus<any_cmd> {};
342 // clang-format on
344 template <typename Rule>
345 struct action : nothing<Rule> {
348 template <typename Rule>
349 struct data_action : nothing<Rule> {
352 template <>
353 struct action<bogus_cmd_short> {
354 template <typename Input>
355 static void apply(Input const& in, Ctx& ctx)
357 LOG(INFO) << "bogus_cmd_short";
358 ctx.session.cmd_unrecognized(in.string());
362 template <>
363 struct action<bogus_cmd_long> {
364 template <typename Input>
365 static void apply(Input const& in, Ctx& ctx)
367 LOG(INFO) << "bogus_cmd_long";
368 ctx.session.cmd_unrecognized(in.string());
372 template <>
373 struct action<esmtp_keyword> {
374 template <typename Input>
375 static void apply(Input const& in, Ctx& ctx)
377 ctx.param.first = in.string();
381 template <>
382 struct action<esmtp_value> {
383 template <typename Input>
384 static void apply(Input const& in, Ctx& ctx)
386 ctx.param.second = in.string();
390 template <>
391 struct action<esmtp_param> {
392 template <typename Input>
393 static void apply(Input const& in, Ctx& ctx)
395 ctx.parameters.insert(ctx.param);
396 ctx.param.first.clear();
397 ctx.param.second.clear();
401 template <>
402 struct action<local_part> {
403 template <typename Input>
404 static void apply(Input const& in, Ctx& ctx)
406 ctx.mb_loc = in.string();
407 // RFC 5321, section 4.5.3.1.1.
408 if (ctx.mb_loc.length() > 64) {
409 LOG(WARNING) << "local part length == " << ctx.mb_loc.length();
414 template <>
415 struct action<non_local_part> {
416 template <typename Input>
417 static void apply(Input const& in, Ctx& ctx)
419 ctx.mb_dom = in.string();
420 // RFC 5321, section 4.5.3.1.2.
421 if (ctx.mb_dom.length() > 255) {
422 LOG(WARNING) << "domain name or number too long " << ctx.mb_dom;
427 template <>
428 struct action<magic_postmaster> {
429 static void apply0(Ctx& ctx)
431 ctx.mb_loc = std::string{"Postmaster"};
432 ctx.mb_dom.clear();
436 template <>
437 struct action<helo> {
438 template <typename Input>
439 static void apply(Input const& in, Ctx& ctx)
441 auto const b = begin(in) + 5; // +5 for the length of "HELO "
442 auto const e = std::find(b, end(in) - 2, ' '); // -2 for the CRLF
443 ctx.session.helo(std::string_view(b, e - b));
447 template <>
448 struct action<ehlo> {
449 template <typename Input>
450 static void apply(Input const& in, Ctx& ctx)
452 auto const b = begin(in) + 5; // +5 for the length of "EHLO "
453 auto const e = std::find(b, end(in) - 2, ' '); // -2 for the CRLF
454 ctx.session.ehlo(std::string_view(b, e - b));
458 template <>
459 struct action<mail_from> {
460 static void apply0(Ctx& ctx)
462 ctx.session.mail_from(Mailbox{ctx.mb_loc, ctx.mb_dom}, ctx.parameters);
463 ctx.mb_loc.clear();
464 ctx.mb_dom.clear();
465 ctx.parameters.clear();
469 template <>
470 struct action<rcpt_to> {
471 static void apply0(Ctx& ctx)
473 ctx.session.rcpt_to(Mailbox{ctx.mb_loc, ctx.mb_dom}, ctx.parameters);
474 ctx.mb_loc.clear();
475 ctx.mb_dom.clear();
476 ctx.parameters.clear();
480 template <>
481 struct action<chunk_size> {
482 template <typename Input>
483 static void apply(Input const& in, Ctx& ctx)
485 ctx.chunk_size = std::strtoull(in.string().c_str(), nullptr, 10);
489 void bdat_act(Ctx& ctx, bool last)
491 auto status_returned = false;
493 if (!ctx.session.bdat_start(ctx.chunk_size))
494 status_returned = true;
496 auto to_xfer = ctx.chunk_size;
498 auto const bfr_size{std::min(to_xfer, std::streamsize(FLAGS_max_xfer_size))};
499 iobuffer<char> bfr(bfr_size);
501 while (to_xfer) {
502 auto const xfer_sz{std::min(to_xfer, bfr_size)};
504 ctx.session.in().read(bfr.data(), xfer_sz);
505 if (!ctx.session.in()) {
506 LOG(ERROR) << "attempt to read " << xfer_sz << " octets but only got "
507 << ctx.session.in().gcount();
508 if (ctx.session.maxed_out()) {
509 LOG(ERROR) << "input maxed out";
510 if (!status_returned)
511 ctx.session.bdat_size_error();
513 else if (ctx.session.timed_out()) {
514 LOG(ERROR) << "input timed out";
515 if (!status_returned)
516 ctx.session.bdat_io_error();
518 else if (ctx.session.in().eof()) {
519 LOG(ERROR) << "EOF in BDAT";
520 if (!status_returned)
521 ctx.session.bdat_io_error();
523 else {
524 LOG(ERROR) << "I/O error in BDAT";
525 if (!status_returned)
526 ctx.session.bdat_io_error();
528 return;
530 if (!status_returned && !ctx.session.msg_write(bfr.data(), xfer_sz)) {
531 status_returned = true;
534 to_xfer -= xfer_sz;
537 if (!status_returned) {
538 ctx.session.bdat_done(ctx.chunk_size, last);
542 template <>
543 struct action<bdat> {
544 static void apply0(Ctx& ctx)
546 bdat_act(ctx, false);
550 template <>
551 struct action<bdat_last> {
552 static void apply0(Ctx& ctx)
554 bdat_act(ctx, true);
558 template <>
559 struct data_action<data_end> {
560 static void apply0(Ctx& ctx) { ctx.session.data_done(); }
563 template <>
564 struct data_action<data_blank> {
565 static void apply0(Ctx& ctx)
567 constexpr char CRLF[]{'\r', '\n'};
568 ctx.session.msg_write(CRLF, sizeof(CRLF));
572 template <>
573 struct data_action<data_plain> {
574 template <typename Input>
575 static void apply(Input const& in, Ctx& ctx)
577 auto const len{end(in) - begin(in)};
578 ctx.session.msg_write(begin(in), len);
582 template <>
583 struct data_action<data_dot> {
584 template <typename Input>
585 static void apply(Input const& in, Ctx& ctx)
587 auto const len{end(in) - begin(in) - 1};
588 ctx.session.msg_write(begin(in) + 1, len);
592 template <>
593 struct data_action<data_long> {
594 template <typename Input>
595 static void apply(Input const& in, Ctx& ctx)
597 LOG(WARNING) << "garbage in data stream: \"" << esc(in.string()) << "\"";
598 auto const len{end(in) - begin(in)};
599 if (len)
600 ctx.session.msg_write(begin(in), len);
601 if (len > 1000) {
602 LOG(WARNING) << "line too long at " << len << " octets";
607 template <>
608 struct data_action<data_not_end> {
609 static void apply0(Ctx& ctx) __attribute__((noreturn))
611 ctx.session.bare_lf();
615 template <>
616 struct data_action<data_also_not_end> {
617 static void apply0(Ctx& ctx) __attribute__((noreturn))
619 ctx.session.bare_lf();
623 template <>
624 struct action<data> {
625 template <typename Input>
626 static void apply(Input const& in, Ctx& ctx)
628 if (ctx.session.data_start()) {
629 auto din = istream_input<eol::crlf, 1>(ctx.session.in(),
630 FLAGS_data_bfr_size, "data");
631 try {
632 if (!parse_nested<RFC5321::data_grammar, RFC5321::data_action>(in, din,
633 ctx)) {
634 ctx.session.log_stats();
635 if (!(ctx.session.maxed_out() || ctx.session.timed_out())) {
636 ctx.session.error("bad DATA syntax");
639 return;
641 catch (parse_error const& e) {
642 LOG(WARNING) << e.what();
643 ctx.session.error("unable to parse DATA stream");
645 catch (std::exception const& e) {
646 LOG(WARNING) << e.what();
647 ctx.session.error("unknown problem in DATA stream");
653 template <>
654 struct action<rset> {
655 static void apply0(Ctx& ctx) { ctx.session.rset(); }
658 template <typename Input>
659 std::string_view get_string_view(Input const& in)
661 auto const b = begin(in) + 4;
662 auto const len = end(in) - b;
663 auto str = std::string_view(b, len);
664 if (str.front() == ' ')
665 str.remove_prefix(1);
666 return str;
669 template <>
670 struct action<noop> {
671 template <typename Input>
672 static void apply(Input const& in, Ctx& ctx)
674 auto const str = get_string_view(in);
675 ctx.session.noop(str);
679 template <>
680 struct action<vrfy> {
681 template <typename Input>
682 static void apply(Input const& in, Ctx& ctx)
684 auto const str = get_string_view(in);
685 ctx.session.vrfy(str);
689 template <>
690 struct action<help> {
691 template <typename Input>
692 static void apply(Input const& in, Ctx& ctx)
694 auto const str = get_string_view(in);
695 ctx.session.help(str);
699 template <>
700 struct action<starttls> {
701 static void apply0(Ctx& ctx) { ctx.session.starttls(); }
704 template <>
705 struct action<quit> {
706 static void apply0(Ctx& ctx) __attribute__((noreturn)) { ctx.session.quit(); }
709 template <>
710 struct action<auth> {
711 static void apply0(Ctx& ctx) __attribute__((noreturn)) { ctx.session.auth(); }
713 } // namespace RFC5321
715 void timeout(int signum) __attribute__((noreturn));
716 void timeout(int signum)
718 const char errmsg[] = "421 4.4.2 time-out\r\n";
719 write(STDOUT_FILENO, errmsg, sizeof errmsg - 1);
720 _Exit(1);
723 void install_syscall_filter()
725 /// scmp_filter_ctx ctx = CHECK_NOTNULL(seccomp_init(SCMP_ACT_ERRNO(EPERM)));
726 scmp_filter_ctx ctx = CHECK_NOTNULL(seccomp_init(SCMP_ACT_LOG));
728 auto rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
729 CHECK_EQ(rc, 0) << "seccomp_rule_add write failed";
731 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
732 CHECK_EQ(rc, 0) << "seccomp_rule_add read failed";
734 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(close), 0);
735 CHECK_EQ(rc, 0) << "seccomp_rule_add close failed";
737 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigprocmask), 0);
738 CHECK_EQ(rc, 0) << "seccomp_rule_add rt_sigprocmask failed";
740 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigreturn), 0);
741 CHECK_EQ(rc, 0) << "seccomp_rule_add rt_sigreturn failed";
743 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rt_sigaction), 0);
744 CHECK_EQ(rc, 0) << "seccomp_rule_add rt_sigaction failed";
746 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(newfstatat), 0);
747 CHECK_EQ(rc, 0) << "seccomp_rule_add newfstatat failed";
749 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(openat), 0);
750 CHECK_EQ(rc, 0) << "seccomp_rule_add openat failed";
752 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(socket), 0);
753 CHECK_EQ(rc, 0) << "seccomp_rule_add socket failed";
755 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(connect), 0);
756 CHECK_EQ(rc, 0) << "seccomp_rule_add connect failed";
758 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(poll), 0);
759 CHECK_EQ(rc, 0) << "seccomp_rule_add poll failed";
761 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(sendto), 0);
762 CHECK_EQ(rc, 0) << "seccomp_rule_add sendto failed";
764 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(recvfrom), 0);
765 CHECK_EQ(rc, 0) << "seccomp_rule_add recvfrom failed";
767 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(sigaltstack), 0);
768 CHECK_EQ(rc, 0) << "seccomp_rule_add sigaltstack failed";
770 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(prctl), 0);
771 CHECK_EQ(rc, 0) << "seccomp_rule_add prctl failed";
773 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(gettid), 0);
774 CHECK_EQ(rc, 0) << "seccomp_rule_add gettid failed";
776 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getpid), 0);
777 CHECK_EQ(rc, 0) << "seccomp_rule_add getpid failed";
779 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getppid), 0);
780 CHECK_EQ(rc, 0) << "seccomp_rule_add getppid failed";
782 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(newfstatat), 0);
783 CHECK_EQ(rc, 0) << "seccomp_rule_add newfstatat failed";
785 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(tgkill), 0);
786 CHECK_EQ(rc, 0) << "seccomp_rule_add tgkill failed";
788 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(dup), 0);
789 CHECK_EQ(rc, 0) << "seccomp_rule_add dup failed";
791 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mmap), 0);
792 CHECK_EQ(rc, 0) << "seccomp_rule_add mmap failed";
794 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(munmap), 0);
795 CHECK_EQ(rc, 0) << "seccomp_rule_add munmap failed";
797 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(pipe2), 0);
798 CHECK_EQ(rc, 0) << "seccomp_rule_add pipe2 failed";
800 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(readlinkat), 0);
801 CHECK_EQ(rc, 0) << "seccomp_rule_add readlinkat failed";
803 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(pselect6), 0);
804 CHECK_EQ(rc, 0) << "seccomp_rule_add pselect6 failed";
806 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(uname), 0);
807 CHECK_EQ(rc, 0) << "seccomp_rule_add uname failed";
809 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(fcntl), 0);
810 CHECK_EQ(rc, 0) << "seccomp_rule_add fcntl failed";
812 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(unlink), 0);
813 CHECK_EQ(rc, 0) << "seccomp_rule_add unlink failed";
815 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(symlink), 0);
816 CHECK_EQ(rc, 0) << "seccomp_rule_add symlink failed";
818 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(madvise), 0);
819 CHECK_EQ(rc, 0) << "seccomp_rule_add madvise failed";
821 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(mprotect), 0);
822 CHECK_EQ(rc, 0) << "seccomp_rule_add mprotect failed";
824 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(futex), 0);
825 CHECK_EQ(rc, 0) << "seccomp_rule_add futex failed";
827 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(writev), 0);
828 CHECK_EQ(rc, 0) << "seccomp_rule_add writev failed";
830 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(sysinfo), 0);
831 CHECK_EQ(rc, 0) << "seccomp_rule_add sysinfo failed";
833 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(brk), 0);
834 CHECK_EQ(rc, 0) << "seccomp_rule_add brk failed";
836 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getrandom), 0);
837 CHECK_EQ(rc, 0) << "seccomp_rule_add getrandom failed";
839 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(getdents64), 0);
840 CHECK_EQ(rc, 0) << "seccomp_rule_add getdents64 failed";
842 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(lseek), 0);
843 CHECK_EQ(rc, 0) << "seccomp_rule_add lseek failed";
845 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(setsockopt), 0);
846 CHECK_EQ(rc, 0) << "seccomp_rule_add setsockopt failed";
848 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(alarm), 0);
849 CHECK_EQ(rc, 0) << "seccomp_rule_add alarm failed";
851 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(rename), 0);
852 CHECK_EQ(rc, 0) << "seccomp_rule_add rename failed";
854 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(clock_gettime), 0);
855 CHECK_EQ(rc, 0) << "seccomp_rule_add clock_gettime failed";
857 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);
858 CHECK_EQ(rc, 0) << "seccomp_rule_add exit_group failed";
860 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
861 CHECK_EQ(rc, 0) << "seccomp_rule_add exit failed";
863 // for sanitize
865 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(clone), 0);
866 CHECK_EQ(rc, 0) << "seccomp_rule_add clone failed";
868 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(wait4), 0);
869 CHECK_EQ(rc, 0) << "seccomp_rule_add wait4 failed";
871 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(sched_yield), 0);
872 CHECK_EQ(rc, 0) << "seccomp_rule_add sched_yield failed";
874 rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(ptrace), 0);
875 CHECK_EQ(rc, 0) << "seccomp_rule_add ptrace failed";
877 rc = seccomp_load(ctx);
878 CHECK_EQ(rc, 0) << "seccomp_load failed";
880 // seccomp_export_pfc(ctx, STDERR_FILENO);
882 seccomp_release(ctx);
885 int main(int argc, char* argv[])
887 std::ios::sync_with_stdio(false);
889 { // Need to work with either namespace.
890 using namespace gflags;
891 using namespace google;
892 ParseCommandLineFlags(&argc, &argv, true);
895 // Set timeout signal handler to limit total run time.
896 struct sigaction sact {};
897 PCHECK(sigemptyset(&sact.sa_mask) == 0);
898 sact.sa_flags = 0;
899 sact.sa_handler = timeout;
900 PCHECK(sigaction(SIGALRM, &sact, nullptr) == 0);
902 auto const log_dir{getenv("GOOGLE_LOG_DIR")};
903 if (log_dir) {
904 error_code ec;
905 fs::create_directories(log_dir, ec);
908 google::InitGoogleLogging(argv[0]);
910 std::unique_ptr<RFC5321::Ctx> ctx;
912 // Don't wait for STARTTLS to fail if no cert.
913 auto const config_path = osutil::get_config_dir();
914 auto const certs = osutil::list_directory(config_path, Config::cert_fn_re);
915 CHECK_GE(certs.size(), 1) << "no certs found";
917 auto const read_hook{[&ctx]() { ctx->session.flush(); }};
918 ctx = std::make_unique<RFC5321::Ctx>(config_path, read_hook);
920 ctx->session.greeting();
922 install_syscall_filter();
924 istream_input<eol::crlf, 1> in{ctx->session.in(), FLAGS_cmd_bfr_size,
925 "session"};
927 int ret = 0;
928 try {
929 ret = !parse<RFC5321::grammar, RFC5321::action>(in, *ctx);
931 catch (std::exception const& e) {
932 LOG(WARNING) << e.what();
935 if (ctx->session.maxed_out()) {
936 ctx->session.max_out();
938 else if (ctx->session.timed_out()) {
939 ctx->session.time_out();
941 // else {
942 // ctx->session.error("session end without QUIT command from client");
943 // }
945 return ret;