summaryrefslogtreecommitdiffstats
path: root/src/modules/rlm_logintime
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules/rlm_logintime')
-rw-r--r--src/modules/rlm_logintime/README.md14
-rw-r--r--src/modules/rlm_logintime/all.mk2
-rw-r--r--src/modules/rlm_logintime/rlm_logintime.c260
-rw-r--r--src/modules/rlm_logintime/timestr.c269
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
+