Make cardops layer a true object layer! Re-implement cryptocard
[freeradius.git] / src / modules / rlm_x99_token / x99_sync.c
1 /*
2  * x99_sync.c
3  * $Id$
4  *
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.
9  *
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.
14  *
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
18  *
19  * Copyright 2001,2002  Google, Inc.
20  * Copyright 2005 Frank Cusack
21  */
22
23 #include "x99.h"
24 #include "x99_sync.h"
25
26 #include <assert.h>
27 #include <errno.h>
28 #include <limits.h>
29 #include <stdlib.h>
30 #include <stdio.h>
31 #include <string.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <fcntl.h>
35 #include <unistd.h>
36 #include <openssl/des.h> /* des_cblock */
37
38 static const char rcsid[] = "$Id$";
39
40
41 /*
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)
47  */
48
49
50 /* Get stored sync challenge for a given user.
51  * Returns 0 on success, non-zero otherwise.
52  */
53 int
54 x99_get_sync_challenge(const char *syncdir, const char *username,
55                        char challenge[MAX_CHALLENGE_LEN + 1])
56 {
57     int rc;
58     char *lock;
59
60     if ((lock = x99_acquire_sd_lock(syncdir, username)) == NULL)
61         return -1;
62     rc = x99_get_sd(syncdir, username, challenge, NULL, NULL, NULL);
63     x99_release_sd_lock(lock);
64     return rc;
65 }
66
67
68 /*
69  * Set sync data for a given user.
70  * Returns 0 on success, non-zero otherwise.
71  * Side effects:
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.
77  *
78  * username:  duh
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)
82  */
83 int
84 x99_set_sync_data(const char *syncdir, const char *username,
85                   const char *challenge,
86 #ifdef __GNUC__
87 __attribute__ ((unused))
88 #endif
89                    const des_cblock keyblock)
90 {
91     int rc;
92     char *lock;
93
94     if ((lock = x99_acquire_sd_lock(syncdir, username)) == NULL)
95         return -1;
96
97     rc = x99_set_sd(syncdir, username, challenge, 0, time(NULL), 0);
98     x99_release_sd_lock(lock);
99     return rc;
100 }
101
102
103 /*
104  * Return the last time the user authenticated.
105  * Returns 0 on success, non-zero otherwise.
106  */
107 int
108 x99_get_last_auth(const char *syncdir, const char *username, time_t *last_auth)
109 {
110     int rc;
111     char *lock;
112
113     if ((lock = x99_acquire_sd_lock(syncdir, username)) == NULL)
114         return -1;
115     rc = x99_get_sd(syncdir, username, NULL, NULL, last_auth, NULL);
116     x99_release_sd_lock(lock);
117     return rc;
118 }
119
120 /*
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.
126  */
127 int
128 x99_upd_last_auth(const char *syncdir, const char *username)
129 {
130     int failcount, rc;
131     char *lock;
132     char challenge[MAX_CHALLENGE_LEN + 1];
133     unsigned pos;
134
135     if ((lock = x99_acquire_sd_lock(syncdir, username)) == NULL)
136         return -1;
137
138     rc = x99_get_sd(syncdir, username, challenge, &failcount, NULL, &pos);
139     if (rc == 0)
140         rc = x99_set_sd(syncdir, username, challenge, failcount, time(NULL),
141                         pos);
142
143     x99_release_sd_lock(lock);
144     return rc;
145 }
146
147
148 /*
149  * Atomically increment a user's failed login count.
150  * Also updates last_auth.
151  */
152 int
153 x99_incr_failcount(const char *syncdir, const char *username)
154 {
155     int failcount, rc;
156     char *lock;
157     char challenge[MAX_CHALLENGE_LEN + 1];
158     unsigned pos;
159
160     if ((lock = x99_acquire_sd_lock(syncdir, username)) == NULL)
161         return -1;
162
163     /* Get current value. */
164     rc = x99_get_sd(syncdir, username, challenge, &failcount, NULL, &pos);
165     if (rc == 0) {
166         /* Increment. */
167         if (++failcount == INT_MAX)
168             failcount--;
169         rc = x99_set_sd(syncdir, username, challenge, failcount, time(NULL),
170                         pos);
171     }
172
173     x99_release_sd_lock(lock);
174     return rc;
175 }
176
177 /*
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).
184  */
185 int
186 x99_reset_failcount(const char *syncdir, const char *username)
187 {
188     int rc;
189     char *lock;
190     char challenge[MAX_CHALLENGE_LEN + 1];
191
192     if ((lock = x99_acquire_sd_lock(syncdir, username)) == NULL)
193         return -1;
194
195     rc = x99_get_sd(syncdir, username, challenge, NULL, NULL, NULL);
196     if (rc == 0)
197         rc = x99_set_sd(syncdir, username, challenge, 0, time(NULL), 0);
198
199     x99_release_sd_lock(lock);
200     return rc;
201 }
202
203
204 /*
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).
211  */
212 int
213 x99_check_failcount(const char *username, const x99_token_t *inst)
214 {
215     time_t last_auth;
216     int failcount;
217
218     if (x99_get_last_auth(inst->syncdir, username, &last_auth) != 0) {
219         x99_log(X99_LOG_ERR,
220                 "auth: unable to get last auth time for [%s]", username);
221         return FAIL_ERR;
222     }
223     if (x99_get_failcount(inst->syncdir, username, &failcount) != 0) {
224         x99_log(X99_LOG_ERR,
225                 "auth: unable to get failure count for [%s]", username);
226         return FAIL_ERR;
227     }
228
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) {
235             x99_log(X99_LOG_ERR,
236                     "auth: unable to increment failure count for "
237                     "locked out user [%s]", username);
238         }
239         return FAIL_HARD;
240     }
241
242     /* Check against softfail setting. */
243     if (inst->softfail && failcount >= inst->softfail) {
244         time_t when;
245         int fcount;
246
247         /*
248          * Determine the next time this user can authenticate.
249          *
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.
256          *
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.
260          */
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) {
269                 x99_log(X99_LOG_ERR,
270                         "auth: unable to increment failure count for "
271                         "delayed user [%s]", username);
272             }
273             return FAIL_SOFT;
274         }
275     }
276
277     return 0;
278 }
279
280 /*
281  * Get the last auth window position for ewindow2.
282  * Returns 0 on failure (caller cannot distinguish between failure and
283  * a 0 position).
284  */
285 unsigned
286 x99_get_last_auth_pos(const char *syncdir, const char *username)
287 {
288     int rc;
289     char *lock;
290     char challenge[MAX_CHALLENGE_LEN + 1];
291     unsigned pos;
292
293     if ((lock = x99_acquire_sd_lock(syncdir, username)) == NULL)
294         return -1;
295
296     rc = x99_get_sd(syncdir, username, challenge, NULL, NULL, &pos);
297
298     x99_release_sd_lock(lock);
299     return rc ? 0 : pos;
300 }
301
302 /*
303  * Record the last auth window position (for ewindow2).
304  */
305 int
306 x99_set_last_auth_pos(const char *syncdir, const char *username, unsigned pos)
307 {
308     int rc;
309     char *lock;
310     char challenge[MAX_CHALLENGE_LEN + 1];
311     int failcount;
312     time_t last_auth;
313
314     if ((lock = x99_acquire_sd_lock(syncdir, username)) == NULL)
315         return -1;
316
317     rc = x99_get_sd(syncdir, username, challenge, &failcount, &last_auth, NULL);
318     if (rc == 0)
319         rc = x99_set_sd(syncdir, username, challenge, failcount, last_auth,
320                         pos);
321
322     x99_release_sd_lock(lock);
323     return rc;
324 }
325
326
327 /*
328  * Return the failed login count for a user.
329  * Returns 0 on success, non-zero otherwise.
330  */
331 static int
332 x99_get_failcount(const char *syncdir, const char *username, int *failcount)
333 {
334     int rc;
335     char *lock;
336
337     if ((lock = x99_acquire_sd_lock(syncdir, username)) == NULL)
338         return -1;
339     rc = x99_get_sd(syncdir, username, NULL, failcount, NULL, NULL);
340     x99_release_sd_lock(lock);
341     return rc;
342 }
343
344
345 /*
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.
351  *
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
356  * concurrency.
357  *
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.
362  *
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.
366  */
367
368
369 /*
370  * x99_acquire_sd_lock() returns NULL on failure, or a char *
371  * which must be passed to x99_release_sd_lock() later.
372  */
373 static char *
374 x99_acquire_sd_lock(const char *syncdir, const char *username)
375 {
376     char *lockfile;
377     int i, fd = -1;
378     struct stat st;
379
380     /* Verify permissions first. */
381     if (stat(syncdir, &st) != 0) {
382         x99_log(X99_LOG_ERR, "syncdir %s error: %s",
383                 syncdir, strerror(errno));
384         return NULL;
385     }
386     if (st.st_mode != (S_IFDIR|S_IRUSR|S_IWUSR|S_IXUSR)) {
387         x99_log(X99_LOG_ERR,
388                 "x99_acquire_sd_lock: syncdir %s has loose permissions",
389                 syncdir);
390         return NULL;
391     }
392
393     /* We use dotfile locking. */
394     lockfile = malloc(strlen(syncdir) + strlen(username) + 3);
395     if (!lockfile) {
396         x99_log(X99_LOG_CRIT, "x99_acquire_sd_lock: out of memory");
397         return NULL;
398     }
399     (void) sprintf(lockfile, "%s/.%s", syncdir, username);
400
401     /*
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.
406      */
407     for (i = 0; i < 10; ++i) {
408         if ((fd = open(lockfile, O_CREAT|O_EXCL, S_IRUSR|S_IWUSR)) != -1) {
409             break;
410         }
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);
415
416         usleep(500000); /* 0.5 second */
417     }
418     if (fd == -1) {
419         x99_log(X99_LOG_ERR,
420                 "x99_acquire_sd_lock: unable to acquire lock for [%s]",
421                 username);
422         free(lockfile);
423         return NULL;
424     }
425
426     (void) close(fd);
427     return lockfile;
428 }
429
430 static void
431 x99_release_sd_lock(char *lockfile)
432 {
433     (void) unlink(lockfile);
434     free(lockfile);
435 }
436
437
438 /*
439  * x99_get_sd() returns 0 on success, non-zero otherwise.
440  * On successful returns, challenge, failures, last_auth, pos are filled in,
441  * if non-NULL.
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.
445  */
446 static int
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)
450 {
451     char syncfile[PATH_MAX + 1];
452     FILE *fp;
453
454     char syncdata[BUFSIZ];
455     char *p, *q;
456     unsigned ver = UINT_MAX;
457
458     (void) snprintf(syncfile, PATH_MAX, "%s/%s",  syncdir, username);
459     syncfile[PATH_MAX] = '\0';
460
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));
466             return -1;
467         }
468         /*
469          * Sync file did not exist.  If we can create it, all is well.
470          * Set the challenge to something "impossible".
471          */
472         if (failures)
473             *failures = 0;
474         return x99_set_sd(syncdir, username, "NEWSTATE", 0, 0, 0);
475     }
476
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));
481         (void) fclose(fp);
482         return -1;
483     }
484     (void) fclose(fp);
485     p = syncdata;
486
487     /* Now, parse the sync data. */
488     /* Get the version. */
489     if ((q = strchr(p, ':')) == NULL) {
490         x99_log(X99_LOG_ERR,
491                 "x99_get_sd: invalid sync data for user %s", username);
492         return -1;
493     }
494     *q++ = '\0';
495     if ((sscanf(p, "%u", &ver) != 1) || (ver > 2)) {
496         x99_log(X99_LOG_ERR,
497                 "x99_get_sd: invalid sync data (version) for user %s",
498                 username);
499         return -1;
500     }
501     p = q;
502
503     /* Sanity check the username. */
504     if ((q = strchr(p, ':')) == NULL) {
505         x99_log(X99_LOG_ERR,
506                 "x99_get_sd: invalid sync data (username) for user %s",
507                 username);
508         return -1;
509     }
510     *q++ = '\0';
511     if (strcmp(p, username)) {
512         x99_log(X99_LOG_ERR,
513                 "x99_get_sd: invalid sync data (user mismatch) for user %s",
514                 username);
515         return -1;
516     }
517     p = q;
518
519     /* Get challenge. */
520     if ((q = strchr(p, ':')) == NULL) {
521         x99_log(X99_LOG_ERR,
522                 "x99_get_sd: invalid sync data (challenge) for user %s",
523                 username);
524         return -1;
525     }
526     *q++ = '\0';
527     if (strlen(p) > MAX_CHALLENGE_LEN) {
528         x99_log(X99_LOG_ERR,
529                 "x99_get_sd: invalid sync data (challenge length) for user %s",
530                 username);
531         return -1;
532     }
533     if (challenge)
534         strcpy(challenge, p);
535     p = q;
536
537     /* Eat key. */
538     if ((p = strchr(p, ':')) == NULL) {
539         x99_log(X99_LOG_ERR,
540                 "x99_get_sd: invalid sync data (key) for user %s", username);
541         return -1;
542     }
543     p++;
544
545     /* Get failures. */
546     if ((q = strchr(p, ':')) == NULL) {
547         x99_log(X99_LOG_ERR,
548                 "x99_get_sd: invalid sync data (failures) for user %s",
549                 username);
550         return -1;
551     }
552     *q++ = '\0';
553     if (failures && (sscanf(p, "%d", failures) != 1)) {
554         x99_log(X99_LOG_ERR,
555                 "x99_get_sd: invalid sync data (failures) for user %s",
556                 username);
557         return -1;
558     }
559     p = q;
560
561     /* Get last_auth. */
562     if ((q = strchr(p, ':')) == NULL) {
563         x99_log(X99_LOG_ERR,
564                 "x99_get_sd: invalid sync data (last_auth) for user %s",
565                 username);
566         return -1;
567     }
568     *q++ = '\0';
569     if (last_auth && (sscanf(p, "%ld", last_auth) != 1)) {
570         x99_log(X99_LOG_ERR,
571                 "x99_get_sd: invalid sync data (last_auth) for user %s",
572                 username);
573         return -1;
574     }
575     p = q;
576
577     /* Get last auth position. */
578     if (pos) {
579         if (ver == 1) {
580             *pos = 0;
581         } else if (sscanf(p, "%u", pos) != 1) {
582             x99_log(X99_LOG_ERR,
583                     "x99_get_sd: invalid sync data (win. pos) for user %s",
584                     username);
585             return -1;
586         }
587     }
588     return 0;
589 }
590
591
592 /*
593  * See x99_get_sd().
594  * The caller must have obtained an exclusive lock on the sync file.
595  */
596 static int
597 x99_set_sd(const char *syncdir, const char *username, const char *challenge,
598            int failures, time_t last_auth, unsigned pos)
599 {
600     char syncfile[PATH_MAX + 1];
601     FILE *fp;
602
603     (void) snprintf(syncfile, PATH_MAX, "%s/%s",  syncdir, username);
604     syncfile[PATH_MAX] = '\0';
605
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));
609         return -1;
610     }
611
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));
618         return -1;
619     }
620
621     return 0;
622 }
623