5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19 * Copyright 2001,2002 Google, Inc.
20 * Copyright 2005 Frank Cusack
32 #include <sys/types.h>
36 #include <openssl/des.h> /* des_cblock */
38 static const char rcsid[] = "$Id$";
42 * Sync data fields changed slightly between v1 and v2, and were renamed.
43 * These routines, however, retain the v1 names. The name:field mapping is:
44 * *_last_auth: last_auth_t last authentication time
45 * *_failcount: last_auth_s number of consecutive auth failures
46 * *_last_auth_pos: last_auth_p window pos. of last auth (not in v1)
50 /* Get stored sync challenge for a given user.
51 * Returns 0 on success, non-zero otherwise.
54 x99_get_sync_challenge(const char *syncdir, const char *username,
55 char challenge[MAX_CHALLENGE_LEN + 1])
60 if ((lock = x99_acquire_sd_lock(syncdir, username)) == NULL)
62 rc = x99_get_sd(syncdir, username, challenge, NULL, NULL, NULL);
63 x99_release_sd_lock(lock);
69 * Set sync data for a given user.
70 * Returns 0 on success, non-zero otherwise.
72 * - Resets failure count to 0 on successful return.
73 * - Sets last auth time to "now" on successful return.
74 * - Sets last auth window position to 0.
75 * Because of the failure count reset, this should only be called for/after
76 * successful authentications.
79 * challenge: The challenge to be stored.
80 * keyblock: The key to be stored. This is for sync modes in which the
81 * key changes for successive challenges. (NOT IMPLEMENTED)
84 x99_set_sync_data(const char *syncdir, const char *username,
85 const char *challenge,
87 __attribute__ ((unused))
89 const des_cblock keyblock)
94 if ((lock = x99_acquire_sd_lock(syncdir, username)) == NULL)
97 rc = x99_set_sd(syncdir, username, challenge, 0, time(NULL), 0);
98 x99_release_sd_lock(lock);
104 * Return the last time the user authenticated.
105 * Returns 0 on success, non-zero otherwise.
108 x99_get_last_auth(const char *syncdir, const char *username, time_t *last_auth)
113 if ((lock = x99_acquire_sd_lock(syncdir, username)) == NULL)
115 rc = x99_get_sd(syncdir, username, NULL, NULL, last_auth, NULL);
116 x99_release_sd_lock(lock);
121 * Set the last auth time for a user to "now".
122 * Returns 0 on success, non-zero otherwise.
123 * Note that x99_set_sync_data() also resets the auth time.
124 * This function is no longer called, (the failcount() routines do this work),
125 * but I'm saving it here for reference.
128 x99_upd_last_auth(const char *syncdir, const char *username)
132 char challenge[MAX_CHALLENGE_LEN + 1];
135 if ((lock = x99_acquire_sd_lock(syncdir, username)) == NULL)
138 rc = x99_get_sd(syncdir, username, challenge, &failcount, NULL, &pos);
140 rc = x99_set_sd(syncdir, username, challenge, failcount, time(NULL),
143 x99_release_sd_lock(lock);
149 * Atomically increment a user's failed login count.
150 * Also updates last_auth.
153 x99_incr_failcount(const char *syncdir, const char *username)
157 char challenge[MAX_CHALLENGE_LEN + 1];
160 if ((lock = x99_acquire_sd_lock(syncdir, username)) == NULL)
163 /* Get current value. */
164 rc = x99_get_sd(syncdir, username, challenge, &failcount, NULL, &pos);
167 if (++failcount == INT_MAX)
169 rc = x99_set_sd(syncdir, username, challenge, failcount, time(NULL),
173 x99_release_sd_lock(lock);
178 * Reset failure count to 0. Also updates last_auth and resets pos.
179 * Returns 0 on success, non-zero otherwise.
180 * This is almost just like x99_incr_failcount().
181 * x99_set_sync_data() resets the failcount also, but that's because
182 * we keep the failcount and other sync data together; we don't want
183 * to necessarily make that visible to our callers (x99_rlm.c).
186 x99_reset_failcount(const char *syncdir, const char *username)
190 char challenge[MAX_CHALLENGE_LEN + 1];
192 if ((lock = x99_acquire_sd_lock(syncdir, username)) == NULL)
195 rc = x99_get_sd(syncdir, username, challenge, NULL, NULL, NULL);
197 rc = x99_set_sd(syncdir, username, challenge, 0, time(NULL), 0);
199 x99_release_sd_lock(lock);
205 * checks the failure counter.
206 * returns 0 if the user is allowed to authenticate, < 0 otherwise:
207 * FAIL_ERR if the user is failed due to internal error,
208 * FAIL_HARD if the user is failed "hard",
209 * FAIL_SOFT if the user is failed "soft".
210 * caller does not need to log failures, we do it (in order to be specific).
213 x99_check_failcount(const char *username, const x99_token_t *inst)
218 if (x99_get_last_auth(inst->syncdir, username, &last_auth) != 0) {
220 "auth: unable to get last auth time for [%s]", username);
223 if (x99_get_failcount(inst->syncdir, username, &failcount) != 0) {
225 "auth: unable to get failure count for [%s]", username);
229 /* Check against hardfail setting. */
230 if (inst->hardfail && failcount >= inst->hardfail) {
231 x99_log(X99_LOG_AUTH,
232 "auth: %d/%d failed/max authentications for [%s]",
233 failcount, inst->hardfail, username);
234 if (x99_incr_failcount(inst->syncdir, username) != 0) {
236 "auth: unable to increment failure count for "
237 "locked out user [%s]", username);
242 /* Check against softfail setting. */
243 if (inst->softfail && failcount >= inst->softfail) {
248 * Determine the next time this user can authenticate.
250 * Once we hit softfail, we introduce a 1m delay before the user
251 * can authenticate. For each successive failed authentication,
252 * we double the delay time, up to a max of 32 minutes. While in
253 * the "delay mode" of operation, all authentication ATTEMPTS are
254 * considered failures (we don't test if the password is correct).
255 * Also, each attempt during the delay period restarts the clock.
257 * The advantage of a delay instead of a simple lockout is that an
258 * attacker can't lock out a user as easily; the user need only wait
259 * a bit before he can authenticate.
261 fcount = failcount - inst->softfail;
262 when = last_auth + (fcount > 5 ? 32 * 60 : (1 << fcount) * 60);
263 if (time(NULL) < when) {
264 x99_log(X99_LOG_AUTH,
265 "auth: user [%s] auth too soon while delayed, "
266 "%d/%d failed/softfail authentications",
267 username, failcount, inst->softfail);
268 if (x99_incr_failcount(inst->syncdir, username) != 0) {
270 "auth: unable to increment failure count for "
271 "delayed user [%s]", username);
281 * Get the last auth window position for ewindow2.
282 * Returns 0 on failure (caller cannot distinguish between failure and
286 x99_get_last_auth_pos(const char *syncdir, const char *username)
290 char challenge[MAX_CHALLENGE_LEN + 1];
293 if ((lock = x99_acquire_sd_lock(syncdir, username)) == NULL)
296 rc = x99_get_sd(syncdir, username, challenge, NULL, NULL, &pos);
298 x99_release_sd_lock(lock);
303 * Record the last auth window position (for ewindow2).
306 x99_set_last_auth_pos(const char *syncdir, const char *username, unsigned pos)
310 char challenge[MAX_CHALLENGE_LEN + 1];
314 if ((lock = x99_acquire_sd_lock(syncdir, username)) == NULL)
317 rc = x99_get_sd(syncdir, username, challenge, &failcount, &last_auth, NULL);
319 rc = x99_set_sd(syncdir, username, challenge, failcount, last_auth,
322 x99_release_sd_lock(lock);
328 * Return the failed login count for a user.
329 * Returns 0 on success, non-zero otherwise.
332 x99_get_failcount(const char *syncdir, const char *username, int *failcount)
337 if ((lock = x99_acquire_sd_lock(syncdir, username)) == NULL)
339 rc = x99_get_sd(syncdir, username, NULL, failcount, NULL, NULL);
340 x99_release_sd_lock(lock);
346 * Sync data is kept in a flat file[s], only because it's easy to implement.
347 * It might be worth looking at Berkeley DB, but the flat file implementation
348 * gives maximal concurrency with minimal complexity. Performance will be
349 * better on filesystems like ext2fs, ffs w/ soft updates, etc, due to
350 * the large number of ephemeral dot-files created/destroyed for locking.
352 * One file per user is created, and we typically expect that each thread
353 * is handling a different user (even if a user is authenticating to
354 * multiple NASs/ports, he can't really authenticate simultaneously to
355 * each -- unless it's an attack), so this should give us maximal
358 * The file format is 'version:user:challenge:key:failures:last_auth:'.
359 * Version is there to provide easy forward compatibility. The trailing
360 * colon is there for the same reason. Future versions must add data to
361 * the end. The current version is 1.
363 * For performance enhancements, it might be more worthwhile to look at
364 * caching the inst->pwdfile data. Users who are disabled should also
365 * be cached somehow, to reduce the impact of possible attacks.
370 * x99_acquire_sd_lock() returns NULL on failure, or a char *
371 * which must be passed to x99_release_sd_lock() later.
374 x99_acquire_sd_lock(const char *syncdir, const char *username)
380 /* Verify permissions first. */
381 if (stat(syncdir, &st) != 0) {
382 x99_log(X99_LOG_ERR, "syncdir %s error: %s",
383 syncdir, strerror(errno));
386 if (st.st_mode != (S_IFDIR|S_IRUSR|S_IWUSR|S_IXUSR)) {
388 "x99_acquire_sd_lock: syncdir %s has loose permissions",
393 /* We use dotfile locking. */
394 lockfile = malloc(strlen(syncdir) + strlen(username) + 3);
396 x99_log(X99_LOG_CRIT, "x99_acquire_sd_lock: out of memory");
399 (void) sprintf(lockfile, "%s/.%s", syncdir, username);
402 * Try to obtain exclusive access. 10 should be *plenty* of
403 * iterations, we don't expect concurrent accesses to the same file,
404 * and any accesses should be very quick. This is broken over NFS,
405 * but you shouldn't have this data on NFS anyway.
407 for (i = 0; i < 10; ++i) {
408 if ((fd = open(lockfile, O_CREAT|O_EXCL, S_IRUSR|S_IWUSR)) != -1) {
411 /* break stale locks (older than 60s) */
412 if (stat(lockfile, &st) == 0)
413 if (st.st_ctime < time(NULL) - 60)
414 (void) unlink(lockfile);
416 usleep(500000); /* 0.5 second */
420 "x99_acquire_sd_lock: unable to acquire lock for [%s]",
431 x99_release_sd_lock(char *lockfile)
433 (void) unlink(lockfile);
439 * x99_get_sd() returns 0 on success, non-zero otherwise.
440 * On successful returns, challenge, failures, last_auth, pos are filled in,
442 * On unsuccessful returns, challenge, failures, last_auth, pos may be garbage.
443 * challenge should be sized as indicated (if non-NULL).
444 * The caller must have obtained an exclusive lock on the sync file.
447 x99_get_sd(const char *syncdir, const char *username,
448 char challenge[MAX_CHALLENGE_LEN + 1], int *failures,
449 time_t *last_auth, unsigned *pos)
451 char syncfile[PATH_MAX + 1];
454 char syncdata[BUFSIZ];
456 unsigned ver = UINT_MAX;
458 (void) snprintf(syncfile, PATH_MAX, "%s/%s", syncdir, username);
459 syncfile[PATH_MAX] = '\0';
461 /* Open sync file. */
462 if ((fp = fopen(syncfile, "r")) == NULL) {
463 if (errno != ENOENT) {
464 x99_log(X99_LOG_ERR, "x99_get_sd: unable to open sync file %s: %s",
465 syncfile, strerror(errno));
469 * Sync file did not exist. If we can create it, all is well.
470 * Set the challenge to something "impossible".
474 return x99_set_sd(syncdir, username, "NEWSTATE", 0, 0, 0);
477 /* Read sync data. */
478 if ((fgets(syncdata, sizeof(syncdata), fp) == NULL) || !strlen(syncdata)) {
479 x99_log(X99_LOG_ERR, "x99_get_sd: unable to read sync data from %s: %s",
480 syncfile, strerror(errno));
487 /* Now, parse the sync data. */
488 /* Get the version. */
489 if ((q = strchr(p, ':')) == NULL) {
491 "x99_get_sd: invalid sync data for user %s", username);
495 if ((sscanf(p, "%u", &ver) != 1) || (ver > 2)) {
497 "x99_get_sd: invalid sync data (version) for user %s",
503 /* Sanity check the username. */
504 if ((q = strchr(p, ':')) == NULL) {
506 "x99_get_sd: invalid sync data (username) for user %s",
511 if (strcmp(p, username)) {
513 "x99_get_sd: invalid sync data (user mismatch) for user %s",
520 if ((q = strchr(p, ':')) == NULL) {
522 "x99_get_sd: invalid sync data (challenge) for user %s",
527 if (strlen(p) > MAX_CHALLENGE_LEN) {
529 "x99_get_sd: invalid sync data (challenge length) for user %s",
534 strcpy(challenge, p);
538 if ((p = strchr(p, ':')) == NULL) {
540 "x99_get_sd: invalid sync data (key) for user %s", username);
546 if ((q = strchr(p, ':')) == NULL) {
548 "x99_get_sd: invalid sync data (failures) for user %s",
553 if (failures && (sscanf(p, "%d", failures) != 1)) {
555 "x99_get_sd: invalid sync data (failures) for user %s",
562 if ((q = strchr(p, ':')) == NULL) {
564 "x99_get_sd: invalid sync data (last_auth) for user %s",
569 if (last_auth && (sscanf(p, "%ld", last_auth) != 1)) {
571 "x99_get_sd: invalid sync data (last_auth) for user %s",
577 /* Get last auth position. */
581 } else if (sscanf(p, "%u", pos) != 1) {
583 "x99_get_sd: invalid sync data (win. pos) for user %s",
594 * The caller must have obtained an exclusive lock on the sync file.
597 x99_set_sd(const char *syncdir, const char *username, const char *challenge,
598 int failures, time_t last_auth, unsigned pos)
600 char syncfile[PATH_MAX + 1];
603 (void) snprintf(syncfile, PATH_MAX, "%s/%s", syncdir, username);
604 syncfile[PATH_MAX] = '\0';
606 if ((fp = fopen(syncfile, "w")) == NULL) {
607 x99_log(X99_LOG_ERR, "x99_set_sd: unable to open sync file %s: %s",
608 syncfile, strerror(errno));
612 /* Write our (version 2) sync data. */
613 (void) fprintf(fp, "2:%s:%s:%s:%d:%ld:%u:\n", username, challenge, "",
614 failures, last_auth, pos);
615 if (fclose(fp) != 0) {
616 x99_log(X99_LOG_ERR, "x99_set_sd: unable to write sync file %s: %s",
617 syncfile, strerror(errno));