6e263fb57c98b733690b347474d4e8c33d518a8f
[freeradius.git] / src / modules / rlm_logintime / rlm_logintime.c
1 /*
2  *   This program is is free software; you can redistribute it and/or modify
3  *   it under the terms of the GNU General Public License as published by
4  *   the Free Software Foundation; either version 2 of the License, or (at
5  *   your option) any later version.
6  *
7  *   This program is distributed in the hope that it will be useful,
8  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  *   GNU General Public License for more details.
11  *
12  *   You should have received a copy of the GNU General Public License
13  *   along with this program; if not, write to the Free Software
14  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15  */
16
17 /**
18  * $Id$
19  * @file rlm_logintime.c
20  * @brief Allow login only during a given timeslot.
21  *
22  * @copyright 2001,2006  The FreeRADIUS server project
23  * @copyright 2004  Kostas Kalevras <kkalev@noc.ntua.gr>
24  */
25 RCSID("$Id$")
26
27 #include <freeradius-devel/radiusd.h>
28 #include <freeradius-devel/modules.h>
29
30 #include <ctype.h>
31
32 /* timestr.c */
33 int             timestr_match(char const *, time_t);
34
35 /*
36  *      Define a structure for our module configuration.
37  *
38  *      These variables do not need to be in a structure, but it's
39  *      a lot cleaner to do so, and a pointer to the structure can
40  *      be used as the instance handle.
41  */
42 typedef struct rlm_logintime_t {
43         uint32_t        min_time;
44 } rlm_logintime_t;
45
46 /*
47  *      A mapping of configuration file names to internal variables.
48  *
49  *      Note that the string is dynamically allocated, so it MUST
50  *      be freed.  When the configuration file parse re-reads the string,
51  *      it free's the old one, and strdup's the new one, placing the pointer
52  *      to the strdup'd string into 'config.string'.  This gets around
53  *      buffer over-flows.
54  */
55 static const CONF_PARSER module_config[] = {
56   { "minimum-timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER | PW_TYPE_DEPRECATED, rlm_logintime_t, min_time), NULL },
57   { "minimum_timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_logintime_t, min_time), "60" },
58
59   { NULL, -1, 0, NULL, NULL }
60 };
61
62
63 /*
64  *      Compare the current time to a range.
65  */
66 static int timecmp(UNUSED void *instance, REQUEST *req, UNUSED VALUE_PAIR *request, VALUE_PAIR *check,
67                    UNUSED VALUE_PAIR *check_pairs, UNUSED VALUE_PAIR **reply_pairs)
68 {
69         /*
70          *      If there's a request, use that timestamp.
71          */
72         if (timestr_match(check->vp_strvalue,
73         req ? req->timestamp : time(NULL)) >= 0)
74                 return 0;
75
76         return -1;
77 }
78
79
80 /*
81  *      Time-Of-Day support
82  */
83 static int time_of_day(UNUSED void *instance, REQUEST *req, UNUSED VALUE_PAIR *request, VALUE_PAIR *check,
84                        UNUSED VALUE_PAIR *check_pairs, UNUSED VALUE_PAIR **reply_pairs)
85 {
86         int scan;
87         int hhmmss, when;
88         char const *p;
89         struct tm *tm, s_tm;
90
91         /*
92          *      Must be called with a request pointer.
93          */
94         if (!req) return -1;
95
96         if (strspn(check->vp_strvalue, "0123456789: ") != strlen(check->vp_strvalue)) {
97                 DEBUG("rlm_logintime: Bad Time-Of-Day value \"%s\"",
98                       check->vp_strvalue);
99                 return -1;
100         }
101
102         tm = localtime_r(&req->timestamp, &s_tm);
103         hhmmss = (tm->tm_hour * 3600) + (tm->tm_min * 60) + tm->tm_sec;
104
105         /*
106          *      Time of day is a 24-hour clock
107          */
108         p = check->vp_strvalue;
109         scan = atoi(p);
110         p = strchr(p, ':');
111         if ((scan > 23) || !p) {
112                 DEBUG("rlm_logintime: Bad Time-Of-Day value \"%s\"",
113                       check->vp_strvalue);
114                 return -1;
115         }
116         when = scan * 3600;
117         p++;
118
119         scan = atoi(p);
120         if (scan > 59) {
121                 DEBUG("rlm_logintime: Bad Time-Of-Day value \"%s\"",
122                       check->vp_strvalue);
123                 return -1;
124         }
125         when += scan * 60;
126
127         p = strchr(p, ':');
128         if (p) {
129                 scan = atoi(p + 1);
130                 if (scan > 59) {
131                         DEBUG("rlm_logintime: Bad Time-Of-Day value \"%s\"",
132                               check->vp_strvalue);
133                         return -1;
134                 }
135                 when += scan;
136         }
137
138         fprintf(stderr, "returning %d - %d\n",
139                 hhmmss, when);
140
141         return hhmmss - when;
142 }
143
144 /*
145  *      Check if account has expired, and if user may login now.
146  */
147 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request)
148 {
149         rlm_logintime_t *inst = instance;
150         VALUE_PAIR *ends, *timeout;
151         int left;
152
153         ends = fr_pair_find_by_num(request->config, PW_LOGIN_TIME, 0, TAG_ANY);
154         if (!ends) {
155                 return RLM_MODULE_NOOP;
156         }
157
158         /*
159          *      Authentication is OK. Now see if this user may login at this time of the day.
160          */
161         RDEBUG("Checking Login-Time");
162
163         /*
164          *      Compare the time the request was received with the current Login-Time value
165          */
166         left = timestr_match(ends->vp_strvalue, request->timestamp);
167         if (left < 0) return RLM_MODULE_USERLOCK; /* outside of the allowed time */
168
169         /*
170          *      Do nothing, login time is not controlled (unendsed).
171          */
172         if (left == 0) {
173                 return RLM_MODULE_OK;
174         }
175
176         /*
177          *      The min_time setting is to deal with NAS that won't allow Session-Timeout values below a certain value
178          *      For example some Alcatel Lucent products won't allow a Session-Timeout < 300 (5 minutes).
179          *
180          *      We don't know were going to get another chance to lock out the user, so we need to do it now.
181          */
182         if (left < (int) inst->min_time) {
183                 REDEBUG("Login outside of allowed time-slot (session end %s, with lockout %i seconds before)",
184                         ends->vp_strvalue, inst->min_time);
185
186                 return RLM_MODULE_USERLOCK;
187         }
188
189         /* else left > inst->min_time */
190
191         /*
192          *      There's time left in the users session, inform the NAS by including a Session-Timeout
193          *      attribute in the reply, or modifying the existing one.
194          */
195         RDEBUG("Login within allowed time-slot, %d seconds left in this session", left);
196
197         timeout = fr_pair_find_by_num(request->reply->vps, PW_SESSION_TIMEOUT, 0, TAG_ANY);
198         if (timeout) {  /* just update... */
199                 if (timeout->vp_integer > (unsigned int) left) {
200                         timeout->vp_integer = left;
201                 }
202         } else {
203                 timeout = radius_pair_create(request->reply, &request->reply->vps, PW_SESSION_TIMEOUT, 0);
204                 timeout->vp_integer = left;
205         }
206
207         RDEBUG("reply:Session-Timeout set to %d", left);
208
209         return RLM_MODULE_UPDATED;
210 }
211
212
213 /*
214  *      Do any per-module initialization that is separate to each
215  *      configured instance of the module.  e.g. set up connections
216  *      to external databases, read configuration files, set up
217  *      dictionary entries, etc.
218  *
219  *      If configuration information is given in the config section
220  *      that must be referenced in later calls, store a handle to it
221  *      in *instance otherwise put a null pointer there.
222  */
223 static int mod_instantiate(CONF_SECTION *conf, void *instance)
224 {
225         rlm_logintime_t *inst = instance;
226
227         if (inst->min_time == 0) {
228                 cf_log_err_cs(conf, "Invalid value '0' for minimum_timeout");
229                 return -1;
230         }
231
232         /*
233          * Register a Current-Time comparison function
234          */
235         paircompare_register(dict_attrbyvalue(PW_CURRENT_TIME, 0), NULL, true, timecmp, inst);
236         paircompare_register(dict_attrbyvalue(PW_TIME_OF_DAY, 0), NULL, true, time_of_day, inst);
237
238         return 0;
239 }
240
241 /*
242  *      The module name should be the only globally exported symbol.
243  *      That is, everything else should be 'static'.
244  *
245  *      If the module needs to temporarily modify it's instantiation
246  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
247  *      The server will then take care of ensuring that the module
248  *      is single-threaded.
249  */
250 extern module_t rlm_logintime;
251 module_t rlm_logintime = {
252         .magic          = RLM_MODULE_INIT,
253         .name           = "logintime",
254         .inst_size      = sizeof(rlm_logintime_t),
255         .config         = module_config,
256         .instantiate    = mod_instantiate,
257         .methods = {
258                 [MOD_AUTHORIZE]         = mod_authorize,
259                 [MOD_POST_AUTH]         = mod_authorize
260         },
261 };