Software /
code /
prosody-modules
Comparison
mod_net_dovecotauth/mod_net_dovecotauth.lua @ 1088:6f8e7f65f704
mod_net_dovecotauth: Initial commit of server implementation of the Dovecot authentication protocol
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Fri, 28 Jun 2013 01:38:35 +0200 |
child | 1491:e7294423512f |
comparison
equal
deleted
inserted
replaced
1087:447af80a16ad | 1088:6f8e7f65f704 |
---|---|
1 -- mod_net_dovecotauth.lua | |
2 -- | |
3 -- Protocol spec: | |
4 -- http://dovecot.org/doc/auth-protocol.txt | |
5 -- | |
6 -- Example postfix config: | |
7 -- sudo postconf smtpd_sasl_path=inet:127.0.0.1:28484 | |
8 -- sudo postconf smtpd_sasl_type=dovecot | |
9 -- sudo postconf smtpd_sasl_auth_enable=yes | |
10 | |
11 module:set_global(); | |
12 | |
13 -- Imports | |
14 local new_sasl = require "core.usermanager".get_sasl_handler; | |
15 local user_exists = require "core.usermanager".user_exists; | |
16 local base64 = require"util.encodings".base64; | |
17 local new_buffer = module:require"buffer".new; | |
18 local dump = require"util.serialization".serialize; | |
19 | |
20 -- Config | |
21 local vhost = module:get_option_string("dovecotauth_host", (next(hosts))); -- TODO Is there a better solution? | |
22 local allow_master = module:get_option_boolean("adovecotauth_allow_master", false); | |
23 | |
24 -- Active sessions | |
25 local sessions = {}; | |
26 | |
27 -- Session methods | |
28 local new_session; | |
29 do | |
30 local sess = { }; | |
31 local sess_mt = { __index = sess }; | |
32 | |
33 function new_session(conn) | |
34 local sess = { type = "?", conn = conn, buf = assert(new_buffer()), sasl = {} } | |
35 function sess:log(l, m, ...) | |
36 return module:log(l, self.type..tonumber(tostring(self):match("%x+$"), 16)..": "..m, ...); | |
37 end | |
38 return setmetatable(sess, sess_mt); | |
39 end | |
40 | |
41 function sess:send(...) | |
42 local data = table.concat({...}, "\t") .. "\n" | |
43 -- self:log("debug", "SEND: %s", dump(ret)); | |
44 return self.conn:write(data); | |
45 end | |
46 | |
47 local mech_params = { | |
48 ANONYMOUS = "anonymous"; | |
49 PLAIN = "plaintext"; | |
50 ["DIGEST-MD5"] = "mutual-auth"; | |
51 ["SCRAM-SHA-1"] = "mutual-auth"; | |
52 ["SCRAM-SHA-1-PLUS"] = "mutual-auth"; | |
53 } | |
54 | |
55 function sess:handshake() | |
56 self:send("VERSION", 1, 1); | |
57 self:send("SPID", pposix.getpid()); | |
58 self:send("CUID", tonumber(tostring(self):match"%x+$", 16)); | |
59 for mech in pairs(self.g_sasl:mechanisms()) do | |
60 self:send("MECH", mech, mech_params[mech]); | |
61 end | |
62 self:send("DONE"); | |
63 end | |
64 | |
65 function sess:feed(data) | |
66 -- TODO break this up a bit | |
67 -- module:log("debug", "sess = %s", dump(self)); | |
68 local buf = self.buf; | |
69 buf:write(data); | |
70 local line = buf:read("*l") | |
71 while line and line ~= "" do | |
72 local part = line:gmatch("[^\t]+"); | |
73 local command = part(); | |
74 if command == "VERSION" then | |
75 local major = tonumber(part()); | |
76 local minor = tonumber(part()); | |
77 if major ~= 1 then | |
78 self:log("warn", "Wrong version, expected 1.1, got %s.%s", tostring(major), tostring(minor)); | |
79 self.conn:close(); | |
80 break; | |
81 end | |
82 elseif command == "CPID" then | |
83 self.type = "C"; | |
84 self.pid = part(); | |
85 elseif command == "SPID" and allow_master then | |
86 self.type = "M"; | |
87 self.pid = part(); | |
88 elseif command == "AUTH" and self.type ~= "?" then | |
89 -- C: "AUTH" TAB <id> TAB <mechanism> TAB service=<service> [TAB <parameters>] | |
90 local id = part() -- <id> | |
91 local sasl = self.sasl[id]; | |
92 local mech = part(); | |
93 if not sasl then | |
94 -- TODO Should maybe initialize SASL handler after parsing the line? | |
95 sasl = self.g_sasl:clean_clone(); | |
96 self.sasl[id] = sasl; | |
97 if not sasl:select(mech) then | |
98 self:send("FAIL", id, "reason=invalid-mechanism"); | |
99 self.sasl[id] = nil; | |
100 sasl = false | |
101 end | |
102 end | |
103 if sasl then | |
104 local params = {}; -- Not used for anything yet | |
105 for p in part do | |
106 local k,v = p:match("^([^=]*)=(.*)$"); | |
107 if k == "resp" then | |
108 self:log("debug", "params = %s", dump(params)); | |
109 v = base64.decode(v); | |
110 local status, ret, err = sasl:process(v); | |
111 self:log("debug", status); | |
112 if status == "challenge" then | |
113 self:send("CONT", id, base64.encode(ret)); | |
114 elseif status == "failure" then | |
115 self.sasl[id] = nil; | |
116 self:send("FAIL", id, "reason="..tostring(err)); | |
117 elseif status == "success" then | |
118 self.sasl[id] = nil; | |
119 self:send("OK", id, "user="..sasl.username, ret and "resp="..base64.encode(ret)); | |
120 end | |
121 break; -- resp MUST be the last param | |
122 else | |
123 params[k or p] = v or true; | |
124 end | |
125 end | |
126 end | |
127 elseif command == "USER" and self.type == "M" then | |
128 -- FIXME Should this be on a separate listener? | |
129 local id = part(); | |
130 local user = part(); | |
131 if user and user_exists(user, vhost) then | |
132 self:send("USER", id); | |
133 else | |
134 self:send("NOTFOUND", id); | |
135 end | |
136 else | |
137 self:log("warn", "Unhandled command %s", tostring(command)); | |
138 self.conn:close(); | |
139 break; | |
140 end | |
141 line = buf:read("*l"); | |
142 end | |
143 end | |
144 | |
145 end | |
146 | |
147 local listener = {} | |
148 | |
149 function listener.onconnect(conn) | |
150 s = new_session(conn); | |
151 sessions[conn] = s; | |
152 local g_sasl = new_sasl(vhost, s); | |
153 s.g_sasl = g_sasl; | |
154 s:handshake(); | |
155 end | |
156 | |
157 function listener.onincoming(conn, data) | |
158 local s = sessions[conn]; | |
159 -- s:log("debug", "RECV %s", dump(data)); | |
160 return s:feed(data); | |
161 end | |
162 | |
163 function listener.ondisconnect(conn) | |
164 sessions[conn] = nil; | |
165 end | |
166 | |
167 function module.unload() | |
168 for c in pairs(sessions) do | |
169 c:close(); | |
170 end | |
171 end | |
172 | |
173 module:provides("net", { | |
174 default_port = 28484; | |
175 listener = listener; | |
176 }); | |
177 |