Mercurial > prosody-modules
comparison mod_auth_imap/auth_imap/sasl_imap.lib.lua @ 1196:f45ca6edc159
mod_auth_imap: Authentication module that works by passing through SASL to a IMAP connection
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Thu, 26 Sep 2013 13:43:27 +0200 |
parents | |
children | 5d46281a5d23 |
comparison
equal
deleted
inserted
replaced
1195:f502cbffbdd4 | 1196:f45ca6edc159 |
---|---|
1 -- Dovecot authentication backend for Prosody | |
2 -- | |
3 -- Copyright (C) 2011 Kim Alvefur | |
4 -- | |
5 | |
6 local log = require "util.logger".init("sasl_imap"); | |
7 | |
8 local setmetatable = setmetatable; | |
9 | |
10 local s_match, s_gmatch = string.match, string.gmatch | |
11 local t_concat = table.concat; | |
12 local m_random = math.random; | |
13 local tostring, tonumber = tostring, tonumber; | |
14 | |
15 local socket = require "socket" | |
16 -- TODO -- local ssl = require "ssl" | |
17 local base64 = require "util.encodings".base64; | |
18 local b64, unb64 = base64.encode, base64.decode; | |
19 | |
20 local _M = {}; | |
21 | |
22 local method = {}; | |
23 method.__index = method; | |
24 | |
25 -- For extracting the username. | |
26 local mitm = { | |
27 PLAIN = function(message) | |
28 return s_match(message, "^[^%z]*%z([^%z]+)%z[^%z]+"); | |
29 end, | |
30 ["SCRAM-SHA-1"] = function(message) | |
31 return s_match(message, "^[^,]+,[^,]*,n=([^,]*)"); | |
32 end, | |
33 ["DIGEST-MD5"] = function(message) | |
34 return s_match(message, "username=\"([^\"]*)\""); | |
35 end, | |
36 } | |
37 | |
38 local function connect(host, port, ssl) | |
39 port = tonumber(port) or (ssl and 993 or 143); | |
40 log("debug", "connect() to %s:%s:%d", ssl and "ssl" or "tcp", host, tonumber(port)); | |
41 local conn = socket.tcp(); | |
42 | |
43 -- Create a connection to imap socket | |
44 log("debug", "connecting to imap at '%s:%d'", host, port); | |
45 local ok, err = conn:connect(host, port); | |
46 conn:settimeout(10); | |
47 if not ok then | |
48 log("error", "error connecting to imap at '%s:%d'. error was '%s'. check permissions", host, port, err); | |
49 return false; | |
50 end | |
51 | |
52 -- Parse IMAP handshake | |
53 local done = false; | |
54 local supported_mechs = {}; | |
55 local line = conn:receive("*l"); | |
56 log("debug", "imap handshake: '%s'", line); | |
57 if not line then | |
58 return false; | |
59 end | |
60 local caps = line:match("^%*%s+OK%s+(%b[])"); | |
61 if caps then | |
62 caps = caps:sub(2,-2); | |
63 for cap in caps:gmatch("%S+") do | |
64 log("debug", "Capability: %s", cap); | |
65 local mech = cap:match("AUTH=(.*)"); | |
66 if mech then | |
67 log("debug", "Supported SASL mechanism: %s", mech); | |
68 supported_mechs[mech] = mitm[mech] and true or nil; | |
69 end | |
70 end | |
71 end | |
72 | |
73 return conn, supported_mechs; | |
74 end | |
75 | |
76 -- create a new SASL object which can be used to authenticate clients | |
77 function _M.new(realm, service_name, host, port, ssl) | |
78 log("debug", "new(%q, %q, %q, %d)", realm or "", service_name or "", host or "", port or 0); | |
79 local sasl_i = { | |
80 realm = realm, | |
81 service_name = service_name, | |
82 _host = host, | |
83 _port = port, | |
84 _ssl = ssl | |
85 }; | |
86 | |
87 local conn, mechs = connect(host, port, ssl); | |
88 if not conn then | |
89 return nil, "Socket connection failure"; | |
90 end | |
91 sasl_i.conn, sasl_i.mechs = conn, mechs; | |
92 return setmetatable(sasl_i, method); | |
93 end | |
94 | |
95 -- get a fresh clone with the same realm and service name | |
96 function method:clean_clone() | |
97 if self.conn then | |
98 self.conn:close(); | |
99 self.conn = nil; | |
100 end | |
101 log("debug", "method:clean_clone()"); | |
102 return _M.new(self.realm, self.service_name, self._host, self._port) | |
103 end | |
104 | |
105 -- get a list of possible SASL mechanisms to use | |
106 function method:mechanisms() | |
107 log("debug", "method:mechanisms()"); | |
108 return self.mechs; | |
109 end | |
110 | |
111 -- select a mechanism to use | |
112 function method:select(mechanism) | |
113 log("debug", "method:select(%q)", mechanism); | |
114 if not self.selected and self.mechs[mechanism] then | |
115 self.tag = tostring({}):match("0x(%x*)$"); | |
116 self.selected = mechanism; | |
117 local selectmsg = t_concat({ self.tag, "AUTHENTICATE", mechanism }, " "); | |
118 log("debug", "Sending %d bytes: %q", #selectmsg, selectmsg); | |
119 local ok, err = self.conn:send(selectmsg.."\n"); | |
120 if not ok then | |
121 log("error", "Could not write to socket: %s", err); | |
122 return "failure", "internal-server-error", err | |
123 end | |
124 local line, err = self.conn:receive("*l"); | |
125 if not line then | |
126 log("error", "Could not read from socket: %s", err); | |
127 return "failure", "internal-server-error", err | |
128 end | |
129 log("debug", "Received %d bytes: %q", #line, line); | |
130 return line:match("^+") | |
131 end | |
132 end | |
133 | |
134 -- feed new messages to process into the library | |
135 function method:process(message) | |
136 local username = mitm[self.selected](message); | |
137 if username then self.username = username; end | |
138 log("debug", "method:process(%d bytes)", #message); | |
139 local ok, err = self.conn:send(b64(message).."\n"); | |
140 if not ok then | |
141 log("error", "Could not write to socket: %s", err); | |
142 return "failure", "internal-server-error", err | |
143 end | |
144 log("debug", "Sent %d bytes to socket", ok); | |
145 local line, err = self.conn:receive("*l"); | |
146 if not line then | |
147 log("error", "Could not read from socket: %s", err); | |
148 return "failure", "internal-server-error", err | |
149 end | |
150 log("debug", "Received %d bytes from socket: %s", #line, line); | |
151 | |
152 if line:match("^%+") and #line > 2 then | |
153 local data = line:sub(3); | |
154 data = data and unb64(data); | |
155 return "challenge", unb64(data); | |
156 elseif line:sub(1, #self.tag) == self.tag then | |
157 local ok, rest = line:sub(#self.tag+1):match("(%w+)%s+(.*)"); | |
158 ok = ok:lower(); | |
159 log("debug", "%s: %s", ok, rest); | |
160 if ok == "ok" then | |
161 return "success" | |
162 elseif ok == "no" then | |
163 return "failure", "not-authorized", rest; | |
164 end | |
165 elseif line:match("^%* BYE") then | |
166 local err = line:match("BYE%s*(.*)"); | |
167 return "failure", "not-authorized", err; | |
168 end | |
169 end | |
170 | |
171 return _M; |