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