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