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