Software / code / prosody
Comparison
util/sasl.lua @ 1166:5499a028d4ae
Automated merge with http://waqas.ath.cx:8000/
| author | Matthew Wild <mwild1@gmail.com> |
|---|---|
| date | Fri, 15 May 2009 20:38:30 +0100 |
| parent | 1161:5bc2b7b5b81d |
| child | 1305:37657578ea85 |
comparison
equal
deleted
inserted
replaced
| 1165:ec69bcc7ceb5 | 1166:5499a028d4ae |
|---|---|
| 58 end | 58 end |
| 59 end | 59 end |
| 60 return object | 60 return object |
| 61 end | 61 end |
| 62 | 62 |
| 63 | |
| 64 -- implementing RFC 2831 | |
| 63 local function new_digest_md5(realm, password_handler) | 65 local function new_digest_md5(realm, password_handler) |
| 64 --TODO maybe support for authzid | 66 --TODO complete support for authzid |
| 65 | 67 |
| 66 local function serialize(message) | 68 local function serialize(message) |
| 67 local data = "" | 69 local data = "" |
| 68 | 70 |
| 69 if type(message) ~= "table" then error("serialize needs an argument of type table.") end | 71 if type(message) ~= "table" then error("serialize needs an argument of type table.") end |
| 127 return t_concat(p); | 129 return t_concat(p); |
| 128 end | 130 end |
| 129 local function parse(data) | 131 local function parse(data) |
| 130 message = {} | 132 message = {} |
| 131 for k, v in gmatch(data, [[([%w%-]+)="?([^",]*)"?,?]]) do -- FIXME The hacky regex makes me shudder | 133 for k, v in gmatch(data, [[([%w%-]+)="?([^",]*)"?,?]]) do -- FIXME The hacky regex makes me shudder |
| 132 message[k] = v | 134 message[k] = v; |
| 133 end | 135 end |
| 134 return message | 136 return message; |
| 135 end | 137 end |
| 136 | 138 |
| 137 local object = { mechanism = "DIGEST-MD5", realm = realm, password_handler = password_handler} | 139 local object = { mechanism = "DIGEST-MD5", realm = realm, password_handler = password_handler}; |
| 138 | 140 |
| 139 --TODO: something better than math.random would be nice, maybe OpenSSL's random number generator | 141 --TODO: something better than math.random would be nice, maybe OpenSSL's random number generator |
| 140 object.nonce = generate_uuid() | 142 object.nonce = generate_uuid(); |
| 141 object.step = 0 | 143 object.step = 0; |
| 142 object.nonce_count = {} | 144 object.nonce_count = {}; |
| 143 | 145 |
| 144 function object.feed(self, message) | 146 function object.feed(self, message) |
| 145 self.step = self.step + 1 | 147 self.step = self.step + 1; |
| 146 if (self.step == 1) then | 148 if (self.step == 1) then |
| 147 local challenge = serialize({ nonce = object.nonce, | 149 local challenge = serialize({ nonce = object.nonce, |
| 148 qop = "auth", | 150 qop = "auth", |
| 149 charset = "utf-8", | 151 charset = "utf-8", |
| 150 algorithm = "md5-sess", | 152 algorithm = "md5-sess", |
| 151 realm = self.realm}); | 153 realm = self.realm}); |
| 152 return "challenge", challenge | 154 return "challenge", challenge; |
| 153 elseif (self.step == 2) then | 155 elseif (self.step == 2) then |
| 154 local response = parse(message) | 156 local response = parse(message); |
| 155 -- check for replay attack | 157 -- check for replay attack |
| 156 if response["nc"] then | 158 if response["nc"] then |
| 157 if self.nonce_count[response["nc"]] then return "failure", "not-authorized" end | 159 if self.nonce_count[response["nc"]] then return "failure", "not-authorized" end |
| 158 end | 160 end |
| 159 | 161 |
| 160 -- check for username, it's REQUIRED by RFC 2831 | 162 -- check for username, it's REQUIRED by RFC 2831 |
| 161 if not response["username"] then | 163 if not response["username"] then |
| 162 return "failure", "malformed-request" | 164 return "failure", "malformed-request"; |
| 163 end | 165 end |
| 164 self["username"] = response["username"] | 166 self["username"] = response["username"]; |
| 165 | 167 |
| 166 -- check for nonce, ... | 168 -- check for nonce, ... |
| 167 if not response["nonce"] then | 169 if not response["nonce"] then |
| 168 return "failure", "malformed-request" | 170 return "failure", "malformed-request"; |
| 169 else | 171 else |
| 170 -- check if it's the right nonce | 172 -- check if it's the right nonce |
| 171 if response["nonce"] ~= tostring(self.nonce) then return "failure", "malformed-request" end | 173 if response["nonce"] ~= tostring(self.nonce) then return "failure", "malformed-request" end |
| 172 end | 174 end |
| 173 | 175 |
| 182 | 184 |
| 183 local decoder; | 185 local decoder; |
| 184 if response["charset"] == nil then | 186 if response["charset"] == nil then |
| 185 decoder = utf8tolatin1ifpossible; | 187 decoder = utf8tolatin1ifpossible; |
| 186 elseif response["charset"] ~= "utf-8" then | 188 elseif response["charset"] ~= "utf-8" then |
| 187 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." | 189 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."; |
| 188 end | 190 end |
| 189 | 191 |
| 190 local domain = "" | 192 local domain = ""; |
| 191 local protocol = "" | 193 local protocol = ""; |
| 192 if response["digest-uri"] then | 194 if response["digest-uri"] then |
| 193 protocol, domain = response["digest-uri"]:match("(%w+)/(.*)$") | 195 protocol, domain = response["digest-uri"]:match("(%w+)/(.*)$"); |
| 194 if protocol == nil or domain == nil then return "failure", "malformed-request" end | 196 if protocol == nil or domain == nil then return "failure", "malformed-request" end |
| 195 else | 197 else |
| 196 return "failure", "malformed-request", "Missing entry for digest-uri in SASL message." | 198 return "failure", "malformed-request", "Missing entry for digest-uri in SASL message." |
| 197 end | 199 end |
| 198 | 200 |
| 199 --TODO maybe realm support | 201 --TODO maybe realm support |
| 200 self.username = response["username"] | 202 self.username = response["username"]; |
| 201 local password_encoding, Y = self.password_handler(response["username"], response["realm"], "DIGEST-MD5", decoder) | 203 local password_encoding, Y = self.password_handler(response["username"], response["realm"], "DIGEST-MD5", decoder) |
| 202 if Y == nil then return "failure", "not-authorized" | 204 if Y == nil then return "failure", "not-authorized" |
| 203 elseif Y == false then return "failure", "account-disabled" end | 205 elseif Y == false then return "failure", "account-disabled" end |
| 204 | 206 local A1 = ""; |
| 205 local A1 = Y..":"..response["nonce"]..":"..response["cnonce"]--:authzid | 207 if response.authzid then |
| 208 if response.authzid == self.username.."@"..self.realm then | |
| 209 log("warn", "Client is violating XMPP RFC. See section 6.1 of RFC 3920."); | |
| 210 A1 = Y..":"..response["nonce"]..":"..response["cnonce"]..":"..response.authzid; | |
| 211 else | |
| 212 A1 = "?"; | |
| 213 end | |
| 214 else | |
| 215 A1 = Y..":"..response["nonce"]..":"..response["cnonce"]; | |
| 216 end | |
| 206 local A2 = "AUTHENTICATE:"..protocol.."/"..domain; | 217 local A2 = "AUTHENTICATE:"..protocol.."/"..domain; |
| 207 | 218 |
| 208 local HA1 = md5(A1, true) | 219 local HA1 = md5(A1, true); |
| 209 local HA2 = md5(A2, true) | 220 local HA2 = md5(A2, true); |
| 210 | 221 |
| 211 local KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2 | 222 local KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2; |
| 212 local response_value = md5(KD, true) | 223 local response_value = md5(KD, true); |
| 213 | 224 |
| 214 if response_value == response["response"] then | 225 if response_value == response["response"] then |
| 215 -- calculate rspauth | 226 -- calculate rspauth |
| 216 A2 = ":"..protocol.."/"..domain; | 227 A2 = ":"..protocol.."/"..domain; |
| 217 | 228 |
| 218 HA1 = md5(A1, true) | 229 HA1 = md5(A1, true); |
| 219 HA2 = md5(A2, true) | 230 HA2 = md5(A2, true); |
| 220 | 231 |
| 221 KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2 | 232 KD = HA1..":"..response["nonce"]..":"..response["nc"]..":"..response["cnonce"]..":"..response["qop"]..":"..HA2 |
| 222 local rspauth = md5(KD, true) | 233 local rspauth = md5(KD, true); |
| 223 self.authenticated = true | 234 self.authenticated = true; |
| 224 return "challenge", serialize({rspauth = rspauth}) | 235 return "challenge", serialize({rspauth = rspauth}); |
| 225 else | 236 else |
| 226 return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated." | 237 return "failure", "not-authorized", "The response provided by the client doesn't match the one we calculated." |
| 227 end | 238 end |
| 228 elseif self.step == 3 then | 239 elseif self.step == 3 then |
| 229 if self.authenticated ~= nil then return "success" | 240 if self.authenticated ~= nil then return "success" |
| 230 else return "failure", "malformed-request" end | 241 else return "failure", "malformed-request" end |
| 231 end | 242 end |
| 232 end | 243 end |
| 233 return object | 244 return object; |
| 234 end | 245 end |
| 235 | 246 |
| 236 local function new_anonymous(realm, password_handler) | 247 local function new_anonymous(realm, password_handler) |
| 237 local object = { mechanism = "ANONYMOUS", realm = realm, password_handler = password_handler} | 248 local object = { mechanism = "ANONYMOUS", realm = realm, password_handler = password_handler} |
| 238 function object.feed(self, message) | 249 function object.feed(self, message) |