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