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