Fix argument order so ctx is always first
[freeradius.git] / src / modules / rlm_sql / rlm_sql.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, version 2 if the
4  *   License as published by the Free Software Foundation.
5  *
6  *   This program is distributed in the hope that it will be useful,
7  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
8  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9  *   GNU General Public License for more details.
10  *
11  *   You should have received a copy of the GNU General Public License
12  *   along with this program; if not, write to the Free Software
13  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
14  */
15
16 /**
17  * $Id$
18  * @file rlm_sql.c
19  * @brief Implements SQL 'users' file, and SQL accounting.
20  *
21  * @copyright 2012  Arran Cudbard-Bell <a.cudbardb@freeradius.org>
22  * @copyright 2000,2006  The FreeRADIUS server project
23  * @copyright 2000  Mike Machado <mike@innercite.com>
24  * @copyright 2000  Alan DeKok <aland@ox.org>
25  */
26 RCSID("$Id$")
27
28 #include <ctype.h>
29
30 #include <freeradius-devel/radiusd.h>
31 #include <freeradius-devel/modules.h>
32 #include <freeradius-devel/token.h>
33 #include <freeradius-devel/rad_assert.h>
34
35 #include <sys/stat.h>
36
37 #include "rlm_sql.h"
38
39 /*
40  *      So we can do pass2 xlat checks on the queries.
41  */
42 static const CONF_PARSER query_config[] = {
43
44         { "query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_MULTI, rlm_sql_config_t, accounting.query), NULL },
45
46         {NULL, -1, 0, NULL, NULL}
47 };
48
49 /*
50  *      For now hard-code the subsections.  This isn't perfect, but it
51  *      helps the average case.
52  */
53 static const CONF_PARSER type_config[] = {
54
55         { "accounting-on", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) query_config },
56         { "accounting-off", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) query_config },
57         { "start", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) query_config },
58         { "interim-update", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) query_config },
59         { "stop", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) query_config },
60
61         {NULL, -1, 0, NULL, NULL}
62 };
63
64
65
66 static const CONF_PARSER acct_config[] = {
67         { "reference", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, accounting.reference), ".query" },
68         { "logfile", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, accounting.logfile), NULL },
69
70         { "type", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) type_config },
71
72         {NULL, -1, 0, NULL, NULL}
73 };
74
75 static const CONF_PARSER postauth_config[] = {
76         { "reference", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, postauth.reference), ".query" },
77         { "logfile", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, postauth.logfile), NULL },
78
79         { "query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT | PW_TYPE_MULTI, rlm_sql_config_t, postauth.query), NULL },
80
81         {NULL, -1, 0, NULL, NULL}
82 };
83
84 static const CONF_PARSER module_config[] = {
85         { "driver", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, sql_driver_name), "rlm_sql_null" },
86         { "server", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, sql_server), "localhost" },
87         { "port", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, sql_port), "" },
88         { "login", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, sql_login), "" },
89         { "password", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_SECRET, rlm_sql_config_t, sql_password), "" },
90         { "radius_db", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, sql_db), "radius" },
91         { "read_groups", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sql_config_t, read_groups), "yes" },
92         { "read_profiles", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sql_config_t, read_profiles), "yes" },
93         { "readclients", FR_CONF_OFFSET(PW_TYPE_BOOLEAN | PW_TYPE_DEPRECATED, rlm_sql_config_t, do_clients), NULL },
94         { "read_clients", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sql_config_t, do_clients), "no" },
95         { "deletestalesessions", FR_CONF_OFFSET(PW_TYPE_BOOLEAN | PW_TYPE_DEPRECATED, rlm_sql_config_t, deletestalesessions), NULL },
96         { "delete_stale_sessions", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, rlm_sql_config_t, deletestalesessions), "yes" },
97         { "sql_user_name", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, query_user), "" },
98         { "logfile", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, logfile), NULL },
99         { "default_user_profile", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, default_profile), "" },
100         { "nas_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_DEPRECATED, rlm_sql_config_t, client_query), NULL },
101         { "client_query", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, client_query), "SELECT id,nasname,shortname,type,secret FROM nas" },
102         { "open_query", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, open_query), NULL },
103
104         { "authorize_check_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, authorize_check_query), NULL },
105         { "authorize_reply_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, authorize_reply_query), NULL },
106
107         { "authorize_group_check_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, authorize_group_check_query), "" },
108         { "authorize_group_reply_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, authorize_group_reply_query), "" },
109         { "group_membership_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, groupmemb_query), NULL },
110 #ifdef WITH_SESSION_MGMT
111         { "simul_count_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, simul_count_query), "" },
112         { "simul_verify_query", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_XLAT, rlm_sql_config_t, simul_verify_query), "" },
113 #endif
114         { "safe-characters", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_DEPRECATED, rlm_sql_config_t, allowed_chars), NULL },
115         { "safe_characters", FR_CONF_OFFSET(PW_TYPE_STRING, rlm_sql_config_t, allowed_chars), "@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /" },
116
117         /*
118          *      This only works for a few drivers.
119          */
120         { "query_timeout", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_sql_config_t, query_timeout), NULL },
121
122         { "accounting", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) acct_config },
123
124         { "post-auth", FR_CONF_POINTER(PW_TYPE_SUBSECTION, NULL), (void const *) postauth_config },
125
126         {NULL, -1, 0, NULL, NULL}
127 };
128
129 /*
130  *      Fall-Through checking function from rlm_files.c
131  */
132 static sql_fall_through_t fall_through(VALUE_PAIR *vp)
133 {
134         VALUE_PAIR *tmp;
135         tmp = pairfind(vp, PW_FALL_THROUGH, 0, TAG_ANY);
136
137         return tmp ? tmp->vp_integer : FALL_THROUGH_DEFAULT;
138 }
139
140 /*
141  *      Yucky prototype.
142  */
143 static int generate_sql_clients(rlm_sql_t *inst);
144 static size_t sql_escape_func(REQUEST *, char *out, size_t outlen, char const *in, void *arg);
145
146 /*
147  *                      SQL xlat function
148  *
149  *  For selects the first value of the first column will be returned,
150  *  for inserts, updates and deletes the number of rows affected will be
151  *  returned instead.
152  */
153 static ssize_t sql_xlat(void *instance, REQUEST *request, char const *query, char *out, size_t freespace)
154 {
155         rlm_sql_handle_t *handle = NULL;
156         rlm_sql_row_t row;
157         rlm_sql_t *inst = instance;
158         ssize_t ret = 0;
159         size_t len = 0;
160
161         /*
162          *      Add SQL-User-Name attribute just in case it is needed
163          *      We could search the string fmt for SQL-User-Name to see if this is
164          *      needed or not
165          */
166         sql_set_user(inst, request, NULL);
167
168         handle = sql_get_socket(inst);
169         if (!handle) {
170                 return 0;
171         }
172
173         rlm_sql_query_log(inst, request, NULL, query);
174
175         /*
176          *      If the query starts with any of the following prefixes,
177          *      then return the number of rows affected
178          */
179         if ((strncasecmp(query, "insert", 6) == 0) ||
180             (strncasecmp(query, "update", 6) == 0) ||
181             (strncasecmp(query, "delete", 6) == 0)) {
182                 int numaffected;
183                 char buffer[21]; /* 64bit max is 20 decimal chars + null byte */
184
185                 if (rlm_sql_query(&handle, inst, query) != RLM_SQL_OK) {
186                         char const *error = (inst->module->sql_error)(handle, inst->config);
187                         REDEBUG("SQL query failed: %s", error);
188
189                         ret = -1;
190                         goto finish;
191                 }
192
193                 numaffected = (inst->module->sql_affected_rows)(handle, inst->config);
194                 if (numaffected < 1) {
195                         RDEBUG("SQL query affected no rows");
196
197                         goto finish;
198                 }
199
200                 /*
201                  *      Don't chop the returned number if freespace is
202                  *      too small.  This hack is necessary because
203                  *      some implementations of snprintf return the
204                  *      size of the written data, and others return
205                  *      the size of the data they *would* have written
206                  *      if the output buffer was large enough.
207                  */
208                 snprintf(buffer, sizeof(buffer), "%d", numaffected);
209
210                 len = strlen(buffer);
211                 if (len >= freespace){
212                         RDEBUG("rlm_sql (%s): Can't write result, insufficient string space", inst->config->xlat_name);
213
214                         (inst->module->sql_finish_query)(handle, inst->config);
215
216                         ret = -1;
217                         goto finish;
218                 }
219
220                 memcpy(out, buffer, len + 1); /* we did bounds checking above */
221                 ret = len;
222
223                 (inst->module->sql_finish_query)(handle, inst->config);
224
225                 goto finish;
226         } /* else it's a SELECT statement */
227
228         if (rlm_sql_select_query(&handle, inst, query) != RLM_SQL_OK){
229                 char const *error = (inst->module->sql_error)(handle, inst->config);
230                 REDEBUG("SQL query failed: %s", error);
231                 ret = -1;
232
233                 goto finish;
234         }
235
236         ret = rlm_sql_fetch_row(&handle, inst);
237         if (ret) {
238                 REDEBUG("SQL query failed");
239                 (inst->module->sql_finish_select_query)(handle, inst->config);
240                 ret = -1;
241
242                 goto finish;
243         }
244
245         row = handle->row;
246         if (!row) {
247                 RDEBUG("SQL query returned no results");
248                 (inst->module->sql_finish_select_query)(handle, inst->config);
249                 ret = -1;
250
251                 goto finish;
252         }
253
254         if (!row[0]){
255                 RDEBUG("NULL value in first column of result");
256                 (inst->module->sql_finish_select_query)(handle, inst->config);
257                 ret = -1;
258
259                 goto finish;
260         }
261
262         len = strlen(row[0]);
263         if (len >= freespace){
264                 RDEBUG("Insufficient string space");
265                 (inst->module->sql_finish_select_query)(handle, inst->config);
266
267                 ret = -1;
268                 goto finish;
269         }
270
271         strlcpy(out, row[0], freespace);
272         ret = len;
273
274         (inst->module->sql_finish_select_query)(handle, inst->config);
275
276 finish:
277         sql_release_socket(inst, handle);
278
279         return ret;
280 }
281
282 static int generate_sql_clients(rlm_sql_t *inst)
283 {
284         rlm_sql_handle_t *handle;
285         rlm_sql_row_t row;
286         unsigned int i = 0;
287         RADCLIENT *c;
288
289         DEBUG("rlm_sql (%s): Processing generate_sql_clients",
290               inst->config->xlat_name);
291
292         DEBUG("rlm_sql (%s) in generate_sql_clients: query is %s",
293               inst->config->xlat_name, inst->config->client_query);
294
295         handle = sql_get_socket(inst);
296         if (!handle) {
297                 return -1;
298         }
299
300         if (rlm_sql_select_query(&handle, inst, inst->config->client_query) != RLM_SQL_OK){
301                 return -1;
302         }
303
304         while ((rlm_sql_fetch_row(&handle, inst) == 0) && (row = handle->row)) {
305                 char *server = NULL;
306                 i++;
307
308                 /*
309                  *  The return data for each row MUST be in the following order:
310                  *
311                  *  0. Row ID (currently unused)
312                  *  1. Name (or IP address)
313                  *  2. Shortname
314                  *  3. Type
315                  *  4. Secret
316                  *  5. Virtual Server (optional)
317                  */
318                 if (!row[0]){
319                         ERROR("rlm_sql (%s): No row id found on pass %d",inst->config->xlat_name,i);
320                         continue;
321                 }
322                 if (!row[1]){
323                         ERROR("rlm_sql (%s): No nasname found for row %s",inst->config->xlat_name,row[0]);
324                         continue;
325                 }
326                 if (!row[2]){
327                         ERROR("rlm_sql (%s): No short name found for row %s",inst->config->xlat_name,row[0]);
328                         continue;
329                 }
330                 if (!row[4]){
331                         ERROR("rlm_sql (%s): No secret found for row %s",inst->config->xlat_name,row[0]);
332                         continue;
333                 }
334
335                 if (((inst->module->sql_num_fields)(handle, inst->config) > 5) && (row[5] != NULL) && *row[5]) {
336                         server = row[5];
337                 }
338
339                 DEBUG("rlm_sql (%s): Adding client %s (%s) to %s clients list",
340                       inst->config->xlat_name,
341                       row[1], row[2], server ? server : "global");
342
343                 /* FIXME: We should really pass a proper ctx */
344                 c = client_from_query(NULL,
345                                       row[1],   /* identifier */
346                                       row[4],   /* secret */
347                                       row[2],   /* shortname */
348                                       row[3],   /* type */
349                                       server,   /* server */
350                                       false);   /* require message authenticator */
351                 if (!c) {
352                         continue;
353                 }
354
355                 if (!client_add(NULL, c)) {
356                         WARN("Failed to add client, possible duplicate?");
357
358                         client_free(c);
359                         continue;
360                 }
361
362                 DEBUG("rlm_sql (%s): Client \"%s\" (%s) added", c->longname, c->shortname,
363                       inst->config->xlat_name);
364         }
365
366         (inst->module->sql_finish_select_query)(handle, inst->config);
367         sql_release_socket(inst, handle);
368
369         return 0;
370 }
371
372
373 /*
374  *      Translate the SQL queries.
375  */
376 static size_t sql_escape_func(UNUSED REQUEST *request, char *out, size_t outlen,
377                               char const *in, void *arg)
378 {
379         rlm_sql_t *inst = arg;
380         size_t len = 0;
381
382         while (in[0]) {
383                 /*
384                  *      Non-printable characters get replaced with their
385                  *      mime-encoded equivalents.
386                  */
387                 if ((in[0] < 32) ||
388                     strchr(inst->config->allowed_chars, *in) == NULL) {
389                         /*
390                          *      Only 3 or less bytes available.
391                          */
392                         if (outlen <= 3) {
393                                 break;
394                         }
395
396                         snprintf(out, outlen, "=%02X", (unsigned char) in[0]);
397                         in++;
398                         out += 3;
399                         outlen -= 3;
400                         len += 3;
401                         continue;
402                 }
403
404                 /*
405                  *      Only one byte left.
406                  */
407                 if (outlen <= 1) {
408                         break;
409                 }
410
411                 /*
412                  *      Allowed character.
413                  */
414                 *out = *in;
415                 out++;
416                 in++;
417                 outlen--;
418                 len++;
419         }
420         *out = '\0';
421         return len;
422 }
423
424 /*
425  *      Set the SQL user name.
426  *
427  *      We don't call the escape function here. The resulting string
428  *      will be escaped later in the queries xlat so we don't need to
429  *      escape it twice. (it will make things wrong if we have an
430  *      escape candidate character in the username)
431  */
432 int sql_set_user(rlm_sql_t *inst, REQUEST *request, char const *username)
433 {
434         char *expanded = NULL;
435         VALUE_PAIR *vp = NULL;
436         char const *sqluser;
437         ssize_t len;
438
439         rad_assert(request->packet != NULL);
440
441         if (username != NULL) {
442                 sqluser = username;
443         } else if (inst->config->query_user[0] != '\0') {
444                 sqluser = inst->config->query_user;
445         } else {
446                 return 0;
447         }
448
449         len = radius_axlat(&expanded, request, sqluser, NULL, NULL);
450         if (len < 0) {
451                 return -1;
452         }
453
454         vp = pairalloc(request->packet, inst->sql_user);
455         if (!vp) {
456                 talloc_free(expanded);
457                 return -1;
458         }
459
460         pairstrsteal(vp, expanded);
461         RDEBUG2("SQL-User-Name set to '%s'", vp->vp_strvalue);
462         vp->op = T_OP_SET;
463         radius_pairmove(request, &request->packet->vps, vp, false);     /* needs to be pair move else op is not respected */
464
465         return 0;
466 }
467
468 /*
469  *      Do a set/unset user, so it's a bit clearer what's going on.
470  */
471 #define sql_unset_user(_i, _r) pairdelete(&_r->packet->vps, _i->sql_user->attr, _i->sql_user->vendor, TAG_ANY)
472
473 static int sql_get_grouplist(rlm_sql_t *inst, rlm_sql_handle_t **handle, REQUEST *request,
474                              rlm_sql_grouplist_t **phead)
475 {
476         char    *expanded = NULL;
477         int     num_groups = 0;
478         rlm_sql_row_t row;
479         rlm_sql_grouplist_t *entry;
480         int ret;
481
482         /* NOTE: sql_set_user should have been run before calling this function */
483
484         entry = *phead = NULL;
485
486         if (!inst->config->groupmemb_query || (inst->config->groupmemb_query[0] == 0)) {
487                 return 0;
488         }
489
490         if (radius_axlat(&expanded, request, inst->config->groupmemb_query, sql_escape_func, inst) < 0) {
491                 return -1;
492         }
493
494         ret = rlm_sql_select_query(handle, inst, expanded);
495         talloc_free(expanded);
496         if (ret != RLM_SQL_OK) {
497                 return -1;
498         }
499
500         while (rlm_sql_fetch_row(handle, inst) == 0) {
501                 row = (*handle)->row;
502                 if (!row)
503                         break;
504
505                 if (!row[0]){
506                         RDEBUG("row[0] returned NULL");
507                         (inst->module->sql_finish_select_query)(*handle, inst->config);
508                         talloc_free(entry);
509                         return -1;
510                 }
511
512                 if (!*phead) {
513                         *phead = talloc_zero(*handle, rlm_sql_grouplist_t);
514                         entry = *phead;
515                 } else {
516                         entry->next = talloc_zero(*phead, rlm_sql_grouplist_t);
517                         entry = entry->next;
518                 }
519                 entry->next = NULL;
520                 entry->name = talloc_typed_strdup(entry, row[0]);
521
522                 num_groups++;
523         }
524
525         (inst->module->sql_finish_select_query)(*handle, inst->config);
526
527         return num_groups;
528 }
529
530
531 /*
532  * sql groupcmp function. That way we can do group comparisons (in the users file for example)
533  * with the group memberships reciding in sql
534  * The group membership query should only return one element which is the username. The returned
535  * username will then be checked with the passed check string.
536  */
537
538 static int CC_HINT(nonnull (1 ,2, 4)) sql_groupcmp(void *instance, REQUEST *request, UNUSED VALUE_PAIR *request_vp,
539                                                    VALUE_PAIR *check, UNUSED VALUE_PAIR *check_pairs,
540                                                    UNUSED VALUE_PAIR **reply_pairs)
541 {
542         rlm_sql_handle_t *handle;
543         rlm_sql_t *inst = instance;
544         rlm_sql_grouplist_t *head, *entry;
545
546         RDEBUG("sql_groupcmp");
547
548         if (check->length == 0){
549                 RDEBUG("sql_groupcmp: Illegal group name");
550                 return 1;
551         }
552
553         /*
554          *      Set, escape, and check the user attr here
555          */
556         if (sql_set_user(inst, request, NULL) < 0)
557                 return 1;
558
559         /*
560          *      Get a socket for this lookup
561          */
562         handle = sql_get_socket(inst);
563         if (!handle) {
564                 return 1;
565         }
566
567         /*
568          *      Get the list of groups this user is a member of
569          */
570         if (sql_get_grouplist(inst, &handle, request, &head) < 0) {
571                 REDEBUG("Error getting group membership");
572                 sql_release_socket(inst, handle);
573                 return 1;
574         }
575
576         for (entry = head; entry != NULL; entry = entry->next) {
577                 if (strcmp(entry->name, check->vp_strvalue) == 0){
578                         RDEBUG("sql_groupcmp finished: User is a member of group %s",
579                                check->vp_strvalue);
580                         talloc_free(head);
581                         sql_release_socket(inst, handle);
582                         return 0;
583                 }
584         }
585
586         /* Free the grouplist */
587         talloc_free(head);
588         sql_release_socket(inst, handle);
589
590         RDEBUG("sql_groupcmp finished: User is NOT a member of group %s", check->vp_strvalue);
591
592         return 1;
593 }
594
595 static rlm_rcode_t rlm_sql_process_groups(rlm_sql_t *inst, REQUEST *request, rlm_sql_handle_t **handle,
596                                           sql_fall_through_t *do_fall_through)
597 {
598         rlm_rcode_t             rcode = RLM_MODULE_NOOP;
599         VALUE_PAIR              *check_tmp = NULL, *reply_tmp = NULL, *sql_group = NULL;
600         rlm_sql_grouplist_t     *head = NULL, *entry = NULL;
601
602         char                    *expanded = NULL;
603         int                     rows;
604
605         rad_assert(request->packet != NULL);
606
607         /*
608          *      Get the list of groups this user is a member of
609          */
610         rows = sql_get_grouplist(inst, handle, request, &head);
611         if (rows < 0) {
612                 REDEBUG("Error retrieving group list");
613
614                 return RLM_MODULE_FAIL;
615         }
616         if (rows == 0) {
617                 RDEBUG2("User not found in any groups");
618                 rcode = RLM_MODULE_NOTFOUND;
619                 goto finish;
620         }
621         rad_assert(head);
622
623         RDEBUG2("User found in the group table");
624
625         entry = head;
626         do {
627                 /*
628                  *      Add the Sql-Group attribute to the request list so we know
629                  *      which group we're retrieving attributes for
630                  */
631                 sql_group = pairmake_packet("Sql-Group", entry->name, T_OP_EQ);
632                 if (!sql_group) {
633                         REDEBUG("Error creating Sql-Group attribute");
634                         rcode = RLM_MODULE_FAIL;
635                         goto finish;
636                 }
637
638                 if (inst->config->authorize_group_check_query && (inst->config->authorize_group_check_query != '\0')) {
639                         /*
640                          *      Expand the group query
641                          */
642                         if (radius_axlat(&expanded, request, inst->config->authorize_group_check_query,
643                                          sql_escape_func, inst) < 0) {
644                                 REDEBUG("Error generating query");
645                                 rcode = RLM_MODULE_FAIL;
646                                 goto finish;
647                         }
648
649                         rows = sql_getvpdata(request, inst, handle, &check_tmp, expanded);
650                         TALLOC_FREE(expanded);
651                         if (rows < 0) {
652                                 REDEBUG("Error retrieving check pairs for group %s", entry->name);
653                                 rcode = RLM_MODULE_FAIL;
654                                 goto finish;
655                         }
656
657                         /*
658                          *      If we got check rows we need to process them before we decide to process the reply rows
659                          */
660                         if ((rows > 0) &&
661                             (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) != 0)) {
662                                 pairfree(&check_tmp);
663                                 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, TAG_ANY);
664
665                                 continue;
666                         }
667
668                         RDEBUG2("Group \"%s\" check items matched", entry->name);
669                         rcode = RLM_MODULE_OK;
670
671                         radius_pairmove(request, &request->config_items, check_tmp, true);
672                         check_tmp = NULL;
673                 }
674
675                 if (inst->config->authorize_group_reply_query && (inst->config->authorize_group_reply_query != '\0')) {
676                         /*
677                          *      Now get the reply pairs since the paircompare matched
678                          */
679                         if (radius_axlat(&expanded, request, inst->config->authorize_group_reply_query,
680                                          sql_escape_func, inst) < 0) {
681                                 REDEBUG("Error generating query");
682                                 rcode = RLM_MODULE_FAIL;
683                                 goto finish;
684                         }
685
686                         rows = sql_getvpdata(request->reply, inst, handle, &reply_tmp, expanded);
687                         TALLOC_FREE(expanded);
688                         if (rows < 0) {
689                                 REDEBUG("Error retrieving reply pairs for group %s", entry->name);
690                                 rcode = RLM_MODULE_FAIL;
691                                 goto finish;
692                         }
693
694                         *do_fall_through = fall_through(reply_tmp);
695
696                         RDEBUG2("Group \"%s\" reply items processed", entry->name);
697                         rcode = RLM_MODULE_OK;
698
699                         radius_pairmove(request, &request->reply->vps, reply_tmp, true);
700                         reply_tmp = NULL;
701                 }
702
703                 pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, TAG_ANY);
704                 entry = entry->next;
705         } while (entry != NULL && (*do_fall_through == FALL_THROUGH_YES));
706
707 finish:
708         talloc_free(head);
709         pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, TAG_ANY);
710
711         return rcode;
712 }
713
714
715 static int mod_detach(void *instance)
716 {
717         rlm_sql_t *inst = instance;
718
719         if (inst->config) {
720                 if (inst->pool) sql_poolfree(inst);
721         }
722
723         if (inst->handle) {
724 #if 0
725                 /*
726                  *      FIXME: Call the modules 'destroy' function?
727                  */
728                 dlclose(inst->handle);  /* ignore any errors */
729 #endif
730         }
731
732         return 0;
733 }
734
735 static int mod_instantiate(CONF_SECTION *conf, void *instance)
736 {
737         rlm_sql_t *inst = instance;
738
739         /*
740          *      Hack...
741          */
742         inst->config = &inst->myconfig;
743         inst->cs = conf;
744
745         inst->config->xlat_name = cf_section_name2(conf);
746         if (!inst->config->xlat_name) {
747                 inst->config->xlat_name = cf_section_name1(conf);
748         } else {
749                 char *group_name;
750                 DICT_ATTR const *da;
751                 ATTR_FLAGS flags;
752
753                 /*
754                  *      Allocate room for <instance>-SQL-Group
755                  */
756                 group_name = talloc_typed_asprintf(inst, "%s-SQL-Group", inst->config->xlat_name);
757                 DEBUG("rlm_sql (%s): Creating new attribute %s",
758                       inst->config->xlat_name, group_name);
759
760                 memset(&flags, 0, sizeof(flags));
761                 if (dict_addattr(group_name, -1, 0, PW_TYPE_STRING, flags) < 0) {
762                         ERROR("rlm_sql (%s): Failed to create "
763                                "attribute %s: %s", inst->config->xlat_name, group_name,
764                                fr_strerror());
765                         return -1;
766                 }
767
768                 da = dict_attrbyname(group_name);
769                 if (!da) {
770                         ERROR("rlm_sql (%s): Failed to create "
771                                "attribute %s", inst->config->xlat_name, group_name);
772                         return -1;
773                 }
774
775                 if (inst->config->groupmemb_query &&
776                     inst->config->groupmemb_query[0]) {
777                         DEBUG("rlm_sql (%s): Registering sql_groupcmp for %s",
778                               inst->config->xlat_name, group_name);
779                         paircompare_register(da, dict_attrbyvalue(PW_USER_NAME, 0),
780                                              false, sql_groupcmp, inst);
781                 }
782         }
783
784         rad_assert(inst->config->xlat_name);
785
786         /*
787          *      This will always exist, as cf_section_parse_init()
788          *      will create it if it doesn't exist.  However, the
789          *      "reference" config item won't exist in an auto-created
790          *      configuration.  So if that doesn't exist, we ignore
791          *      the whole subsection.
792          */
793         inst->config->accounting.cs = cf_section_sub_find(conf, "accounting");
794         inst->config->accounting.reference_cp = (cf_pair_find(inst->config->accounting.cs, "reference") != NULL);
795
796         inst->config->postauth.cs = cf_section_sub_find(conf, "post-auth");
797         inst->config->postauth.reference_cp = (cf_pair_find(inst->config->postauth.cs, "reference") != NULL);
798
799         /*
800          *      Cache the SQL-User-Name DICT_ATTR, so we can be slightly
801          *      more efficient about creating SQL-User-Name attributes.
802          */
803         inst->sql_user = dict_attrbyname("SQL-User-Name");
804         if (!inst->sql_user) {
805                 return -1;
806         }
807
808         /*
809          *      Export these methods, too.  This avoids RTDL_GLOBAL.
810          */
811         inst->sql_set_user              = sql_set_user;
812         inst->sql_get_socket            = sql_get_socket;
813         inst->sql_release_socket        = sql_release_socket;
814         inst->sql_escape_func           = sql_escape_func;
815         inst->sql_query                 = rlm_sql_query;
816         inst->sql_select_query          = rlm_sql_select_query;
817         inst->sql_fetch_row             = rlm_sql_fetch_row;
818
819         /*
820          *      Register the SQL xlat function
821          */
822         xlat_register(inst->config->xlat_name, sql_xlat, sql_escape_func, inst);
823
824         /*
825          *      Sanity check for crazy people.
826          */
827         if (strncmp(inst->config->sql_driver_name, "rlm_sql_", 8) != 0) {
828                 ERROR("rlm_sql (%s): \"%s\" is NOT an SQL driver!",
829                        inst->config->xlat_name, inst->config->sql_driver_name);
830                 return -1;
831         }
832
833         /*
834          *      Load the appropriate driver for our database
835          */
836         inst->handle = lt_dlopenext(inst->config->sql_driver_name);
837         if (!inst->handle) {
838                 ERROR("Could not link driver %s: %s",
839                        inst->config->sql_driver_name,
840                        dlerror());
841                 ERROR("Make sure it (and all its dependent libraries!)"
842                        "are in the search path of your system's ld");
843                 return -1;
844         }
845
846         inst->module = (rlm_sql_module_t *) dlsym(inst->handle,
847                                                   inst->config->sql_driver_name);
848         if (!inst->module) {
849                 ERROR("Could not link symbol %s: %s",
850                        inst->config->sql_driver_name,
851                        dlerror());
852                 return -1;
853         }
854
855         if (inst->module->mod_instantiate) {
856                 CONF_SECTION *cs;
857                 char const *name;
858
859                 name = strrchr(inst->config->sql_driver_name, '_');
860                 if (!name) {
861                         name = inst->config->sql_driver_name;
862                 } else {
863                         name++;
864                 }
865
866                 cs = cf_section_sub_find(conf, name);
867                 if (!cs) {
868                         cs = cf_section_alloc(conf, name, NULL);
869                         if (!cs) {
870                                 return -1;
871                         }
872                 }
873
874                 /*
875                  *      It's up to the driver to register a destructor
876                  */
877                 if (inst->module->mod_instantiate(cs, inst->config) < 0) {
878                         return -1;
879                 }
880         }
881
882         inst->lf = fr_logfile_init(inst);
883         if (!inst->lf) {
884                 cf_log_err_cs(conf, "Failed creating log file context");
885                 return -1;
886         }
887
888         INFO("rlm_sql (%s): Driver %s (module %s) loaded and linked",
889                inst->config->xlat_name, inst->config->sql_driver_name,
890                inst->module->name);
891
892         /*
893          *      Initialise the connection pool for this instance
894          */
895         INFO("rlm_sql (%s): Attempting to connect to database \"%s\"",
896                inst->config->xlat_name, inst->config->sql_db);
897
898         if (sql_socket_pool_init(inst) < 0) return -1;
899
900         if (inst->config->groupmemb_query &&
901             inst->config->groupmemb_query[0]) {
902                 paircompare_register(dict_attrbyvalue(PW_SQL_GROUP, 0),
903                                 dict_attrbyvalue(PW_USER_NAME, 0), false, sql_groupcmp, inst);
904         }
905
906         if (inst->config->do_clients) {
907                 if (generate_sql_clients(inst) == -1){
908                         ERROR("Failed to load clients from SQL");
909                         return -1;
910                 }
911         }
912
913         return RLM_MODULE_OK;
914 }
915
916
917 static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request)
918 {
919         rlm_rcode_t rcode = RLM_MODULE_NOOP;
920
921         rlm_sql_t *inst = instance;
922         rlm_sql_handle_t  *handle;
923
924         VALUE_PAIR *check_tmp = NULL;
925         VALUE_PAIR *reply_tmp = NULL;
926         VALUE_PAIR *user_profile = NULL;
927
928         bool    user_found = false;
929
930         sql_fall_through_t do_fall_through = FALL_THROUGH_DEFAULT;
931
932         int     rows;
933
934         char    *expanded = NULL;
935
936         rad_assert(request->packet != NULL);
937         rad_assert(request->reply != NULL);
938
939         if (!inst->config->authorize_check_query && !inst->config->authorize_reply_query &&
940             !inst->config->read_groups && !inst->config->read_profiles) {
941                 RWDEBUG("No authorization checks configured, returning noop");
942
943                 return RLM_MODULE_NOOP;
944         }
945
946         /*
947          *      Set, escape, and check the user attr here
948          */
949         if (sql_set_user(inst, request, NULL) < 0) {
950                 return RLM_MODULE_FAIL;
951         }
952
953         /*
954          *      Reserve a socket
955          *
956          *      After this point use goto error or goto release to cleanup socket temporary pairlists and
957          *      temporary attributes.
958          */
959         handle = sql_get_socket(inst);
960         if (!handle) {
961                 rcode = RLM_MODULE_FAIL;
962                 goto error;
963         }
964
965         /*
966          *      Query the check table to find any conditions associated with this user/realm/whatever...
967          */
968         if (inst->config->authorize_check_query) {
969                 if (radius_axlat(&expanded, request, inst->config->authorize_check_query,
970                                  sql_escape_func, inst) < 0) {
971                         REDEBUG("Error generating query");
972                         rcode = RLM_MODULE_FAIL;
973                         goto error;
974                 }
975
976                 rows = sql_getvpdata(request, inst, &handle, &check_tmp, expanded);
977                 TALLOC_FREE(expanded);
978                 if (rows < 0) {
979                         REDEBUG("SQL query error");
980                         rcode = RLM_MODULE_FAIL;
981                         goto error;
982                 }
983
984                 if (rows == 0) goto skipreply;  /* Don't need to free VPs we don't have */
985
986                 /*
987                  *      Only do this if *some* check pairs were returned
988                  */
989                 RDEBUG2("User found in radcheck table");
990                 user_found = true;
991                 if (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) != 0) {
992                         pairfree(&check_tmp);
993                         check_tmp = NULL;
994                         goto skipreply;
995                 }
996
997                 RDEBUG2("Check items matched");
998                 radius_pairmove(request, &request->config_items, check_tmp, true);
999                 rcode = RLM_MODULE_OK;
1000                 check_tmp = NULL;
1001         }
1002
1003         if (inst->config->authorize_reply_query) {
1004                 /*
1005                  *      Now get the reply pairs since the paircompare matched
1006                  */
1007                 if (radius_axlat(&expanded, request, inst->config->authorize_reply_query,
1008                                  sql_escape_func, inst) < 0) {
1009                         REDEBUG("Error generating query");
1010                         rcode = RLM_MODULE_FAIL;
1011                         goto error;
1012                 }
1013
1014                 rows = sql_getvpdata(request->reply, inst, &handle, &reply_tmp, expanded);
1015                 TALLOC_FREE(expanded);
1016                 if (rows < 0) {
1017                         REDEBUG("SQL query error");
1018                         rcode = RLM_MODULE_FAIL;
1019                         goto error;
1020                 }
1021
1022                 if (rows == 0) goto skipreply;
1023
1024                 do_fall_through = fall_through(reply_tmp);
1025
1026                 RDEBUG2("User found in radreply table");
1027                 user_found = true;
1028                 radius_pairmove(request, &request->reply->vps, reply_tmp, true);
1029                 rcode = RLM_MODULE_OK;
1030                 reply_tmp = NULL;
1031         }
1032
1033 skipreply:
1034         if ((do_fall_through == FALL_THROUGH_YES) ||
1035             (inst->config->read_groups && (do_fall_through == FALL_THROUGH_DEFAULT))) {
1036                 rlm_rcode_t ret;
1037
1038                 RDEBUG3("... falling-through to group processing");
1039                 ret = rlm_sql_process_groups(inst, request, &handle, &do_fall_through);
1040                 switch (ret) {
1041                 /*
1042                  *      Nothing bad happened, continue...
1043                  */
1044                 case RLM_MODULE_UPDATED:
1045                         rcode = RLM_MODULE_UPDATED;
1046                         /* FALL-THROUGH */
1047                 case RLM_MODULE_OK:
1048                         if (rcode != RLM_MODULE_UPDATED) {
1049                                 rcode = RLM_MODULE_OK;
1050                         }
1051                         /* FALL-THROUGH */
1052                 case RLM_MODULE_NOOP:
1053                         user_found = true;
1054                         break;
1055
1056                 case RLM_MODULE_NOTFOUND:
1057                         break;
1058
1059                 default:
1060                         rcode = ret;
1061                         goto release;
1062                 }
1063         }
1064
1065         /*
1066          *      Repeat the above process with the default profile or User-Profile
1067          */
1068         if ((do_fall_through == FALL_THROUGH_YES) ||
1069             (inst->config->read_profiles && (do_fall_through == FALL_THROUGH_DEFAULT))) {
1070                 rlm_rcode_t ret;
1071
1072                 /*
1073                  *  Check for a default_profile or for a User-Profile.
1074                  */
1075                 RDEBUG3("... falling-through to profile processing");
1076                 user_profile = pairfind(request->config_items, PW_USER_PROFILE, 0, TAG_ANY);
1077
1078                 char const *profile = user_profile ?
1079                                       user_profile->vp_strvalue :
1080                                       inst->config->default_profile;
1081
1082                 if (!profile || !*profile) {
1083                         goto release;
1084                 }
1085
1086                 RDEBUG2("Checking profile %s", profile);
1087
1088                 if (sql_set_user(inst, request, profile) < 0) {
1089                         REDEBUG("Error setting profile");
1090                         rcode = RLM_MODULE_FAIL;
1091                         goto error;
1092                 }
1093
1094                 ret = rlm_sql_process_groups(inst, request, &handle, &do_fall_through);
1095                 switch (ret) {
1096                 /*
1097                  *      Nothing bad happened, continue...
1098                  */
1099                 case RLM_MODULE_UPDATED:
1100                         rcode = RLM_MODULE_UPDATED;
1101                         /* FALL-THROUGH */
1102                 case RLM_MODULE_OK:
1103                         if (rcode != RLM_MODULE_UPDATED) {
1104                                 rcode = RLM_MODULE_OK;
1105                         }
1106                         /* FALL-THROUGH */
1107                 case RLM_MODULE_NOOP:
1108                         user_found = true;
1109                         break;
1110
1111                 case RLM_MODULE_NOTFOUND:
1112                         break;
1113
1114                 default:
1115                         rcode = ret;
1116                         goto release;
1117                 }
1118         }
1119
1120         /*
1121          *      At this point the key (user) hasn't be found in the check table, the reply table
1122          *      or the group mapping table, and there was no matching profile.
1123          */
1124 release:
1125         if (!user_found) {
1126                 rcode = RLM_MODULE_NOTFOUND;
1127         }
1128
1129         sql_release_socket(inst, handle);
1130         sql_unset_user(inst, request);
1131
1132         return rcode;
1133
1134 error:
1135         pairfree(&check_tmp);
1136         pairfree(&reply_tmp);
1137         sql_unset_user(inst, request);
1138
1139         sql_release_socket(inst, handle);
1140
1141         return rcode;
1142 }
1143
1144 /*
1145  *      Generic function for failing between a bunch of queries.
1146  *
1147  *      Uses the same principle as rlm_linelog, expanding the 'reference' config
1148  *      item using xlat to figure out what query it should execute.
1149  *
1150  *      If the reference matches multiple config items, and a query fails or
1151  *      doesn't update any rows, the next matching config item is used.
1152  *
1153  */
1154 static int acct_redundant(rlm_sql_t *inst, REQUEST *request, sql_acct_section_t *section)
1155 {
1156         rlm_rcode_t             rcode = RLM_MODULE_OK;
1157
1158         rlm_sql_handle_t        *handle = NULL;
1159         int                     sql_ret;
1160         int                     numaffected = 0;
1161
1162         CONF_ITEM               *item;
1163         CONF_PAIR               *pair;
1164         char const              *attr = NULL;
1165         char const              *value;
1166
1167         char                    path[MAX_STRING_LEN];
1168         char                    *p = path;
1169         char                    *expanded = NULL;
1170
1171         rad_assert(section);
1172
1173         if (section->reference[0] != '.') {
1174                 *p++ = '.';
1175         }
1176
1177         if (radius_xlat(p, sizeof(path) - (p - path), request, section->reference, NULL, NULL) < 0) {
1178                 rcode = RLM_MODULE_FAIL;
1179
1180                 goto finish;
1181         }
1182
1183         item = cf_reference_item(NULL, section->cs, path);
1184         if (!item) {
1185                 rcode = RLM_MODULE_FAIL;
1186
1187                 goto finish;
1188         }
1189
1190         if (cf_item_is_section(item)){
1191                 REDEBUG("Sections are not supported as references");
1192                 rcode = RLM_MODULE_FAIL;
1193
1194                 goto finish;
1195         }
1196
1197         pair = cf_itemtopair(item);
1198         attr = cf_pair_attr(pair);
1199
1200         RDEBUG2("Using query template '%s'", attr);
1201
1202         handle = sql_get_socket(inst);
1203         if (!handle) {
1204                 rcode = RLM_MODULE_FAIL;
1205
1206                 goto finish;
1207         }
1208
1209         sql_set_user(inst, request, NULL);
1210
1211         while (true) {
1212                 value = cf_pair_value(pair);
1213                 if (!value) {
1214                         RDEBUG("Ignoring null query");
1215                         rcode = RLM_MODULE_NOOP;
1216
1217                         goto finish;
1218                 }
1219
1220                 if (radius_axlat(&expanded, request, value, sql_escape_func, inst) < 0) {
1221                         rcode = RLM_MODULE_FAIL;
1222
1223                         goto finish;
1224                 }
1225
1226                 if (!*expanded) {
1227                         RDEBUG("Ignoring null query");
1228                         rcode = RLM_MODULE_NOOP;
1229                         talloc_free(expanded);
1230
1231                         goto finish;
1232                 }
1233
1234                 rlm_sql_query_log(inst, request, section, expanded);
1235
1236                 /*
1237                  *  If rlm_sql_query cannot use the socket it'll try and
1238                  *  reconnect. Reconnecting will automatically release
1239                  *  the current socket, and try to select a new one.
1240                  *
1241                  *  If we get RLM_SQL_RECONNECT it means all connections in the pool
1242                  *  were exhausted, and we couldn't create a new connection,
1243                  *  so we do not need to call sql_release_socket.
1244                  */
1245                 sql_ret = rlm_sql_query(&handle, inst, expanded);
1246                 TALLOC_FREE(expanded);
1247                 if (sql_ret == RLM_SQL_RECONNECT) {
1248                         rcode = RLM_MODULE_FAIL;
1249                         goto finish;
1250                 }
1251                 rad_assert(handle);
1252
1253                 /*
1254                  *  Assume all other errors are incidental, and just meant our
1255                  *  operation failed and its not a client or SQL syntax error.
1256                  *
1257                  *  @fixme We should actually be able to distinguish between key
1258                  *  constraint violations (which we expect) and other errors.
1259                  */
1260                 if (sql_ret == RLM_SQL_OK) {
1261                         numaffected = (inst->module->sql_affected_rows)(handle, inst->config);
1262                         if (numaffected > 0) {
1263                                 break;  /* A query succeeded, were done! */
1264                         }
1265
1266                         RDEBUG("No records updated");
1267                 }
1268
1269                 (inst->module->sql_finish_query)(handle, inst->config);
1270
1271                 /*
1272                  *  We assume all entries with the same name form a redundant
1273                  *  set of queries.
1274                  */
1275                 pair = cf_pair_find_next(section->cs, pair, attr);
1276
1277                 if (!pair) {
1278                         RDEBUG("No additional queries configured");
1279                         rcode = RLM_MODULE_NOOP;
1280
1281                         goto finish;
1282                 }
1283
1284                 RDEBUG("Trying next query...");
1285         }
1286
1287         (inst->module->sql_finish_query)(handle, inst->config);
1288
1289 finish:
1290         talloc_free(expanded);
1291         sql_release_socket(inst, handle);
1292         sql_unset_user(inst, request);
1293
1294         return rcode;
1295 }
1296
1297 #ifdef WITH_ACCOUNTING
1298
1299 /*
1300  *      Accounting: Insert or update session data in our sql table
1301  */
1302 static rlm_rcode_t CC_HINT(nonnull) mod_accounting(void *instance, REQUEST * request) {
1303         rlm_sql_t *inst = instance;
1304
1305         if (inst->config->accounting.reference_cp) {
1306                 return acct_redundant(inst, request, &inst->config->accounting);
1307         }
1308
1309         return RLM_MODULE_NOOP;
1310 }
1311
1312 #endif
1313
1314 #ifdef WITH_SESSION_MGMT
1315 /*
1316  *      See if a user is already logged in. Sets request->simul_count to the
1317  *      current session count for this user.
1318  *
1319  *      Check twice. If on the first pass the user exceeds his
1320  *      max. number of logins, do a second pass and validate all
1321  *      logins by querying the terminal server (using eg. SNMP).
1322  */
1323
1324 static rlm_rcode_t CC_HINT(nonnull) mod_checksimul(void *instance, REQUEST * request) {
1325         rlm_rcode_t             rcode = RLM_MODULE_OK;
1326         rlm_sql_handle_t        *handle = NULL;
1327         rlm_sql_t               *inst = instance;
1328         rlm_sql_row_t           row;
1329         int                     check = 0;
1330         uint32_t                ipno = 0;
1331         char const              *call_num = NULL;
1332         VALUE_PAIR              *vp;
1333         int                     ret;
1334         uint32_t                nas_addr = 0;
1335         uint32_t                nas_port = 0;
1336
1337         char                    *expanded = NULL;
1338
1339         /* If simul_count_query is not defined, we don't do any checking */
1340         if (!inst->config->simul_count_query || (inst->config->simul_count_query[0] == '\0')) {
1341                 return RLM_MODULE_NOOP;
1342         }
1343
1344         if((!request->username) || (request->username->length == '\0')) {
1345                 REDEBUG("Zero Length username not permitted");
1346
1347                 return RLM_MODULE_INVALID;
1348         }
1349
1350
1351         if(sql_set_user(inst, request, NULL) < 0) {
1352                 return RLM_MODULE_FAIL;
1353         }
1354
1355         if (radius_axlat(&expanded, request, inst->config->simul_count_query, sql_escape_func, inst) < 0) {
1356                 sql_unset_user(inst, request);
1357                 return RLM_MODULE_FAIL;
1358         }
1359
1360         /* initialize the sql socket */
1361         handle = sql_get_socket(inst);
1362         if (!handle) {
1363                 talloc_free(expanded);
1364                 sql_unset_user(inst, request);
1365                 return RLM_MODULE_FAIL;
1366         }
1367
1368         if (rlm_sql_select_query(&handle, inst, expanded) != RLM_SQL_OK) {
1369                 rcode = RLM_MODULE_FAIL;
1370                 goto finish;
1371         }
1372
1373         ret = rlm_sql_fetch_row(&handle, inst);
1374         if (ret != 0) {
1375                 rcode = RLM_MODULE_FAIL;
1376                 goto finish;
1377         }
1378
1379         row = handle->row;
1380         if (!row) {
1381                 rcode = RLM_MODULE_FAIL;
1382                 goto finish;
1383         }
1384
1385         request->simul_count = atoi(row[0]);
1386
1387         (inst->module->sql_finish_select_query)(handle, inst->config);
1388         TALLOC_FREE(expanded);
1389
1390         if (request->simul_count < request->simul_max) {
1391                 rcode = RLM_MODULE_OK;
1392                 goto finish;
1393         }
1394
1395         /*
1396          *      Looks like too many sessions, so let's start verifying
1397          *      them, unless told to rely on count query only.
1398          */
1399         if (!inst->config->simul_verify_query || (inst->config->simul_verify_query[0] == '\0')) {
1400                 rcode = RLM_MODULE_OK;
1401
1402                 goto finish;
1403         }
1404
1405         if (radius_axlat(&expanded, request, inst->config->simul_verify_query, sql_escape_func, inst) < 0) {
1406                 rcode = RLM_MODULE_FAIL;
1407
1408                 goto finish;
1409         }
1410
1411         if (rlm_sql_select_query(&handle, inst, expanded) != RLM_SQL_OK) {
1412                 goto finish;
1413         }
1414
1415         /*
1416          *      Setup some stuff, like for MPP detection.
1417          */
1418         request->simul_count = 0;
1419
1420         if ((vp = pairfind(request->packet->vps, PW_FRAMED_IP_ADDRESS, 0, TAG_ANY)) != NULL) {
1421                 ipno = vp->vp_ipaddr;
1422         }
1423
1424         if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID, 0, TAG_ANY)) != NULL) {
1425                 call_num = vp->vp_strvalue;
1426         }
1427
1428         while (rlm_sql_fetch_row(&handle, inst) == 0) {
1429                 row = handle->row;
1430                 if (!row) {
1431                         break;
1432                 }
1433
1434                 if (!row[2]){
1435                         RDEBUG("Cannot zap stale entry. No username present in entry");
1436                         rcode = RLM_MODULE_FAIL;
1437
1438                         goto finish;
1439                 }
1440
1441                 if (!row[1]){
1442                         RDEBUG("Cannot zap stale entry. No session id in entry");
1443                         rcode = RLM_MODULE_FAIL;
1444
1445                         goto finish;
1446                 }
1447
1448                 if (row[3]) {
1449                         nas_addr = inet_addr(row[3]);
1450                 }
1451
1452                 if (row[4]) {
1453                         nas_port = atoi(row[4]);
1454                 }
1455
1456                 check = rad_check_ts(nas_addr, nas_port, row[2], row[1]);
1457                 if (check == 0) {
1458                         /*
1459                          *      Stale record - zap it.
1460                          */
1461                         if (inst->config->deletestalesessions == true) {
1462                                 uint32_t framed_addr = 0;
1463                                 char proto = 0;
1464                                 int sess_time = 0;
1465
1466                                 if (row[5])
1467                                         framed_addr = inet_addr(row[5]);
1468                                 if (row[7]){
1469                                         if (strcmp(row[7], "PPP") == 0)
1470                                                 proto = 'P';
1471                                         else if (strcmp(row[7], "SLIP") == 0)
1472                                                 proto = 'S';
1473                                 }
1474                                 if (row[8])
1475                                         sess_time = atoi(row[8]);
1476                                 session_zap(request, nas_addr, nas_port,
1477                                             row[2], row[1], framed_addr,
1478                                             proto, sess_time);
1479                         }
1480                 }
1481                 else if (check == 1) {
1482                         /*
1483                          *      User is still logged in.
1484                          */
1485                         ++request->simul_count;
1486
1487                         /*
1488                          *      Does it look like a MPP attempt?
1489                          */
1490                         if (row[5] && ipno && inet_addr(row[5]) == ipno) {
1491                                 request->simul_mpp = 2;
1492                         } else if (row[6] && call_num && !strncmp(row[6],call_num,16)) {
1493                                 request->simul_mpp = 2;
1494                         }
1495                 } else {
1496                         /*
1497                          *      Failed to check the terminal server for
1498                          *      duplicate logins: return an error.
1499                          */
1500                         REDEBUG("Failed to check the terminal server for user '%s'.", row[2]);
1501
1502                         rcode = RLM_MODULE_FAIL;
1503                         goto finish;
1504                 }
1505         }
1506
1507         finish:
1508
1509         (inst->module->sql_finish_select_query)(handle, inst->config);
1510         sql_release_socket(inst, handle);
1511         talloc_free(expanded);
1512         sql_unset_user(inst, request);
1513
1514         /*
1515          *      The Auth module apparently looks at request->simul_count,
1516          *      not the return value of this module when deciding to deny
1517          *      a call for too many sessions.
1518          */
1519         return rcode;
1520 }
1521 #endif
1522
1523 /*
1524  *      Postauth: Write a record of the authentication attempt
1525  */
1526 static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST * request) {
1527         rlm_sql_t *inst = instance;
1528
1529         if (inst->config->postauth.reference_cp) {
1530                 return acct_redundant(inst, request, &inst->config->postauth);
1531         }
1532
1533         return RLM_MODULE_NOOP;
1534 }
1535
1536 /*
1537  *      Execute postauth_query after authentication
1538  */
1539
1540
1541 /* globally exported name */
1542 module_t rlm_sql = {
1543         RLM_MODULE_INIT,
1544         "SQL",
1545         RLM_TYPE_THREAD_SAFE,   /* type: reserved */
1546         sizeof(rlm_sql_t),
1547         module_config,
1548         mod_instantiate,        /* instantiation */
1549         mod_detach,             /* detach */
1550         {
1551                 NULL,                   /* authentication */
1552                 mod_authorize,  /* authorization */
1553                 NULL,                   /* preaccounting */
1554 #ifdef WITH_ACCOUNTING
1555                 mod_accounting, /* accounting */
1556 #else
1557                 NULL,
1558 #endif
1559 #ifdef WITH_SESSION_MGMT
1560                 mod_checksimul, /* checksimul */
1561 #else
1562                 NULL,
1563 #endif
1564                 NULL,                   /* pre-proxy */
1565                 NULL,                   /* post-proxy */
1566                 mod_post_auth   /* post-auth */
1567         },
1568 };