diff options
Diffstat (limited to 'src/modules/rlm_logintime')
-rw-r--r-- | src/modules/rlm_logintime/README.md | 14 | ||||
-rw-r--r-- | src/modules/rlm_logintime/all.mk | 2 | ||||
-rw-r--r-- | src/modules/rlm_logintime/rlm_logintime.c | 260 | ||||
-rw-r--r-- | src/modules/rlm_logintime/timestr.c | 269 |
4 files changed, 545 insertions, 0 deletions
diff --git a/src/modules/rlm_logintime/README.md b/src/modules/rlm_logintime/README.md new file mode 100644 index 0000000..a8e1cba --- /dev/null +++ b/src/modules/rlm_logintime/README.md @@ -0,0 +1,14 @@ +# rlm_logintime +## Metadata +<dl> + <dt>category</dt><dd>policy</dd> +</dl> + +## Summary + +Enforces the time span during which a user may login to the system. + +Time spans are defined with timestrings, which are similar in +format to those used by UUCP. A timestring may be a simple +timestring, or it may be a list of simple timestrings separated +by "|" or ",". diff --git a/src/modules/rlm_logintime/all.mk b/src/modules/rlm_logintime/all.mk new file mode 100644 index 0000000..ef0b18a --- /dev/null +++ b/src/modules/rlm_logintime/all.mk @@ -0,0 +1,2 @@ +TARGET := rlm_logintime.a +SOURCES := rlm_logintime.c timestr.c diff --git a/src/modules/rlm_logintime/rlm_logintime.c b/src/modules/rlm_logintime/rlm_logintime.c new file mode 100644 index 0000000..ca8249d --- /dev/null +++ b/src/modules/rlm_logintime/rlm_logintime.c @@ -0,0 +1,260 @@ +/* + * This program is is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or (at + * your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +/** + * $Id$ + * @file rlm_logintime.c + * @brief Allow login only during a given timeslot. + * + * @copyright 2001,2006 The FreeRADIUS server project + * @copyright 2004 Kostas Kalevras <kkalev@noc.ntua.gr> + */ +RCSID("$Id$") + +#include <freeradius-devel/radiusd.h> +#include <freeradius-devel/modules.h> + +#include <ctype.h> + +/* timestr.c */ +int timestr_match(char const *, time_t); + +/* + * Define a structure for our module configuration. + * + * These variables do not need to be in a structure, but it's + * a lot cleaner to do so, and a pointer to the structure can + * be used as the instance handle. + */ +typedef struct rlm_logintime_t { + uint32_t min_time; +} rlm_logintime_t; + +/* + * A mapping of configuration file names to internal variables. + * + * Note that the string is dynamically allocated, so it MUST + * be freed. When the configuration file parse re-reads the string, + * it free's the old one, and strdup's the new one, placing the pointer + * to the strdup'd string into 'config.string'. This gets around + * buffer over-flows. + */ +static const CONF_PARSER module_config[] = { + { "minimum-timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER | PW_TYPE_DEPRECATED, rlm_logintime_t, min_time), NULL }, + { "minimum_timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_logintime_t, min_time), "60" }, + CONF_PARSER_TERMINATOR +}; + + +/* + * Compare the current time to a range. + */ +static int timecmp(UNUSED void *instance, REQUEST *req, UNUSED VALUE_PAIR *request, VALUE_PAIR *check, + UNUSED VALUE_PAIR *check_pairs, UNUSED VALUE_PAIR **reply_pairs) +{ + /* + * If there's a request, use that timestamp. + */ + if (timestr_match(check->vp_strvalue, + req ? req->timestamp : time(NULL)) >= 0) + return 0; + + return -1; +} + + +/* + * Time-Of-Day support + */ +static int time_of_day(UNUSED void *instance, REQUEST *req, UNUSED VALUE_PAIR *request, VALUE_PAIR *check, + UNUSED VALUE_PAIR *check_pairs, UNUSED VALUE_PAIR **reply_pairs) +{ + int scan; + int hhmmss, when; + char const *p; + struct tm *tm, s_tm; + + /* + * Must be called with a request pointer. + */ + if (!req) return -1; + + if (strspn(check->vp_strvalue, "0123456789: ") != strlen(check->vp_strvalue)) { + DEBUG("rlm_logintime: Bad Time-Of-Day value \"%s\"", + check->vp_strvalue); + return -1; + } + + tm = localtime_r(&req->timestamp, &s_tm); + hhmmss = (tm->tm_hour * 3600) + (tm->tm_min * 60) + tm->tm_sec; + + /* + * Time of day is a 24-hour clock + */ + p = check->vp_strvalue; + scan = atoi(p); + p = strchr(p, ':'); + if ((scan > 23) || !p) { + DEBUG("rlm_logintime: Bad Time-Of-Day value \"%s\"", + check->vp_strvalue); + return -1; + } + when = scan * 3600; + p++; + + scan = atoi(p); + if (scan > 59) { + DEBUG("rlm_logintime: Bad Time-Of-Day value \"%s\"", + check->vp_strvalue); + return -1; + } + when += scan * 60; + + p = strchr(p, ':'); + if (p) { + scan = atoi(p + 1); + if (scan > 59) { + DEBUG("rlm_logintime: Bad Time-Of-Day value \"%s\"", + check->vp_strvalue); + return -1; + } + when += scan; + } + + fprintf(stderr, "returning %d - %d\n", + hhmmss, when); + + return hhmmss - when; +} + +/* + * Check if account has expired, and if user may login now. + */ +static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request) +{ + rlm_logintime_t *inst = instance; + VALUE_PAIR *ends, *timeout; + int left; + + ends = fr_pair_find_by_num(request->config, PW_LOGIN_TIME, 0, TAG_ANY); + if (!ends) { + return RLM_MODULE_NOOP; + } + + /* + * Authentication is OK. Now see if this user may login at this time of the day. + */ + RDEBUG("Checking Login-Time"); + + /* + * Compare the time the request was received with the current Login-Time value + */ + left = timestr_match(ends->vp_strvalue, request->timestamp); + if (left < 0) return RLM_MODULE_USERLOCK; /* outside of the allowed time */ + + /* + * Do nothing, login time is not controlled (unendsed). + */ + if (left == 0) { + return RLM_MODULE_OK; + } + + /* + * The min_time setting is to deal with NAS that won't allow Session-Timeout values below a certain value + * For example some Alcatel Lucent products won't allow a Session-Timeout < 300 (5 minutes). + * + * We don't know were going to get another chance to lock out the user, so we need to do it now. + */ + if (left < (int) inst->min_time) { + REDEBUG("Login outside of allowed time-slot (session end %s, with lockout %i seconds before)", + ends->vp_strvalue, inst->min_time); + + return RLM_MODULE_USERLOCK; + } + + /* else left > inst->min_time */ + + /* + * There's time left in the users session, inform the NAS by including a Session-Timeout + * attribute in the reply, or modifying the existing one. + */ + RDEBUG("Login within allowed time-slot, %d seconds left in this session", left); + + timeout = fr_pair_find_by_num(request->reply->vps, PW_SESSION_TIMEOUT, 0, TAG_ANY); + if (timeout) { /* just update... */ + if (timeout->vp_integer > (unsigned int) left) { + timeout->vp_integer = left; + } + } else { + timeout = radius_pair_create(request->reply, &request->reply->vps, PW_SESSION_TIMEOUT, 0); + timeout->vp_integer = left; + } + + RDEBUG("reply:Session-Timeout set to %d", left); + + return RLM_MODULE_UPDATED; +} + + +/* + * Do any per-module initialization that is separate to each + * configured instance of the module. e.g. set up connections + * to external databases, read configuration files, set up + * dictionary entries, etc. + * + * If configuration information is given in the config section + * that must be referenced in later calls, store a handle to it + * in *instance otherwise put a null pointer there. + */ +static int mod_instantiate(CONF_SECTION *conf, void *instance) +{ + rlm_logintime_t *inst = instance; + + if (inst->min_time == 0) { + cf_log_err_cs(conf, "Invalid value '0' for minimum_timeout"); + return -1; + } + + /* + * Register a Current-Time comparison function + */ + paircompare_register(dict_attrbyvalue(PW_CURRENT_TIME, 0), NULL, true, timecmp, inst); + paircompare_register(dict_attrbyvalue(PW_TIME_OF_DAY, 0), NULL, true, time_of_day, inst); + + return 0; +} + +/* + * The module name should be the only globally exported symbol. + * That is, everything else should be 'static'. + * + * If the module needs to temporarily modify it's instantiation + * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE. + * The server will then take care of ensuring that the module + * is single-threaded. + */ +extern module_t rlm_logintime; +module_t rlm_logintime = { + .magic = RLM_MODULE_INIT, + .name = "logintime", + .inst_size = sizeof(rlm_logintime_t), + .config = module_config, + .instantiate = mod_instantiate, + .methods = { + [MOD_AUTHORIZE] = mod_authorize, + [MOD_POST_AUTH] = mod_authorize + }, +}; diff --git a/src/modules/rlm_logintime/timestr.c b/src/modules/rlm_logintime/timestr.c new file mode 100644 index 0000000..1cd827a --- /dev/null +++ b/src/modules/rlm_logintime/timestr.c @@ -0,0 +1,269 @@ +/* + * timestr.c See if a string like 'Su2300-0700' matches (UUCP style). + * + * Version: $Id$ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 2000,2006 The FreeRADIUS server project + * Copyright 2000 Alan DeKok <aland@ox.org> + */ + +RCSID("$Id$") + +#include <freeradius-devel/radiusd.h> + +#include <ctype.h> + +int timestr_match(char const *, time_t); + +static char const *days[] = + { "su", "mo", "tu", "we", "th", "fr", "sa", "wk", "any", "al" }; + +#define DAYMIN (24*60) +#define WEEKMIN (24*60*7) +#define val(x) (( (x) < 48 || (x) > 57) ? 0 : ((x) - 48)) + +#if 0 /* Set to 1 if you're a developer and want to debug this code */ +# define timestr_debug DEBUG2 +# define do_timestr_debug 1 +#else +# define timestr_debug if (0) printf +#endif + +/* + * String code. + */ +static int strcode (char const **str) +{ + int i; + size_t l; + + timestr_debug("strcode %s called\n", *str); + + for (i = 0; i < 10; i++) { + l = strlen(days[i]); + if (l > strlen(*str)) + continue; + if (strncmp(*str, days[i], l) == 0) { + *str += l; + break; + } + } + timestr_debug("strcode result %d\n", i); + + return (i >= 10) ? -1 : i; + +} + +/* + * Fill bitmap with hours/mins. + */ +static int hour_fill(char *bitmap, char const *tm) +{ + char *p; + int start, end; + int i, bit, byte; + + timestr_debug("hour_fill called for %s\n", tm); + + /* + * Get timerange in start and end. + */ + end = -1; + if ((p = strchr(tm, '-')) != NULL) { + p++; + if (p - tm != 5 || strlen(p) < 4 || !isdigit((uint8_t) *p)) + return 0; + end = 600 * val(p[0]) + 60 * val(p[1]) + atoi(p + 2); + } + if (*tm == 0) { + start = 0; + end = DAYMIN - 1; + } else { + if (strlen(tm) < 4 || !isdigit((uint8_t) *tm)) + return 0; + start = 600 * val(tm[0]) + 60 * val(tm[1]) + atoi(tm + 2); + if (end < 0) end = start; + } + /* Treat 2400 as 0000, and do some more silent error checks. */ + if (end < 0) end = 0; + if (start < 0) start = 0; + if (end >= DAYMIN) end = DAYMIN - 1; + if (start >= DAYMIN) start = DAYMIN - 1; + + timestr_debug("hour_fill: range from %d to %d\n", start, end); + + /* + * Fill bitmap. + */ + i = start; + while (1) { + byte = (i / 8); + bit = i % 8; + timestr_debug("setting byte %d, bit %d\n", byte, bit); + bitmap[byte] |= (1 << bit); + if (i == end) break; + i++; + i %= DAYMIN; + } + return 1; +} + +/* + * Call the fill bitmap function for every day listed. + */ +static int day_fill(char *bitmap, char const *tm) +{ + char const *hr; + int n; + int start, end; + + for (hr = tm; *hr; hr++) + if (isdigit((uint8_t) *hr)) + break; + if (hr == tm) + tm = "Al"; + + timestr_debug("dayfill: hr %s tm %s\n", hr, tm); + + while ((start = strcode(&tm)) >= 0) { + /* + * Find start and end weekdays and + * build a valid range 0 - 6. + */ + if (*tm == '-') { + tm++; + if ((end = strcode(&tm)) < 0) + break; + } else + end = start; + if (start == 7) { + start = 1; + end = 5; + } + if (start > 7) { + start = 0; + end = 6; + } + n = start; + timestr_debug("day_fill: range from %d to %d\n", start, end); + while (1) { + hour_fill(bitmap + 180 * n, hr); + if (n == end) break; + n++; + n %= 7; + } + } + + return 1; +} + +/* + * Fill the week bitmap with allowed times. + */ +static int week_fill(char *bitmap, char const *tm) +{ + char *s; + char tmp[256]; + + strlcpy(tmp, tm, sizeof(tmp)); + for (s = tmp; *s; s++) + if (isupper((uint8_t) *s)) *s = tolower((uint8_t) *s); + + s = strtok(tmp, ",|"); + while (s) { + day_fill(bitmap, s); + s = strtok(NULL, ",|"); + } + + return 0; +} + +/* + * Match a timestring and return seconds left. + * -1 for no match, 0 for unlimited. + */ +int timestr_match(char const *tmstr, time_t t) +{ + struct tm *tm, s_tm; + char bitmap[WEEKMIN / 8]; + int now, tot, i; + int byte, bit; +#ifdef do_timestr_debug + int y; + char *s; + char null[8]; +#endif + + tm = localtime_r(&t, &s_tm); + now = tm->tm_wday * DAYMIN + tm->tm_hour * 60 + tm->tm_min; + tot = 0; + memset(bitmap, 0, sizeof(bitmap)); + week_fill(bitmap, tmstr); + +#ifdef do_timestr_debug + memset(null, 0, 8); + for (i = 0; i < 7; i++) { + timestr_debug("%d: ", i); + s = bitmap + 180 * i; + for (y = 0; y < 23; y++) { + s = bitmap + 180 * i + (75 * y) / 10; + timestr_debug("%c", memcmp(s, null, 8) == 0 ? '.' : '#'); + } + timestr_debug("\n"); + } +#endif + + /* + * See how many minutes we have. + */ + i = now; + while (1) { + byte = i / 8; + bit = i % 8; + timestr_debug("READ: checking byte %d bit %d\n", byte, bit); + if (!(bitmap[byte] & (1 << bit))) + break; + tot += 60; + i++; + i %= WEEKMIN; + if (i == now) + break; + } + + if (tot == 0) + return -1; + + return (i == now) ? 0 : tot; +} + +#ifdef STANDALONE + +int main(int argc, char **argv) +{ + int l; + + if (argc != 2) { + fprintf(stderr, "Usage: test timestring\n"); + exit(1); + } + l = timestr_match(argv[1], time(NULL)); + printf ("%s: %d seconds left\n", argv[1], l); + return 0; +} + +#endif + |