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