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