/ [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 63 - (show annotations)
Thu Mar 13 13:53:32 2008 UTC (13 years, 7 months ago) by gray
File MIME type: text/plain
File size: 9033 byte(s)
* configure.ac (PAM_COMMON_INCLUDES): Add -I${top_srcdir}/lib.
(AC_OUTPUT): Add lib/Makefile.
* doc/pam-modules.texi: Document `transform' option.
* Make.rules: New file.

* lib/mem.c, lib/slist.c, lib/log.c, lib/converse.c,
lib/graypam.h, lib/Makefile.am, lib/transform.c.

* pam_regex/pam_regex.c: Implement user name transformations.

* pam_fshadow/Makefile.am, pam_sql/Makefile.am:
Add ../lib/libgraypam.la to LDADD
* pam_fshadow/pam_fshadow.c, pam_sql/pam_mysql.c,
pam_sql/pam_pgsql.c, pam_sql/pam_sql.c: Use functions from ../lib.

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

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