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