Software /
code /
prosody-modules
Comparison
mod_auth_imap/auth_imap/sasl_imap.lib.lua @ 1200:34216cdffda6
mod_auth_imap: unfortunately large commit which adds support for SSL (including cert verification), appending the realm to usernames, and various IMAP protocol fixes
author | Matthew Wild <mwild1@gmail.com> |
---|---|
date | Thu, 26 Sep 2013 18:14:45 +0100 |
parent | 1199:5d46281a5d23 |
child | 1343:7dbde05b48a9 |
comparison
equal
deleted
inserted
replaced
1199:5d46281a5d23 | 1200:34216cdffda6 |
---|---|
10 local s_match = string.match; | 10 local s_match = string.match; |
11 local t_concat = table.concat; | 11 local t_concat = table.concat; |
12 local tostring, tonumber = tostring, tonumber; | 12 local tostring, tonumber = tostring, tonumber; |
13 | 13 |
14 local socket = require "socket" | 14 local socket = require "socket" |
15 -- TODO -- local ssl = require "ssl" | 15 local ssl = require "ssl" |
16 local x509 = require "util.x509"; | |
16 local base64 = require "util.encodings".base64; | 17 local base64 = require "util.encodings".base64; |
17 local b64, unb64 = base64.encode, base64.decode; | 18 local b64, unb64 = base64.encode, base64.decode; |
18 | 19 |
19 local _M = {}; | 20 local _M = {}; |
20 | 21 |
32 ["DIGEST-MD5"] = function(message) | 33 ["DIGEST-MD5"] = function(message) |
33 return s_match(message, "username=\"([^\"]*)\""); | 34 return s_match(message, "username=\"([^\"]*)\""); |
34 end, | 35 end, |
35 } | 36 } |
36 | 37 |
37 local function connect(host, port, ssl) | 38 local function connect(host, port, ssl_params) |
38 port = tonumber(port) or (ssl and 993 or 143); | 39 port = tonumber(port) or (ssl_params and 993 or 143); |
39 log("debug", "connect() to %s:%s:%d", ssl and "ssl" or "tcp", host, tonumber(port)); | 40 log("debug", "connect() to %s:%s:%d", ssl_params and "ssl" or "tcp", host, tonumber(port)); |
40 local conn = socket.tcp(); | 41 local conn = socket.tcp(); |
41 | 42 |
42 -- Create a connection to imap socket | 43 -- Create a connection to imap socket |
43 log("debug", "connecting to imap at '%s:%d'", host, port); | 44 log("debug", "connecting to imap at '%s:%d'", host, port); |
44 local ok, err = conn:connect(host, port); | 45 local ok, err = conn:connect(host, port); |
45 conn:settimeout(10); | 46 conn:settimeout(10); |
46 if not ok then | 47 if not ok then |
47 log("error", "error connecting to imap at '%s:%d'. error was '%s'. check permissions", host, port, err); | 48 log("error", "error connecting to imap at '%s:%d': %s", host, port, err); |
48 return false; | 49 return false; |
49 end | 50 end |
50 | 51 |
52 if ssl_params then | |
53 -- Perform SSL handshake | |
54 local ok, err = ssl.wrap(conn, ssl_params); | |
55 if ok then | |
56 conn = ok; | |
57 ok, err = conn:dohandshake(); | |
58 end | |
59 if not ok then | |
60 log("error", "error initializing ssl connection to imap at '%s:%d': %s", host, port, err); | |
61 conn:close(); | |
62 return false; | |
63 end | |
64 | |
65 -- Verify certificate | |
66 if ssl_params.verify then | |
67 if not conn.getpeercertificate then | |
68 log("error", "unable to verify certificate, newer LuaSec required: https://prosody.im/doc/depends#luasec"); | |
69 conn:close(); | |
70 return false; | |
71 end | |
72 if not x509.verify_identity(host, nil, conn:getpeercertificate()) then | |
73 log("warn", "invalid certificate for imap service %s:%d, denying connection", host, port); | |
74 return false; | |
75 end | |
76 end | |
77 end | |
78 | |
51 -- Parse IMAP handshake | 79 -- Parse IMAP handshake |
52 local done = false; | |
53 local supported_mechs = {}; | 80 local supported_mechs = {}; |
54 local line = conn:receive("*l"); | 81 local line = conn:receive("*l"); |
55 log("debug", "imap handshake: '%s'", line); | |
56 if not line then | 82 if not line then |
57 return false; | 83 return false; |
58 end | 84 end |
85 log("debug", "imap greeting: '%s'", line); | |
59 local caps = line:match("^%*%s+OK%s+(%b[])"); | 86 local caps = line:match("^%*%s+OK%s+(%b[])"); |
87 if not caps or not caps:match("^%[CAPABILITY ") then | |
88 conn:send("A CAPABILITY\n"); | |
89 line = conn:receive("*l"); | |
90 log("debug", "imap capabilities response: '%s'", line); | |
91 caps = line:match("^%*%s+CAPABILITY%s+(.*)$"); | |
92 if not conn:receive("*l"):match("^A OK") then | |
93 log("debug", "imap capabilities command failed") | |
94 conn:close(); | |
95 return false; | |
96 end | |
97 elseif caps then | |
98 caps = caps:sub(2,-2); -- Strip surrounding [] | |
99 end | |
60 if caps then | 100 if caps then |
61 caps = caps:sub(2,-2); | |
62 for cap in caps:gmatch("%S+") do | 101 for cap in caps:gmatch("%S+") do |
63 log("debug", "Capability: %s", cap); | 102 log("debug", "Capability: %s", cap); |
64 local mech = cap:match("AUTH=(.*)"); | 103 local mech = cap:match("AUTH=(.*)"); |
65 if mech then | 104 if mech then |
66 log("debug", "Supported SASL mechanism: %s", mech); | 105 log("debug", "Supported SASL mechanism: %s", mech); |
71 | 110 |
72 return conn, supported_mechs; | 111 return conn, supported_mechs; |
73 end | 112 end |
74 | 113 |
75 -- create a new SASL object which can be used to authenticate clients | 114 -- create a new SASL object which can be used to authenticate clients |
76 function _M.new(realm, service_name, host, port, ssl) | 115 function _M.new(realm, service_name, host, port, ssl_params, append_host) |
77 log("debug", "new(%q, %q, %q, %d)", realm or "", service_name or "", host or "", port or 0); | 116 log("debug", "new(%q, %q, %q, %d)", realm or "", service_name or "", host or "", port or 0); |
78 local sasl_i = { | 117 local sasl_i = { |
79 realm = realm, | 118 realm = realm; |
80 service_name = service_name, | 119 service_name = service_name; |
81 _host = host, | 120 _host = host; |
82 _port = port, | 121 _port = port; |
83 _ssl = ssl | 122 _ssl_params = ssl_params; |
123 _append_host = append_host; | |
84 }; | 124 }; |
85 | 125 |
86 local conn, mechs = connect(host, port, ssl); | 126 local conn, mechs = connect(host, port, ssl_params); |
87 if not conn then | 127 if not conn then |
88 return nil, "Socket connection failure"; | 128 return nil, "Socket connection failure"; |
129 end | |
130 if append_host then | |
131 mechs = { PLAIN = mechs.PLAIN }; | |
89 end | 132 end |
90 sasl_i.conn, sasl_i.mechs = conn, mechs; | 133 sasl_i.conn, sasl_i.mechs = conn, mechs; |
91 return setmetatable(sasl_i, method); | 134 return setmetatable(sasl_i, method); |
92 end | 135 end |
93 | 136 |
96 if self.conn then | 139 if self.conn then |
97 self.conn:close(); | 140 self.conn:close(); |
98 self.conn = nil; | 141 self.conn = nil; |
99 end | 142 end |
100 log("debug", "method:clean_clone()"); | 143 log("debug", "method:clean_clone()"); |
101 return _M.new(self.realm, self.service_name, self._host, self._port) | 144 return _M.new(self.realm, self.service_name, self._host, self._port, self._ssl_params, self._append_host) |
102 end | 145 end |
103 | 146 |
104 -- get a list of possible SASL mechanisms to use | 147 -- get a list of possible SASL mechanisms to use |
105 function method:mechanisms() | 148 function method:mechanisms() |
106 log("debug", "method:mechanisms()"); | 149 log("debug", "method:mechanisms()"); |
132 | 175 |
133 -- feed new messages to process into the library | 176 -- feed new messages to process into the library |
134 function method:process(message) | 177 function method:process(message) |
135 local username = mitm[self.selected](message); | 178 local username = mitm[self.selected](message); |
136 if username then self.username = username; end | 179 if username then self.username = username; end |
137 log("debug", "method:process(%d bytes)", #message); | 180 if self._append_host and self.selected == "PLAIN" then |
181 message = message:gsub("^([^%z]*%z[^%z]+)(%z[^%z]+)$", "%1@"..self.realm.."%2"); | |
182 end | |
183 log("debug", "method:process(%d bytes): %q", #message, message:gsub("%z", ".")); | |
138 local ok, err = self.conn:send(b64(message).."\n"); | 184 local ok, err = self.conn:send(b64(message).."\n"); |
139 if not ok then | 185 if not ok then |
140 log("error", "Could not write to socket: %s", err); | 186 log("error", "Could not write to socket: %s", err); |
141 return "failure", "internal-server-error", err | 187 return "failure", "internal-server-error", err |
142 end | 188 end |
145 if not line then | 191 if not line then |
146 log("error", "Could not read from socket: %s", err); | 192 log("error", "Could not read from socket: %s", err); |
147 return "failure", "internal-server-error", err | 193 return "failure", "internal-server-error", err |
148 end | 194 end |
149 log("debug", "Received %d bytes from socket: %s", #line, line); | 195 log("debug", "Received %d bytes from socket: %s", #line, line); |
196 | |
197 while line and line:match("^%* ") do | |
198 line, err = self.conn:receive("*l"); | |
199 end | |
150 | 200 |
151 if line:match("^%+") and #line > 2 then | 201 if line:match("^%+") and #line > 2 then |
152 local data = line:sub(3); | 202 local data = line:sub(3); |
153 data = data and unb64(data); | 203 data = data and unb64(data); |
154 return "challenge", unb64(data); | 204 return "challenge", unb64(data); |