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