Software /
code /
prosody
Comparison
util/sasl.lua @ 2186:1112871916eb sasl
Move each mechanism in an own file.
author | Tobias Markmann <tm@ayena.de> |
---|---|
date | Thu, 12 Nov 2009 21:57:37 +0100 |
parent | 2185:e92339c48ee6 |
child | 2187:f0a85d11823e |
comparison
equal
deleted
inserted
replaced
2185:e92339c48ee6 | 2186:1112871916eb |
---|---|
117 function method:process(message) | 117 function method:process(message) |
118 if message == "" or message == nil then return "failure", "malformed-request" end | 118 if message == "" or message == nil then return "failure", "malformed-request" end |
119 return self.mech_i(self, message); | 119 return self.mech_i(self, message); |
120 end | 120 end |
121 | 121 |
122 --========================= | 122 -- load the mechanisms |
123 --SASL PLAIN according to RFC 4616 | 123 require "sasl.plain" |
124 local function sasl_mechanism_plain(self, message) | 124 require "sasl.digest-md5" |
125 local response = message | 125 require "sasl.scram" |
126 local authorization = s_match(response, "([^%z]+)") | |
127 local authentication = s_match(response, "%z([^%z]+)%z") | |
128 local password = s_match(response, "%z[^%z]+%z([^%z]+)") | |
129 | |
130 if authentication == nil or password == nil then | |
131 return "failure", "malformed-request"; | |
132 end | |
133 | |
134 local correct, state = false, false; | |
135 if self.profile.plain then | |
136 local correct_password; | |
137 correct_password, state = self.profile.plain(authentication, self.realm); | |
138 if correct_password == password then correct = true; else correct = false; end | |
139 elseif self.profile.plain_test then | |
140 correct, state = self.profile.plain_test(authentication, self.realm, password); | |
141 end | |
142 | |
143 self.username = authentication | |
144 if not state then | |
145 return "failure", "account-disabled"; | |
146 end | |
147 | |
148 if correct then | |
149 return "success"; | |
150 else | |
151 return "failure", "not-authorized"; | |
152 end | |
153 end | |
154 registerMechanism("PLAIN", {"plain", "plain_test"}, sasl_mechanism_plain); | |
155 | |
156 --========================= | |
157 --SASL DIGEST-MD5 according to RFC 2831 | |
158 local function sasl_mechanism_digest_md5(self, message) | |
159 --TODO complete support for authzid | |
160 | |
161 local function serialize(message) | |
162 local data = "" | |
163 | |
164 if type(message) ~= "table" then error("serialize needs an argument of type table.") end | |
165 | |
166 -- testing all possible values | |
167 if message["realm"] then data = data..[[realm="]]..message.realm..[[",]] end | |
168 if message["nonce"] then data = data..[[nonce="]]..message.nonce..[[",]] end | |
169 if message["qop"] then data = data..[[qop="]]..message.qop..[[",]] end | |
170 if message["charset"] then data = data..[[charset=]]..message.charset.."," end | |
171 if message["algorithm"] then data = data..[[algorithm=]]..message.algorithm.."," end | |
172 if message["rspauth"] then data = data..[[rspauth=]]..message.rspauth.."," end | |
173 data = data:gsub(",$", "") | |
174 return data | |
175 end | |
176 | |
177 local function utf8tolatin1ifpossible(passwd) | |
178 local i = 1; | |
179 while i <= #passwd do | |
180 local passwd_i = to_byte(passwd:sub(i, i)); | |
181 if passwd_i > 0x7F then | |
182 if passwd_i < 0xC0 or passwd_i > 0xC3 then | |
183 return passwd; | |
184 end | |
185 i = i + 1; | |
186 passwd_i = to_byte(passwd:sub(i, i)); | |
187 if passwd_i < 0x80 or passwd_i > 0xBF then | |
188 return passwd; | |
189 end | |
190 end | |
191 i = i + 1; | |
192 end | |
193 | |
194 local p = {}; | |
195 local j = 0; | |
196 i = 1; | |
197 while (i <= #passwd) do | |
198 local passwd_i = to_byte(passwd:sub(i, i)); | |
199 if passwd_i > 0x7F then | |
200 i = i + 1; | |
201 local passwd_i_1 = to_byte(passwd:sub(i, i)); | |
202 t_insert(p, to_char(passwd_i%4*64 + passwd_i_1%64)); -- I'm so clever | |
203 else | |
204 t_insert(p, to_char(passwd_i)); | |
205 end | |
206 i = i + 1; | |
207 end | |
208 return t_concat(p); | |
209 end | |
210 local function latin1toutf8(str) | |
211 local p = {}; | |
212 for ch in gmatch(str, ".") do | |
213 ch = to_byte(ch); | |
214 if (ch < 0x80) then | |
215 t_insert(p, to_char(ch)); | |
216 elseif (ch < 0xC0) then | |
217 t_insert(p, to_char(0xC2, ch)); | |
218 else | |
219 t_insert(p, to_char(0xC3, ch - 64)); | |
220 end | |
221 end | |
222 return t_concat(p); | |
223 end | |
224 local function parse(data) | |
225 local message = {} | |
226 for k, v in gmatch(data, [[([%w%-]+)="?([^",]*)"?,?]]) do -- FIXME The hacky regex makes me shudder | |
227 message[k] = v; | |
228 end | |
229 return message; | |
230 end | |
231 | |
232 if not self.nonce then | |
233 self.nonce = generate_uuid(); | |
234 self.step = 0; | |
235 self.nonce_count = {}; | |
236 end | |
237 | |
238 self.step = self.step + 1; | |
239 if (self.step == 1) then | |
240 local challenge = serialize({ nonce = object.nonce, | |
241 qop = "auth", | |
242 charset = "utf-8", | |
243 algorithm = "md5-sess", | |
244 realm = self.realm}); | |
245 return "challenge", challenge; | |
246 elseif (self.step == 2) then | |
247 local response = parse(message); | |
248 -- check for replay attack | |
249 if response["nc"] then | |
250 if self.nonce_count[response["nc"]] then return "failure", "not-authorized" end | |
251 end | |
252 | |
253 -- check for username, it's REQUIRED by RFC 2831 | |
254 if not response["username"] then | |
255 return "failure", "malformed-request"; | |
256 end | |
257 self["username"] = response["username"]; | |
258 | |
259 -- check for nonce, ... | |
260 if not response["nonce"] then | |
261 return "failure", "malformed-request"; | |
262 else | |
263 -- check if it's the right nonce | |
264 if response["nonce"] ~= tostring(self.nonce) then return "failure", "malformed-request" end | |
265 end | |
266 | |
267 if not response["cnonce"] then return "failure", "malformed-request", "Missing entry for cnonce in SASL message." end | |
268 if not response["qop"] then response["qop"] = "auth" end | |
269 | |
270 if response["realm"] == nil or response["realm"] == "" then | |
271 response["realm"] = ""; | |
272 elseif response["realm"] ~= self.realm then | |
273 return "failure", "not-authorized", "Incorrect realm value"; | |
274 end | |
275 | |
276 local decoder; | |
277 if response["charset"] == nil then | |
278 decoder = utf8tolatin1ifpossible; | |
279 elseif response["charset"] ~= "utf-8" then | |
280 return "failure", "incorrect-encoding", "The client's response uses "..response["charset"].." for encoding with isn't supported by sasl.lua. Supported encodings are latin or utf-8."; | |
281 end | |
282 | |
283 local domain = ""; | |
284 local protocol = ""; | |
285 if response["digest-uri"] then | |
286 protocol, domain = response["digest-uri"]:match("(%w+)/(.*)$"); | |
287 if protocol == nil or domain == nil then return "failure", "malformed-request" end | |
288 else | |
289 return "failure", "malformed-request", "Missing entry for digest-uri in SASL message." | |
290 end | |
291 | |
292 --TODO maybe realm support | |
293 self.username = response["username"]; | |
294 local password_encoding, Y = self.credentials_handler("DIGEST-MD5", response["username"], self.realm, response["realm"], decoder); | |
295 if Y == nil then return "failure", "not-authorized" | |
296 elseif Y == false then return "failure", "account-disabled" end | |
297 local A1 = ""; | |
298 if response.authzid then | |
299 if response.authzid == self.username.."@"..self.realm then | |
300 -- COMPAT | |
301 log("warn", "Client is violating XMPP RFC. See section 6.1 of RFC 3920."); | |
302 A1 = Y..":"..response["nonce"]..":"..response["cnonce"]..":"..response.authzid; | |
303 else | |
304 A1 = "?"; | |
305 end | |
306 else | |
307 A1 = Y..":"..response["nonce"]..":"..response["cnonce"]; | |
308 end | |
309 local A2 = "AUTHENTICATE:"..protocol.."/"..domain; | |
310 | |
311 local HA1 = md5(A1, true); | |
312 local HA2 = md5(A2, true); | |
313 | |
314 local KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2; | |
315 local response_value = md5(KD, true); | |
316 | |
317 if response_value == response["response"] then | |
318 -- calculate rspauth | |
319 A2 = ":"..protocol.."/"..domain; | |
320 | |
321 HA1 = md5(A1, true); | |
322 HA2 = md5(A2, true); | |
323 | |
324 KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2 | |
325 local rspauth = md5(KD, true); | |
326 self.authenticated = true; | |
327 return "challenge", serialize({rspauth = rspauth}); | |
328 else | |
329 return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated." | |
330 end | |
331 elseif self.step == 3 then | |
332 if self.authenticated ~= nil then return "success" | |
333 else return "failure", "malformed-request" end | |
334 end | |
335 end | |
336 | 126 |
337 return _M; | 127 return _M; |