1 /*
2 * pam_userdb module
3 *
4 * Written by Cristian Gafton <gafton@redhat.com> 1996/09/10
5 * See the end of the file for Copyright Information
6 */
7
8 #include "config.h"
9
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <unistd.h>
13 #include <string.h>
14 #include <syslog.h>
15 #include <stdarg.h>
16 #include <sys/types.h>
17 #include <sys/stat.h>
18 #include <fcntl.h>
19 #include <errno.h>
20 #ifdef HAVE_CRYPT_H
21 #include <crypt.h>
22 #endif
23
24 #include "pam_userdb.h"
25
26 #ifdef HAVE_NDBM_H
27 # include <ndbm.h>
28 #else
29 # ifdef HAVE_DB_H
30 # define DB_DBM_HSEARCH 1 /* use the dbm interface */
31 # define HAVE_DBM /* for BerkDB 5.0 and later */
32 # include <db.h>
33 # else
34 # error "failed to find a libdb or equivalent"
35 # endif
36 #endif
37
38 #include <security/pam_modules.h>
39 #include <security/pam_ext.h>
40 #include <security/_pam_macros.h>
41 #include "pam_inline.h"
42
43 /*
44 * Conversation function to obtain the user's password
45 */
46 static int
47 obtain_authtok(pam_handle_t *pamh)
48 {
49 char *resp;
50 const void *item;
51 int retval;
52
53 retval = pam_prompt(pamh, PAM_PROMPT_ECHO_OFF, &resp, _("Password: "));
54
55 if (retval != PAM_SUCCESS)
56 return retval;
57
58 if (resp == NULL)
59 return PAM_CONV_ERR;
60
61 /* set the auth token */
62 retval = pam_set_item(pamh, PAM_AUTHTOK, resp);
63
64 /* clean it up */
65 pam_overwrite_string(resp);
66 _pam_drop(resp);
67
68 if ( (retval != PAM_SUCCESS) ||
69 (retval = pam_get_item(pamh, PAM_AUTHTOK, &item))
70 != PAM_SUCCESS ) {
71 return retval;
72 }
73
74 return retval;
75 }
76
77 static int
78 _pam_parse (pam_handle_t *pamh, int argc, const char **argv,
79 const char **database, const char **cryptmode)
80 {
81 int ctrl;
82
83 *database = NULL;
84 *cryptmode = NULL;
85
86 /* step through arguments */
87 for (ctrl = 0; argc-- > 0; ++argv)
88 {
89 const char *str;
90
91 /* generic options */
92
93 if (!strcmp(*argv,"debug"))
94 ctrl |= PAM_DEBUG_ARG;
95 else if (!strcasecmp(*argv, "icase"))
96 ctrl |= PAM_ICASE_ARG;
97 else if (!strcasecmp(*argv, "dump"))
98 ctrl |= PAM_DUMP_ARG;
99 else if (!strcasecmp(*argv, "unknown_ok"))
100 ctrl |= PAM_UNKNOWN_OK_ARG;
101 else if (!strcasecmp(*argv, "key_only"))
102 ctrl |= PAM_KEY_ONLY_ARG;
103 else if (!strcasecmp(*argv, "use_first_pass"))
104 ctrl |= PAM_USE_FPASS_ARG;
105 else if (!strcasecmp(*argv, "try_first_pass"))
106 ctrl |= PAM_TRY_FPASS_ARG;
107 else if ((str = pam_str_skip_icase_prefix(*argv, "db=")) != NULL)
108 {
109 *database = str;
110 if (**database == '\0') {
111 *database = NULL;
112 pam_syslog(pamh, LOG_ERR,
113 "db= specification missing argument - ignored");
114 }
115 }
116 else if ((str = pam_str_skip_icase_prefix(*argv, "crypt=")) != NULL)
117 {
118 *cryptmode = str;
119 if (**cryptmode == '\0')
120 pam_syslog(pamh, LOG_ERR,
121 "crypt= specification missing argument - ignored");
122 }
123 else
124 {
125 pam_syslog(pamh, LOG_ERR, "unknown option: %s", *argv);
126 }
127 }
128
129 return ctrl;
130 }
131
132
133 /*
134 * Looks up a user name in a database and checks the password
135 *
136 * return values:
137 * 1 = User not found
138 * 0 = OK
139 * -1 = Password incorrect
140 * -2 = System error
141 */
142 static int
143 user_lookup (pam_handle_t *pamh, const char *database, const char *cryptmode,
144 const char *user, const char *pass, int ctrl)
145 {
146 DBM *dbm;
147 datum key, data;
148
149 /* Open the DB file. */
150 dbm = dbm_open(database, O_RDONLY, 0644);
151 if (dbm == NULL) {
152 pam_syslog(pamh, LOG_ERR,
153 "user_lookup: could not open database `%s': %m", database);
154 return -2;
155 }
156
157 /* dump out the database contents for debugging */
158 if (ctrl & PAM_DUMP_ARG) {
159 pam_syslog(pamh, LOG_INFO, "Database dump:");
160 for (key = dbm_firstkey(dbm); key.dptr != NULL;
161 key = dbm_nextkey(dbm)) {
162 data = dbm_fetch(dbm, key);
163 pam_syslog(pamh, LOG_INFO,
164 "key[len=%d] = `%s', data[len=%d] = `%s'",
165 key.dsize, key.dptr, data.dsize, data.dptr);
166 }
167 }
168
169 /* do some more init work */
170 memset(&key, 0, sizeof(key));
171 memset(&data, 0, sizeof(data));
172 if (ctrl & PAM_KEY_ONLY_ARG) {
173 if (asprintf(&key.dptr, "%s-%s", user, pass) < 0)
174 key.dptr = NULL;
175 else
176 key.dsize = strlen(key.dptr);
177 } else {
178 key.dptr = strdup(user);
179 key.dsize = strlen(user);
180 }
181
182 if (key.dptr) {
183 data = dbm_fetch(dbm, key);
184 pam_overwrite_n(key.dptr, key.dsize);
185 free(key.dptr);
186 }
187
188 if (ctrl & PAM_DEBUG_ARG) {
189 pam_syslog(pamh, LOG_INFO,
190 "password in database is [%p]`%.*s', len is %d",
191 data.dptr, data.dsize, (char *) data.dptr, data.dsize);
192 }
193
194 if (data.dptr != NULL) {
195 int compare = -2;
196
197 if (ctrl & PAM_KEY_ONLY_ARG)
198 {
199 dbm_close (dbm);
200 return 0; /* found it, data contents don't matter */
201 }
202
203 if (cryptmode && pam_str_skip_icase_prefix(cryptmode, "crypt") != NULL) {
204
205 /* crypt(3) password storage */
206
207 char *cryptpw = NULL;
208
209 if (data.dsize < 13) {
210 /* hash is too short */
211 pam_syslog(pamh, LOG_INFO, "password hash in database is too short");
212 } else if (ctrl & PAM_ICASE_ARG) {
213 pam_syslog(pamh, LOG_INFO,
214 "case-insensitive comparison only works with plaintext passwords");
215 } else {
216 /* libdb is not guaranteed to produce null terminated strings */
217 char *pwhash = strndup(data.dptr, data.dsize);
218
219 if (pwhash == NULL) {
220 pam_syslog(pamh, LOG_CRIT, "strndup failed: data.dptr");
221 } else {
222 #ifdef HAVE_CRYPT_R
223 struct crypt_data *cdata = NULL;
224 cdata = malloc(sizeof(*cdata));
225 if (cdata == NULL) {
226 pam_syslog(pamh, LOG_CRIT, "malloc failed: struct crypt_data");
227 } else {
228 cdata->initialized = 0;
229 cryptpw = crypt_r(pass, pwhash, cdata);
230 }
231 #else
232 cryptpw = crypt (pass, pwhash);
233 #endif
234 if (cryptpw && strlen(cryptpw) == (size_t)data.dsize) {
235 compare = memcmp(data.dptr, cryptpw, data.dsize);
236 } else {
237 if (ctrl & PAM_DEBUG_ARG) {
238 if (cryptpw) {
239 pam_syslog(pamh, LOG_INFO, "lengths of computed and stored hashes differ");
240 pam_syslog(pamh, LOG_INFO, "computed hash: %s", cryptpw);
241 } else {
242 pam_syslog(pamh, LOG_ERR, "crypt() returned NULL");
243 }
244 }
245 }
246 #ifdef HAVE_CRYPT_R
247 free(cdata);
248 #endif
249 }
250 pam_overwrite_string(pwhash);
251 free(pwhash);
252 }
253
254 pam_overwrite_string(cryptpw);
255 } else {
256
257 /* Unknown password encryption method -
258 * default to plaintext password storage
259 */
260
261 if (strlen(pass) != (size_t)data.dsize) {
262 compare = 1; /* wrong password len -> wrong password */
263 } else if (ctrl & PAM_ICASE_ARG) {
264 compare = strncasecmp(data.dptr, pass, data.dsize);
265 } else {
266 compare = strncmp(data.dptr, pass, data.dsize);
267 }
268
269 if (cryptmode && pam_str_skip_icase_prefix(cryptmode, "none") == NULL
270 && (ctrl & PAM_DEBUG_ARG)) {
271 pam_syslog(pamh, LOG_INFO, "invalid value for crypt parameter: %s",
272 cryptmode);
273 pam_syslog(pamh, LOG_INFO, "defaulting to plaintext password mode");
274 }
275
276 }
277
278 dbm_close(dbm);
279 if (compare == 0)
280 return 0; /* match */
281 else
282 return -1; /* wrong */
283 } else {
284 int saw_user = 0;
285
286 if (ctrl & PAM_DEBUG_ARG) {
287 pam_syslog(pamh, LOG_INFO, "error returned by dbm_fetch: %m");
288 }
289
290 /* probably we should check dbm_error() here */
291
292 if ((ctrl & PAM_KEY_ONLY_ARG) == 0) {
293 dbm_close(dbm);
294 return 1; /* not key_only, so no entry => no entry for the user */
295 }
296
297 /* now handle the key_only case */
298 for (key = dbm_firstkey(dbm);
299 key.dptr != NULL;
300 key = dbm_nextkey(dbm)) {
301 int compare;
302 /* first compare the user portion (case sensitive) */
303 compare = strncmp(key.dptr, user, strlen(user));
304 if (compare == 0) {
305 /* assume failure */
306 compare = -1;
307 /* if we have the divider where we expect it to be... */
308 if (key.dptr[strlen(user)] == '-') {
309 saw_user = 1;
310 if ((size_t)key.dsize == strlen(user) + 1 + strlen(pass)) {
311 if (ctrl & PAM_ICASE_ARG) {
312 /* compare the password portion (case insensitive)*/
313 compare = strncasecmp(key.dptr + strlen(user) + 1,
314 pass,
315 strlen(pass));
316 } else {
317 /* compare the password portion (case sensitive) */
318 compare = strncmp(key.dptr + strlen(user) + 1,
319 pass,
320 strlen(pass));
321 }
322 }
323 }
324 if (compare == 0) {
325 dbm_close(dbm);
326 return 0; /* match */
327 }
328 }
329 }
330 dbm_close(dbm);
331 if (saw_user)
332 return -1; /* saw the user, but password mismatch */
333 else
334 return 1; /* not found */
335 }
336
337 /* NOT REACHED */
338 return -2;
339 }
340
341 /* --- authentication management functions (only) --- */
342
343 int
344 pam_sm_authenticate(pam_handle_t *pamh, int flags UNUSED,
345 int argc, const char **argv)
346 {
347 const char *username;
348 const void *password;
349 const char *database = NULL;
350 const char *cryptmode = NULL;
351 int retval = PAM_AUTH_ERR, ctrl;
352
353 /* parse arguments */
354 ctrl = _pam_parse(pamh, argc, argv, &database, &cryptmode);
355 if (database == NULL) {
356 pam_syslog(pamh, LOG_ERR, "can not get the database name");
357 return PAM_SERVICE_ERR;
358 }
359
360 /* Get the username */
361 retval = pam_get_user(pamh, &username, NULL);
362 if (retval != PAM_SUCCESS) {
363 pam_syslog(pamh, LOG_NOTICE, "cannot determine user name: %s",
364 pam_strerror(pamh, retval));
365 return PAM_SERVICE_ERR;
366 }
367
368 if ((ctrl & PAM_USE_FPASS_ARG) == 0 && (ctrl & PAM_TRY_FPASS_ARG) == 0) {
369 /* Converse to obtain a password */
370 retval = obtain_authtok(pamh);
371 if (retval != PAM_SUCCESS) {
372 pam_syslog(pamh, LOG_ERR, "can not obtain password from user");
373 return retval;
374 }
375 }
376
377 /* Check if we got a password */
378 retval = pam_get_item(pamh, PAM_AUTHTOK, &password);
379 if (retval != PAM_SUCCESS || password == NULL) {
380 if ((ctrl & PAM_TRY_FPASS_ARG) != 0) {
381 /* Converse to obtain a password */
382 retval = obtain_authtok(pamh);
383 if (retval != PAM_SUCCESS) {
384 pam_syslog(pamh, LOG_ERR, "can not obtain password from user");
385 return retval;
386 }
387 retval = pam_get_item(pamh, PAM_AUTHTOK, &password);
388 }
389 if (retval != PAM_SUCCESS || password == NULL) {
390 pam_syslog(pamh, LOG_ERR, "can not recover user password");
391 return PAM_AUTHTOK_RECOVERY_ERR;
392 }
393 }
394
395 if (ctrl & PAM_DEBUG_ARG)
396 pam_syslog(pamh, LOG_INFO, "Verify user `%s' with a password",
397 username);
398
399 /* Now use the username to look up password in the database file */
400 retval = user_lookup(pamh, database, cryptmode, username, password, ctrl);
401 switch (retval) {
402 case -2:
403 /* some sort of system error. The log was already printed */
404 return PAM_SERVICE_ERR;
405 case -1:
406 /* incorrect password */
407 pam_syslog(pamh, LOG_NOTICE,
408 "user `%s' denied access (incorrect password)",
409 username);
410 return PAM_AUTH_ERR;
411 case 1:
412 /* the user does not exist in the database */
413 if (ctrl & PAM_DEBUG_ARG)
414 pam_syslog(pamh, LOG_NOTICE,
415 "user `%s' not found in the database", username);
416 return PAM_USER_UNKNOWN;
417 case 0:
418 /* Otherwise, the authentication looked good */
419 pam_syslog(pamh, LOG_NOTICE, "user '%s' granted access", username);
420 return PAM_SUCCESS;
421 default:
422 /* we don't know anything about this return value */
423 pam_syslog(pamh, LOG_ERR,
424 "internal module error (retval = %d, user = `%s'",
425 retval, username);
426 return PAM_SERVICE_ERR;
427 }
428
429 /* should not be reached */
430 return PAM_IGNORE;
431 }
432
433 int
434 pam_sm_setcred(pam_handle_t *pamh UNUSED, int flags UNUSED,
435 int argc UNUSED, const char **argv UNUSED)
436 {
437 return PAM_SUCCESS;
438 }
439
440 int
441 pam_sm_acct_mgmt(pam_handle_t *pamh, int flags UNUSED,
442 int argc, const char **argv)
443 {
444 const char *username;
445 const char *database = NULL;
446 const char *cryptmode = NULL;
447 int retval = PAM_AUTH_ERR, ctrl;
448
449 /* parse arguments */
450 ctrl = _pam_parse(pamh, argc, argv, &database, &cryptmode);
451
452 /* Get the username */
453 retval = pam_get_user(pamh, &username, NULL);
454 if (retval != PAM_SUCCESS) {
455 pam_syslog(pamh, LOG_NOTICE, "cannot determine user name: %s",
456 pam_strerror(pamh, retval));
457 return PAM_SERVICE_ERR;
458 }
459
460 /* Now use the username to look up password in the database file */
461 retval = user_lookup(pamh, database, cryptmode, username, "", ctrl);
462 switch (retval) {
463 case -2:
464 /* some sort of system error. The log was already printed */
465 return PAM_SERVICE_ERR;
466 case -1:
467 /* incorrect password, but we don't care */
468 /* FALL THROUGH */
469 case 0:
470 /* authentication succeeded. dumbest password ever. */
471 return PAM_SUCCESS;
472 case 1:
473 /* the user does not exist in the database */
474 return PAM_USER_UNKNOWN;
475 default:
476 /* we don't know anything about this return value */
477 pam_syslog(pamh, LOG_ERR,
478 "internal module error (retval = %d, user = `%s'",
479 retval, username);
480 return PAM_SERVICE_ERR;
481 }
482
483 return PAM_SUCCESS;
484 }
485
486 /*
487 * Copyright (c) Cristian Gafton <gafton@redhat.com>, 1999
488 * All rights reserved
489 *
490 * Redistribution and use in source and binary forms, with or without
491 * modification, are permitted provided that the following conditions
492 * are met:
493 * 1. Redistributions of source code must retain the above copyright
494 * notice, and the entire permission notice in its entirety,
495 * including the disclaimer of warranties.
496 * 2. Redistributions in binary form must reproduce the above copyright
497 * notice, this list of conditions and the following disclaimer in the
498 * documentation and/or other materials provided with the distribution.
499 * 3. The name of the author may not be used to endorse or promote
500 * products derived from this software without specific prior
501 * written permission.
502 *
503 * ALTERNATIVELY, this product may be distributed under the terms of
504 * the GNU Public License, in which case the provisions of the GPL are
505 * required INSTEAD OF the above restrictions. (This clause is
506 * necessary due to a potential bad interaction between the GPL and
507 * the restrictions contained in a BSD-style copyright.)
508 *
509 * THIS SOFTWARE IS PROVIDED `AS IS'' AND ANY EXPRESS OR IMPLIED
510 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
511 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
512 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
513 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
514 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
515 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
516 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
517 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
518 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
519 * OF THE POSSIBILITY OF SUCH DAMAGE.
520 */