comparison mod_auth_wordpress/mod_auth_wordpress.lua @ 423:524dda2ecb6a

mod_auth_wordpress: Initial commit.
author Kim Alvefur <zash@zash.se>
date Sun, 11 Sep 2011 21:42:16 +0200
parents mod_auth_phpbb3/mod_auth_phpbb3.lua@816d8e3e83a3
children 22935ef37284
comparison
equal deleted inserted replaced
422:1082856e4612 423:524dda2ecb6a
1 -- Wordpress authentication backend for Prosody
2 --
3 -- Copyright (C) 2011 Waqas Hussain
4 --
5
6 local log = require "util.logger".init("auth_sql");
7 local new_sasl = require "util.sasl".new;
8 local nodeprep = require "util.encodings".stringprep.nodeprep;
9 local saslprep = require "util.encodings".stringprep.saslprep;
10 local DBI = require "DBI"
11 local md5 = require "util.hashes".md5;
12 local uuid_gen = require "util.uuid".generate;
13
14 local connection;
15 local params = module:get_option("sql");
16
17 local resolve_relative_path = require "core.configmanager".resolve_relative_path;
18
19 local function test_connection()
20 if not connection then return nil; end
21 if connection:ping() then
22 return true;
23 else
24 module:log("debug", "Database connection closed");
25 connection = nil;
26 end
27 end
28 local function connect()
29 if not test_connection() then
30 prosody.unlock_globals();
31 local dbh, err = DBI.Connect(
32 params.driver, params.database,
33 params.username, params.password,
34 params.host, params.port
35 );
36 prosody.lock_globals();
37 if not dbh then
38 module:log("debug", "Database connection failed: %s", tostring(err));
39 return nil, err;
40 end
41 module:log("debug", "Successfully connected to database");
42 dbh:autocommit(true); -- don't run in transaction
43 connection = dbh;
44 return connection;
45 end
46 end
47
48 do -- process options to get a db connection
49 params = params or { driver = "SQLite3" };
50
51 if params.driver == "SQLite3" then
52 params.database = resolve_relative_path(prosody.paths.data or ".", params.database or "prosody.sqlite");
53 end
54
55 assert(params.driver and params.database, "Both the SQL driver and the database need to be specified");
56
57 assert(connect());
58 end
59
60 local function getsql(sql, ...)
61 if params.driver == "PostgreSQL" then
62 sql = sql:gsub("`", "\"");
63 end
64 if not test_connection() then connect(); end
65 -- do prepared statement stuff
66 local stmt, err = connection:prepare(sql);
67 if not stmt and not test_connection() then error("connection failed"); end
68 if not stmt then module:log("error", "QUERY FAILED: %s %s", err, debug.traceback()); return nil, err; end
69 -- run query
70 local ok, err = stmt:execute(...);
71 if not ok and not test_connection() then error("connection failed"); end
72 if not ok then return nil, err; end
73
74 return stmt;
75 end
76 local function setsql(sql, ...)
77 local stmt, err = getsql(sql, ...);
78 if not stmt then return stmt, err; end
79 return stmt:affected();
80 end
81
82 local function get_password(username)
83 local stmt, err = getsql("SELECT `user_pass` FROM `wp_users` WHERE `user_login`=?", username);
84 if stmt then
85 for row in stmt:rows(true) do
86 return row.user_password;
87 end
88 end
89 end
90
91
92 local itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
93 local function hashEncode64(input, count)
94 local output = "";
95 local i, value = 0, 0;
96
97 while true do
98 value = input:byte(i+1)
99 i = i+1;
100 local idx = value % 0x40 + 1;
101 output = output .. itoa64:sub(idx, idx);
102
103 if i < count then
104 value = value + input:byte(i+1) * 256;
105 end
106 local _ = value % (2^6);
107 local idx = ((value - _) / (2^6)) % 0x40 + 1
108 output = output .. itoa64:sub(idx, idx);
109
110 if i >= count then break; end
111 i = i+1;
112
113 if i < count then
114 value = value + input:byte(i+1) * 256 * 256;
115 end
116 local _ = value % (2^12);
117 local idx = ((value - _) / (2^12)) % 0x40 + 1
118 output = output .. itoa64:sub(idx, idx);
119
120 if i >= count then break; end
121 i = i+1;
122
123 local _ = value % (2^18);
124 local idx = ((value - _) / (2^18)) % 0x40 + 1
125 output = output .. itoa64:sub(idx, idx);
126
127 if not(i < count) then break; end
128 end
129 return output;
130 end
131 local function hashCryptPrivate(password, genSalt)
132 local output = "*";
133 if not genSalt:match("^%$P%$") then return output; end
134
135 local count_log2 = itoa64:find(genSalt:sub(4,4)) - 1;
136 if count_log2 < 7 or count_log2 > 30 then return output; end
137
138 local count = 2 ^ count_log2;
139 local salt = genSalt:sub(5, 12);
140
141 if #salt ~= 8 then return output; end
142
143 local hash = md5(salt..password);
144
145 while true do
146 hash = md5(hash..password);
147 if not(count > 1) then break; end
148 count = count-1;
149 end
150
151 output = genSalt:sub(1, 12);
152 output = output .. hashEncode64(hash, 16);
153
154 return output;
155 end
156 local function hashGensaltPrivate(input)
157 local iteration_count_log2 = 6;
158 local output = "$H$";
159 local idx = math.min(iteration_count_log2 + 5, 30) + 1;
160 output = output .. itoa64:sub(idx, idx);
161 output = output .. hashEncode64(input, 6);
162 return output;
163 end
164 local function phpbbCheckHash(password, hash)
165 if #hash == 32 then return hash == md5(password, true); end -- legacy PHPBB2 hash
166 return #hash == 34 and hashCryptPrivate(password, hash) == hash;
167 end
168 local function phpbbCreateHash(password)
169 local random = uuid_gen():sub(-6);
170 local salt = hashGensaltPrivate(random);
171 local hash = hashCryptPrivate(password, salt);
172 if #hash == 34 then return hash; end
173 return md5(password, true);
174 end
175
176
177 provider = { name = "wordpress" };
178
179 function provider.test_password(username, password)
180 local hash = get_password(username);
181 return hash and phpbbCheckHash(password, hash);
182 end
183 function provider.user_exists(username)
184 module:log("debug", "test user %s existence", username);
185 return get_password(username) and true;
186 end
187
188 function provider.get_password(username)
189 return nil, "Getting password is not supported.";
190 end
191 function provider.set_password(username, password)
192 local hash = phpbbCreateHash(password);
193 local stmt, err = setsql("UPDATE `wp_users` SET `user_pass`=? WHERE `user_login`=?", hash, username);
194 return stmt and true, err;
195 end
196 function provider.create_user(username, password)
197 return nil, "Account creation/modification not supported.";
198 end
199
200 local escapes = {
201 [" "] = "\\20";
202 ['"'] = "\\22";
203 ["&"] = "\\26";
204 ["'"] = "\\27";
205 ["/"] = "\\2f";
206 [":"] = "\\3a";
207 ["<"] = "\\3c";
208 [">"] = "\\3e";
209 ["@"] = "\\40";
210 ["\\"] = "\\5c";
211 };
212 local unescapes = {};
213 for k,v in pairs(escapes) do unescapes[v] = k; end
214 local function jid_escape(s) return s and (s:gsub(".", escapes)); end
215 local function jid_unescape(s) return s and (s:gsub("\\%x%x", unescapes)); end
216
217 function provider.get_sasl_handler()
218 local sasl = {};
219 function sasl:clean_clone() return provider.get_sasl_handler(); end
220 function sasl:mechanisms() return { PLAIN = true; }; end
221 function sasl:select(mechanism)
222 if not self.selected and mechanism == "PLAIN" then
223 self.selected = mechanism;
224 return true;
225 end
226 end
227 function sasl:process(message)
228 if not message then return "failure", "malformed-request"; end
229 local authorization, authentication, password = message:match("^([^%z]*)%z([^%z]+)%z([^%z]+)");
230 if not authorization then return "failure", "malformed-request"; end
231 authentication = saslprep(authentication);
232 password = saslprep(password);
233 if (not password) or (password == "") or (not authentication) or (authentication == "") then
234 return "failure", "malformed-request", "Invalid username or password.";
235 end
236 local function test(authentication)
237 local prepped = nodeprep(authentication);
238 local normalized = jid_unescape(prepped);
239 return normalized and provider.test_password(normalized, password) and prepped;
240 end
241 local username = test(authentication) or test(jid_escape(authentication));
242 if username then
243 self.username = username;
244 return "success";
245 end
246 return "failure", "not-authorized", "Unable to authorize you with the authentication credentials you've sent.";
247 end
248 return sasl;
249 end
250
251 module:add_item("auth-provider", provider);
252