better with obs forms
[ghsmtp.git] / sasl.cpp
blobd19d82e2f31589a7c25f8ec36e96591cd82fd834
1 #include "Base64.hpp"
2 #include "SockBuffer.hpp"
4 #include <boost/iostreams/concepts.hpp>
5 #include <boost/iostreams/device/file_descriptor.hpp>
6 #include <boost/iostreams/stream.hpp>
8 #include <iostream>
9 #include <string>
10 #include <unordered_map>
12 #include <fmt/format.h>
14 #include <netdb.h>
15 #include <sys/socket.h>
16 #include <sys/un.h>
18 #include <sys/types.h>
20 #include <glog/logging.h>
22 #include <tao/pegtl.hpp>
23 #include <tao/pegtl/contrib/abnf.hpp>
25 #include "test-credentials.ipp"
27 using namespace std::string_literals;
29 using namespace tao::pegtl;
30 using namespace tao::pegtl::abnf;
32 namespace dovecot {
33 // clang-format off
35 using HYPHEN = one<'-'>;
36 using UNDERSCORE = one<'_'>;
38 struct id : plus<DIGIT> {};
39 struct pid : plus<DIGIT> {};
40 struct cookie : rep<32, HEXDIG> {};
42 struct base64_char : sor<ALPHA, DIGIT, one<'+'>, one<'/'>> {};
43 struct base64_data : seq<plus<base64_char>, rep_min_max<0, 2, one<'='>>> {};
45 struct param_char : sor<ALPHA, DIGIT, HYPHEN, UNDERSCORE> {};
46 struct param_name : rep_min_max<1, 20, param_char> {};
48 struct param_vchar : not_one<'\t'> {};
49 struct param_val : rep_min_max<1, 200, param_vchar> {};
51 struct parameter : sor<param_name, seq<param_name, one<'='>, param_val>> {};
53 struct UPPER_ALPHA : range<'A', 'Z'> {};
55 struct mech_char : sor<UPPER_ALPHA, DIGIT, HYPHEN, UNDERSCORE> {};
56 struct sasl_mech : rep_min_max<1, 20, mech_char> {};
58 struct vers : seq<TAO_PEGTL_STRING("VERSION"), HTAB, one<'1'>, HTAB, DIGIT, LF> {};
59 struct mech : seq<TAO_PEGTL_STRING("MECH"), HTAB, sasl_mech, star<seq<HTAB, parameter>>, LF> {};
60 struct spid : seq<TAO_PEGTL_STRING("SPID"), HTAB, pid, LF> {};
61 struct cuid : seq<TAO_PEGTL_STRING("CUID"), HTAB, pid, LF> {};
62 struct cook : seq<TAO_PEGTL_STRING("COOKIE"), HTAB, cookie, LF> {};
63 struct done : seq<TAO_PEGTL_STRING("DONE"), LF> {};
65 struct resp : seq<vers, star<mech>, spid, cuid, cook, done, discard> {};
67 struct auth_ok : seq<TAO_PEGTL_STRING("OK"), HTAB, id, star<seq<HTAB, parameter>>> {};
68 struct auth_cont : seq<TAO_PEGTL_STRING("CONT"), HTAB, id, HTAB, base64_data> {};
69 struct auth_fail : seq<TAO_PEGTL_STRING("FAIL"), HTAB, id, star<seq<HTAB, parameter>>> {};
71 struct auth_resp : seq<sor<auth_ok, auth_cont, auth_fail>, discard> {};
73 // clang-format on
75 struct Context {
76 using parameters_t = std::vector<std::string>;
77 using mechs_t = std::unordered_map<std::string, parameters_t>;
79 uint32_t id;
80 std::string cookie;
81 std::string sasl_mech;
83 parameters_t parameters;
84 mechs_t mechs;
86 enum class auth_response { none, ok, cont, fail };
88 // clang-format off
89 static constexpr auto none = auth_response::none;
90 static constexpr auto ok = auth_response::ok;
91 static constexpr auto cont = auth_response::cont;
92 static constexpr auto fail = auth_response::fail;
94 static constexpr char const* c_str(auth_response rsp)
96 switch (rsp) {
97 case none: return "none";
98 case ok: return "ok";
99 case cont: return "cont";
100 case fail: return "fail";
102 return "** unknown **";
104 // clang-format on
106 auth_response auth_resp{none};
109 std::ostream& operator<<(std::ostream& os, Context::auth_response rsp)
111 return os << Context::c_str(rsp);
114 template <typename Rule>
115 struct action : nothing<Rule> {
118 template <>
119 struct action<id> {
120 template <typename Input>
121 static void apply(Input const& in, Context& ctx)
123 ctx.id = strtoul(in.string().c_str(), nullptr, 10);
127 template <>
128 struct action<cookie> {
129 template <typename Input>
130 static void apply(Input const& in, Context& ctx)
132 ctx.cookie = in.string();
136 template <>
137 struct action<parameter> {
138 template <typename Input>
139 static void apply(Input const& in, Context& ctx)
141 ctx.parameters.push_back(in.string());
145 template <>
146 struct action<sasl_mech> {
147 template <typename Input>
148 static void apply(Input const& in, Context& ctx)
150 ctx.sasl_mech = in.string();
154 template <>
155 struct action<mech> {
156 template <typename Input>
157 static void apply(Input const& in, Context& ctx)
159 ctx.mechs.emplace(std::move(ctx.sasl_mech), std::move(ctx.parameters));
163 template <>
164 struct action<auth_ok> {
165 static void apply0(Context& ctx)
167 ctx.auth_resp = Context::auth_response::ok;
171 template <>
172 struct action<auth_cont> {
173 static void apply0(Context& ctx)
175 ctx.auth_resp = Context::auth_response::cont;
179 template <>
180 struct action<auth_fail> {
181 static void apply0(Context& ctx)
183 ctx.auth_resp = Context::auth_response::fail;
186 } // namespace dovecot
188 // clang-format off
189 constexpr char const* defined_params[]{
190 "anonymous",
191 "plaintext",
192 "dictionary",
193 "active",
194 "forward-secrecy",
195 "mutual-auth",
196 "private",
198 // clang-format on
200 int main()
202 auto const fd{socket(AF_UNIX, SOCK_STREAM, 0)};
203 PCHECK(fd >= 0) << "socket failed";
205 sockaddr_un addr = {.sun_family = AF_UNIX};
207 constexpr char socket_path[]{"/var/spool/postfix/private/auth"};
208 static_assert(sizeof(socket_path) <= sizeof(addr.sun_path));
209 strcpy(addr.sun_path, socket_path);
211 PCHECK(connect(fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) == 0)
212 << "connect to " << socket_path << " failed";
214 auto ios{boost::iostreams::stream<SockBuffer>{fd, fd}};
216 ios << "VERSION\t1\t1\n"
217 << "CPID\t" << getpid() << "\n"
218 << std::flush;
220 auto ctx = dovecot::Context{};
221 auto in = istream_input<eol::lf, 1>{ios, 8 * 1024, "sasl"};
222 if (!parse<dovecot::resp, dovecot::action>(in, ctx)) {
223 LOG(WARNING) << "handshake response parse failed";
226 for (auto const& m : ctx.mechs) {
227 LOG(INFO) << m.first;
230 auto const tok{fmt::format("\0{}\0{}", test::username, test::password)};
231 auto const init{Base64::enc(tok)};
233 if (ctx.mechs.find("PLAIN") != end(ctx.mechs)) {
234 auto id{uint32_t{0x12345678}};
236 ios << "AUTH\t" << id << "\tPLAIN"
237 << "\tservice=SMTP"
238 << "\tresp=" << init << '\n'
239 << std::flush;
241 if (!parse<dovecot::auth_resp, dovecot::action>(in, ctx)) {
242 LOG(WARNING) << "auth response parse failed";
245 CHECK_EQ(ctx.id, id);
246 LOG(INFO) << "AUTH: " << ctx.auth_resp;