Use macro for terminating CONF_PARSER arrays
[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         CONF_PARSER_TERMINATOR
59 };
60
61
62 /*
63  *      Compare the current time to a range.
64  */
65 static int timecmp(UNUSED void *instance, REQUEST *req, UNUSED VALUE_PAIR *request, VALUE_PAIR *check,
66                    UNUSED VALUE_PAIR *check_pairs, UNUSED VALUE_PAIR **reply_pairs)
67 {
68         /*
69          *      If there's a request, use that timestamp.
70          */
71         if (timestr_match(check->vp_strvalue,
72         req ? req->timestamp : time(NULL)) >= 0)
73                 return 0;
74
75         return -1;
76 }
77
78
79 /*
80  *      Time-Of-Day support
81  */
82 static int time_of_day(UNUSED void *instance, REQUEST *req, UNUSED VALUE_PAIR *request, VALUE_PAIR *check,
83                        UNUSED VALUE_PAIR *check_pairs, UNUSED VALUE_PAIR **reply_pairs)
84 {
85         int scan;
86         int hhmmss, when;
87         char const *p;
88         struct tm *tm, s_tm;
89
90         /*
91          *      Must be called with a request pointer.
92          */
93         if (!req) return -1;
94
95         if (strspn(check->vp_strvalue, "0123456789: ") != strlen(check->vp_strvalue)) {
96                 DEBUG("rlm_logintime: Bad Time-Of-Day value \"%s\"",
97                       check->vp_strvalue);
98                 return -1;
99         }
100
101         tm = localtime_r(&req->timestamp, &s_tm);
102         hhmmss = (tm->tm_hour * 3600) + (tm->tm_min * 60) + tm->tm_sec;
103
104         /*
105          *      Time of day is a 24-hour clock
106          */
107         p = check->vp_strvalue;
108         scan = atoi(p);
109         p = strchr(p, ':');
110         if ((scan > 23) || !p) {
111                 DEBUG("rlm_logintime: Bad Time-Of-Day value \"%s\"",
112                       check->vp_strvalue);
113                 return -1;
114         }
115         when = scan * 3600;
116         p++;
117
118         scan = atoi(p);
119         if (scan > 59) {
120                 DEBUG("rlm_logintime: Bad Time-Of-Day value \"%s\"",
121                       check->vp_strvalue);
122                 return -1;
123         }
124         when += scan * 60;
125
126         p = strchr(p, ':');
127         if (p) {
128                 scan = atoi(p + 1);
129                 if (scan > 59) {
130                         DEBUG("rlm_logintime: Bad Time-Of-Day value \"%s\"",
131                               check->vp_strvalue);
132                         return -1;
133                 }
134                 when += scan;
135         }
136
137         fprintf(stderr, "returning %d - %d\n",
138                 hhmmss, when);
139
140         return hhmmss - when;
141 }
142
143 /*
144  *      Check if account has expired, and if user may login now.
145  */
146 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request)
147 {
148         rlm_logintime_t *inst = instance;
149         VALUE_PAIR *ends, *timeout;
150         int left;
151
152         ends = fr_pair_find_by_num(request->config, PW_LOGIN_TIME, 0, TAG_ANY);
153         if (!ends) {
154                 return RLM_MODULE_NOOP;
155         }
156
157         /*
158          *      Authentication is OK. Now see if this user may login at this time of the day.
159          */
160         RDEBUG("Checking Login-Time");
161
162         /*
163          *      Compare the time the request was received with the current Login-Time value
164          */
165         left = timestr_match(ends->vp_strvalue, request->timestamp);
166         if (left < 0) return RLM_MODULE_USERLOCK; /* outside of the allowed time */
167
168         /*
169          *      Do nothing, login time is not controlled (unendsed).
170          */
171         if (left == 0) {
172                 return RLM_MODULE_OK;
173         }
174
175         /*
176          *      The min_time setting is to deal with NAS that won't allow Session-Timeout values below a certain value
177          *      For example some Alcatel Lucent products won't allow a Session-Timeout < 300 (5 minutes).
178          *
179          *      We don't know were going to get another chance to lock out the user, so we need to do it now.
180          */
181         if (left < (int) inst->min_time) {
182                 REDEBUG("Login outside of allowed time-slot (session end %s, with lockout %i seconds before)",
183                         ends->vp_strvalue, inst->min_time);
184
185                 return RLM_MODULE_USERLOCK;
186         }
187
188         /* else left > inst->min_time */
189
190         /*
191          *      There's time left in the users session, inform the NAS by including a Session-Timeout
192          *      attribute in the reply, or modifying the existing one.
193          */
194         RDEBUG("Login within allowed time-slot, %d seconds left in this session", left);
195
196         timeout = fr_pair_find_by_num(request->reply->vps, PW_SESSION_TIMEOUT, 0, TAG_ANY);
197         if (timeout) {  /* just update... */
198                 if (timeout->vp_integer > (unsigned int) left) {
199                         timeout->vp_integer = left;
200                 }
201         } else {
202                 timeout = radius_pair_create(request->reply, &request->reply->vps, PW_SESSION_TIMEOUT, 0);
203                 timeout->vp_integer = left;
204         }
205
206         RDEBUG("reply:Session-Timeout set to %d", left);
207
208         return RLM_MODULE_UPDATED;
209 }
210
211
212 /*
213  *      Do any per-module initialization that is separate to each
214  *      configured instance of the module.  e.g. set up connections
215  *      to external databases, read configuration files, set up
216  *      dictionary entries, etc.
217  *
218  *      If configuration information is given in the config section
219  *      that must be referenced in later calls, store a handle to it
220  *      in *instance otherwise put a null pointer there.
221  */
222 static int mod_instantiate(CONF_SECTION *conf, void *instance)
223 {
224         rlm_logintime_t *inst = instance;
225
226         if (inst->min_time == 0) {
227                 cf_log_err_cs(conf, "Invalid value '0' for minimum_timeout");
228                 return -1;
229         }
230
231         /*
232          * Register a Current-Time comparison function
233          */
234         paircompare_register(dict_attrbyvalue(PW_CURRENT_TIME, 0), NULL, true, timecmp, inst);
235         paircompare_register(dict_attrbyvalue(PW_TIME_OF_DAY, 0), NULL, true, time_of_day, inst);
236
237         return 0;
238 }
239
240 /*
241  *      The module name should be the only globally exported symbol.
242  *      That is, everything else should be 'static'.
243  *
244  *      If the module needs to temporarily modify it's instantiation
245  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
246  *      The server will then take care of ensuring that the module
247  *      is single-threaded.
248  */
249 extern module_t rlm_logintime;
250 module_t rlm_logintime = {
251         .magic          = RLM_MODULE_INIT,
252         .name           = "logintime",
253         .inst_size      = sizeof(rlm_logintime_t),
254         .config         = module_config,
255         .instantiate    = mod_instantiate,
256         .methods = {
257                 [MOD_AUTHORIZE]         = mod_authorize,
258                 [MOD_POST_AUTH]         = mod_authorize
259         },
260 };