/ [pam-modules] / trunk / pam_sql / pam_mysql.c
To checkout: svn checkout http://svn.gnu.org.ua/sources/pam-modules/trunk/pam_sql/pam_mysql.c
Puszcza

Contents of /trunk/pam_sql/pam_mysql.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 61 - (show annotations)
Mon Aug 27 22:38:35 2007 UTC (14 years, 5 months ago) by gray
File MIME type: text/plain
File size: 9334 byte(s)
Lots of fixes in pam_mysql
1 /* This file is part of pam-modules.
2 Copyright (C) 2005, 2006, 2007 Sergey Poznyakoff
3
4 This program is free software; you can redistribute it and/or modify it
5 under the terms of the GNU General Public License as published by the
6 Free Software Foundation; either version 3 of the License, or (at your
7 option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License along
15 with this program. If not, see <http://www.gnu.org/licenses/>. */
16
17 #if defined(HAVE_CONFIG_H)
18 # include <config.h>
19 #endif
20 #ifdef HAVE__PAM_ACONF_H
21 #include <security/_pam_aconf.h>
22 #endif
23
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <stdarg.h>
27 #include <string.h>
28 #include <unistd.h>
29 #include <mysql/mysql.h>
30
31 #include "pam_sql.c"
32 #include "sha1.h"
33 #include "md5.h"
34
35 static char *
36 sql_expand_query (MYSQL *mysql,
37 const char *query, const char *user, const char *pass)
38 {
39 char *p, *q, *res;
40 int len;
41 char *esc_user = NULL;
42 char *esc_pass = NULL;
43
44 if (!query)
45 return NULL;
46
47 /* Compute resulting query length */
48 for (len = 0, p = (char *) query; *p; ) {
49 if (*p == '%') {
50 if (p[1] == 'u') {
51 int input_len = strlen(user);
52 int size = 2 * input_len +1;
53 esc_user = malloc(size);
54 if (!esc_user)
55 return NULL;
56 mysql_real_escape_string(mysql,
57 esc_user,
58 user, input_len);
59
60 len += strlen (esc_user);
61 p += 2;
62 } if (p[1] == 'p') {
63 int input_len = strlen(pass);
64 int size = 2 * input_len + 1;
65 esc_pass = malloc(size);
66 if (!esc_pass)
67 return NULL;
68 mysql_real_escape_string(mysql,
69 esc_pass,
70 pass, input_len);
71
72 len += strlen (esc_pass);
73 p += 2;
74 } else if (p[1] == '%') {
75 len++;
76 p += 2;
77 } else {
78 len++;
79 p++;
80 }
81 } else {
82 len++;
83 p++;
84 }
85 }
86
87 res = malloc (len + 1);
88 if (!res) {
89 free (esc_user);
90 free (esc_pass);
91 return res;
92 }
93
94 for (p = (char *) query, q = res; *p; ) {
95 if (*p == '%') {
96 switch (*++p) {
97 case 'u':
98 strcpy (q, esc_user);
99 q += strlen (q);
100 p++;
101 break;
102
103 case 'p':
104 strcpy (q, esc_pass);
105 q += strlen (q);
106 p++;
107 break;
108
109 case '%':
110 *q++ = *p++;
111 break;
112
113 default:
114 *q++ = *p++;
115 }
116 } else
117 *q++ = *p++;
118 }
119 *q = 0;
120
121 free (esc_user);
122 free (esc_pass);
123 return res;
124 }
125
126
127 /* MySQL scrambled password support */
128
129
130 /* Convert a single hex digit to corresponding number */
131 static unsigned
132 digit_to_number (char c)
133 {
134 return (unsigned) (c >= '0' && c <= '9' ? c-'0' :
135 c >= 'A' && c <= 'Z' ? c-'A'+10 :
136 c-'a'+10);
137 }
138
139 /* Extract salt value from MySQL scrambled password.
140
141 WARNING: The code assumes that
142 1. strlen (password) % 8 == 0
143 2. number_of_entries (RES) = strlen (password) / 8
144
145 For MySQL >= 3.21, strlen(password) == 16 */
146 static void
147 get_salt_from_scrambled (unsigned long *res, const char *password)
148 {
149 res[0] = res[1] = 0;
150 while (*password)
151 {
152 unsigned long val = 0;
153 unsigned i;
154
155 for (i = 0; i < 8 ; i++)
156 val = (val << 4) + digit_to_number (*password++);
157 *res++ = val;
158 }
159 }
160
161 /* Scramble a plaintext password */
162 static void
163 scramble_password (unsigned long *result, const char *password)
164 {
165 unsigned long nr = 1345345333L, add = 7, nr2 = 0x12345671L;
166 unsigned long tmp;
167
168 for (; *password ; password++)
169 {
170 if (*password == ' ' || *password == '\t')
171 continue;
172 tmp = (unsigned long) (unsigned char) *password;
173 nr ^= (((nr & 63) + add) * tmp)+ (nr << 8);
174 nr2 += (nr2 << 8) ^ nr;
175 add += tmp;
176 }
177
178 result[0] = nr & (((unsigned long) 1L << 31) -1L);
179 result[1] = nr2 & (((unsigned long) 1L << 31) -1L);
180 }
181
182 static void
183 mu_octet_to_hex (char *to, const unsigned char *str, unsigned len)
184 {
185 const unsigned char *str_end= str + len;
186 static char d[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
187
188 for ( ; str != str_end; ++str)
189 {
190 *to++ = d[(*str & 0xF0) >> 4];
191 *to++ = d[*str & 0x0F];
192 }
193 *to= '\0';
194 }
195
196 #define SHA1_HASH_SIZE 20
197 static int
198 mu_check_mysql_4x_password (const char *scrambled, const char *message)
199 {
200 struct sha1_ctx sha1_context;
201 unsigned char hash_stage2[SHA1_HASH_SIZE];
202 char to[2*SHA1_HASH_SIZE + 2];
203
204 /* stage 1: hash password */
205 sha1_init_ctx (&sha1_context);
206 sha1_process_bytes (message, strlen (message), &sha1_context);
207 sha1_finish_ctx (&sha1_context, to);
208
209 /* stage 2: hash stage1 output */
210 sha1_init_ctx (&sha1_context);
211 sha1_process_bytes (to, SHA1_HASH_SIZE, &sha1_context);
212 sha1_finish_ctx (&sha1_context, hash_stage2);
213
214 /* convert hash_stage2 to hex string */
215 to[0] = '*';
216 mu_octet_to_hex (to + 1, hash_stage2, SHA1_HASH_SIZE);
217
218 /* Compare both strings */
219 return memcmp (to, scrambled, strlen (scrambled));
220 }
221
222 static int
223 mu_check_mysql_3x_password (const char *scrambled, const char *message)
224 {
225 unsigned long hash_pass[2], hash_message[2];
226 char buf[17];
227
228 memcpy (buf, scrambled, 16);
229 buf[16] = 0;
230 scrambled = buf;
231
232 get_salt_from_scrambled (hash_pass, scrambled);
233 scramble_password (hash_message, message);
234 return !(hash_message[0] == hash_pass[0]
235 && hash_message[1] == hash_pass[1]);
236 }
237
238 /* Check whether a plaintext password MESSAGE matches MySQL scrambled password
239 PASSWORD */
240 int
241 mu_check_mysql_scrambled_password (const char *scrambled, const char *message)
242 {
243 const char *p;
244
245 /* Try to normalize it by cutting off trailing whitespace */
246 for (p = scrambled + strlen (scrambled) - 1;
247 p > scrambled && isspace (*p); p--)
248 ;
249 switch (p - scrambled) {
250 case 15:
251 return mu_check_mysql_3x_password (scrambled, message);
252 case 40:
253 return mu_check_mysql_4x_password (scrambled, message);
254 }
255 return 1;
256 }
257
258 static int
259 check_mysql_pass(const char *sqlpass, const char *userpass)
260 {
261 if (mu_check_mysql_scrambled_password (sqlpass, userpass) == 0)
262 return PAM_SUCCESS;
263 else
264 return PAM_AUTH_ERR;
265 }
266
267
268 static void
269 make_digest (char *md5str, unsigned char *digest)
270 {
271 int i;
272
273 for (i = 0; i < 16; i++) {
274 sprintf(md5str, "%02x", digest[i]);
275 md5str += 2;
276 }
277
278 *md5str = 0;
279 }
280
281 static int
282 check_md5_pass(const char *sqlpass, const char *userpass)
283 {
284 char md5str[33];
285 struct md5_ctx ctx;
286 unsigned char digest[16];
287
288 md5str[0] = 0;
289 md5_init_ctx (&ctx);
290 md5_process_bytes (userpass, strlen (userpass), &ctx);
291 md5_finish_ctx (&ctx, digest);
292 make_digest (md5str, digest);
293 if (strcmp (sqlpass, md5str) == 0)
294 return PAM_SUCCESS;
295 else
296 return PAM_AUTH_ERR;
297 }
298
299
300
301 static int
302 check_query_result(MYSQL *mysql, const char *pass)
303 {
304 MYSQL_RES *result;
305 int rc = PAM_AUTH_ERR;
306
307 result = mysql_store_result(mysql);
308 if (!result) {
309 _pam_log(LOG_ERR, "MySQL: query returned 0 tuples");
310 rc = PAM_AUTH_ERR;
311 } else {
312 MYSQL_ROW row;
313 long n = mysql_num_rows(result);
314 if (n != 1) {
315 _pam_log(LOG_WARNING,
316 "MySQL: query returned %d tuples", n);
317 if (n == 0) {
318 mysql_free_result(result);
319 return PAM_AUTH_ERR;
320 }
321 }
322 n = mysql_num_fields(result);
323 if (n != 1) {
324 _pam_log(LOG_WARNING,
325 "MySQL: query returned %d fields", n);
326 }
327
328 row = mysql_fetch_row(result);
329 chop(row[0]);
330 DEBUG(100,("Obtained password value: %s", row[0]));
331 if (strcmp(row[0], crypt(pass, row[0])) == 0)
332 rc = PAM_SUCCESS;
333 if (rc != PAM_SUCCESS
334 && check_boolean_config ("allow-mysql-pass", 1))
335 rc = check_mysql_pass (row[0], pass);
336 if (rc != PAM_SUCCESS
337 && check_boolean_config ("allow-md5-pass", 1))
338 rc = check_md5_pass (row[0], pass);
339 if (rc != PAM_SUCCESS
340 && check_boolean_config ("allow-plaintext-pass", 0)) {
341 if (strcmp (row[0], pass) == 0)
342 rc = PAM_SUCCESS;
343 }
344 }
345 mysql_free_result(result);
346
347 return rc;
348 }
349
350 static int
351 verify_user_pass(const char *username, const char *password)
352 {
353 MYSQL mysql;
354 char *socket_path = NULL;
355 char *hostname;
356 char *login;
357 char *pass;
358 char *db;
359 char *port;
360 int portno;
361 char *query, *exquery;
362 char *p;
363 int rc;
364
365 hostname = find_config("host");
366 CHKVAR(hostname);
367 if (hostname[0] == '/') {
368 socket_path = hostname;
369 hostname = "localhost";
370 }
371
372 port = find_config("port");
373 if (!port)
374 portno = 3306;
375 else {
376 portno = strtoul (port, &p, 0);
377 if (*p) {
378 _pam_log(LOG_ERR, "Invalid port number: %s", port);
379 return PAM_SERVICE_ERR;
380 }
381 }
382
383 login = find_config("login");
384 CHKVAR(login);
385
386 pass = find_config("pass");
387 CHKVAR(pass);
388
389 db = find_config("db");
390 CHKVAR(db);
391
392 query = find_config("query");
393 CHKVAR(query);
394
395 mysql_init(&mysql);
396
397 if (!mysql_real_connect(&mysql, hostname,
398 login, pass, db,
399 portno, socket_path, 0)) {
400 _pam_log(LOG_ERR, "cannot connect to MySQL");
401 return PAM_SERVICE_ERR;
402 }
403
404 exquery = sql_expand_query (&mysql, query, username, password);
405 if (!exquery) {
406 mysql_close(&mysql);
407 _pam_log(LOG_ERR, "cannot expand query");
408 return PAM_SERVICE_ERR;
409 }
410
411 if (mysql_query(&mysql, exquery)) {
412 _pam_log(LOG_ERR, "MySQL: %s", mysql_error(&mysql));
413 mysql_close(&mysql);
414 free(exquery);
415 return PAM_SERVICE_ERR;
416 }
417 free(exquery);
418
419 rc = check_query_result(&mysql, password);
420
421 mysql_close(&mysql);
422
423 return rc;
424 }

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

Send suggestions and bug reports to Sergey Poznyakoff
ViewVC Help
Powered by ViewVC 1.1.20