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