/* * 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 */ RCSID("$Id$") #include #include #include /* 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 }, };