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