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