4ce96848eb5559e048c82b8898222d57b10111c2
[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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  *
21  * Copyright 2000  The FreeRADIUS server project
22  * Copyright 2000  Mike Machado <mike@innercite.com>
23  * Copyright 2000  Alan DeKok <aland@ox.org>
24  */
25
26 static const char rcsid[] =
27         "$Id$";
28
29 #include "autoconf.h"
30
31 #include <stdio.h>
32 #include <sys/stat.h>
33 #include <stdlib.h>
34
35 #include <time.h>
36 #include <unistd.h>
37 #include <sys/types.h>
38 #include <sys/wait.h>
39 #include <string.h>
40
41 #include <sys/socket.h>
42 #include <netinet/in.h>
43 #include <arpa/inet.h>
44
45 #include "radiusd.h"
46 #include "modules.h"
47 #include "conffile.h"
48 #include "rlm_sql.h"
49 #include "rad_assert.h"
50
51 static CONF_PARSER module_config[] = {
52         {"driver",PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_driver), NULL, "mysql"},
53         {"server",PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_server), NULL, "localhost"},
54         {"port",PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_port), NULL, ""},
55         {"login", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_login), NULL, ""},
56         {"password", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_password), NULL, ""},
57         {"radius_db", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_db), NULL, "radius"},
58         {"acct_table", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_acct_table), NULL, "radacct"},
59         {"acct_table2", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_acct_table2), NULL, "radacct"},
60         {"authcheck_table", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_authcheck_table), NULL, "radcheck"},
61         {"authreply_table", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_authreply_table), NULL, "radreply"},
62         {"groupcheck_table", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_groupcheck_table), NULL, "radgroupcheck"},
63         {"groupreply_table", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_groupreply_table), NULL, "radgroupreply"},
64         {"usergroup_table", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_usergroup_table), NULL, "usergroup"},
65         {"nas_table", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_nas_table), NULL, "nas"},
66         {"dict_table", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,sql_dict_table), NULL, "dictionary"},
67         {"sqltrace", PW_TYPE_BOOLEAN, offsetof(SQL_CONFIG,sqltrace), NULL, "0"},
68         {"sqltracefile", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,tracefile), NULL, SQLTRACEFILE},
69         {"deletestalesessions", PW_TYPE_BOOLEAN, offsetof(SQL_CONFIG,deletestalesessions), NULL, "0"},
70         {"num_sql_socks", PW_TYPE_INTEGER, offsetof(SQL_CONFIG,num_sql_socks), NULL, "5"},
71         {"sql_user_name", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,query_user), NULL, ""},
72         {"authorize_check_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,authorize_check_query), NULL, ""},
73         {"authorize_reply_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,authorize_reply_query), NULL, ""},
74         {"authorize_group_check_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,authorize_group_check_query), NULL, ""},
75         {"authorize_group_reply_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,authorize_group_reply_query), NULL, ""},
76         {"accounting_onoff_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,accounting_onoff_query), NULL, ""},
77         {"accounting_update_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,accounting_update_query), NULL, ""},
78         {"accounting_start_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,accounting_start_query), NULL, ""},
79         {"accounting_start_query_alt", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,accounting_start_query_alt), NULL, ""},
80         {"accounting_stop_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,accounting_stop_query), NULL, ""},
81         {"accounting_stop_query_alt", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,accounting_stop_query_alt), NULL, ""},
82         {"group_membership_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,groupmemb_query), NULL, ""},
83         {"connect_failure_retry_delay", PW_TYPE_INTEGER, offsetof(SQL_CONFIG,connect_failure_retry_delay), NULL, "60"},
84         {"simul_count_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,simul_count_query), NULL, ""},
85         {"simul_verify_query", PW_TYPE_STRING_PTR, offsetof(SQL_CONFIG,simul_verify_query), NULL, ""},
86
87         {NULL, -1, 0, NULL, NULL}
88 };
89
90 /***********************************************************************
91  * start of main routines
92  ***********************************************************************/
93 static int rlm_sql_init(void) {
94
95         /*
96          * FIXME:
97          * We should put the sqlsocket array here once
98          * the module code is reworked to not unload
99          * modules on HUP.  This way we can have
100          * persistant connections.  -jcarneal
101          */
102         return 0;
103 }
104
105 /*
106  *      sql xlat function. Right now only SELECTs are supported. Only
107  *      the first element of the SELECT result will be used.
108  */
109 static int sql_xlat(void *instance, REQUEST *request, char *fmt, char *out, int freespace,
110                         RADIUS_ESCAPE_STRING func)
111 {
112         SQLSOCK *sqlsocket;
113         SQL_ROW row;
114         SQL_INST *inst=instance;
115         char querystr[MAX_QUERY_LEN];
116         int ret = 0;
117
118         DEBUG("rlm_sql: - sql_xlat");
119         /*
120          * Do an xlat on the provided string (nice recursive operation).
121          */
122         if (!radius_xlat(querystr, sizeof(querystr), fmt, request, func)){
123                 radlog(L_ERR, "rlm_sql: xlat failed.");
124                 return 0;
125         }
126
127         sqlsocket = sql_get_socket(inst);
128         if (sqlsocket == NULL)
129                 return 0;
130         if (rlm_sql_select_query(sqlsocket,inst,querystr)){
131                 radlog(L_ERR, "rlm_sql: database query error");
132                 sql_release_socket(inst,sqlsocket);
133                 return 0;
134         }
135
136         ret = rlm_sql_fetch_row(sqlsocket, inst);
137         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
138
139         if (ret) {
140                 DEBUG("rlm_sql: SQL query did not succeed");
141                 sql_release_socket(inst,sqlsocket);
142                 return 0;
143         }
144
145         row = sqlsocket->row;
146         if (row == NULL) {
147                 DEBUG("rlm_sql: SQL query did not return any results");
148                 sql_release_socket(inst,sqlsocket);
149                 return 0;
150         }
151
152         if (row[0] == NULL){
153                 DEBUG("rlm_sql: row[0] returned NULL");
154                 sql_release_socket(inst,sqlsocket);
155                 return 0;
156         }
157         ret = strlen(row[0]);
158         if (ret > freespace){
159                 DEBUG("rlm_sql: sql_xlat:: Insufficient string space");
160                 sql_release_socket(inst,sqlsocket);
161                 return 0;
162         }
163
164         strncpy(out,row[0],ret);
165
166         DEBUG("rlm_sql: - sql_xlat finished");
167
168         sql_release_socket(inst,sqlsocket);
169         return ret;
170 }
171
172 /*
173  *      Translate the SQL queries.
174  */
175 static int sql_escape_func(char *out, int outlen, const char *in)
176 {
177         int len = 0;
178         
179         while (in[0]) {
180                 /*
181                  *  Only one byte left.
182                  */
183                 if (outlen <= 1) {
184                         break;
185                 }
186                 
187                 /*
188                  *      Non-printable characters get replaced with their
189                  *      mime-encoded equivalents.
190                  */
191                 if ((in[0] < 32) ||
192                     strchr("@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: =/", *in) == NULL) {
193                         snprintf(out, outlen, "=%02X", (unsigned char) in[0]);
194                         in++;
195                         out += 3;
196                         outlen -= 3;
197                         len += 3;
198                         continue;
199                 }
200                 
201                 /*
202                  *      Else it's a nice character.
203                  */
204                 *out = *in;
205                 out++;
206                 in++;
207                 outlen--;
208                 len++;
209         }
210         *out = '\0';
211         return len;
212 }
213
214 /*
215  *      Set the SQl user name.
216  */
217 static int sql_set_user(SQL_INST *inst, REQUEST *request, char *sqlusername, const char *username) {
218         VALUE_PAIR *vp=NULL;
219         char tmpuser[MAX_STRING_LEN];
220
221         tmpuser[0]=0;
222         sqlusername[0]=0;
223
224         /* Remove any user attr we added previously */
225         pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
226
227         if (username != NULL) {
228                 strNcpy(tmpuser, username, MAX_STRING_LEN);
229         } else if (strlen(inst->config->query_user)) {
230                 radius_xlat(tmpuser, MAX_STRING_LEN, inst->config->query_user, request, sql_escape_func);
231         } else {
232                 return 0;
233         }
234
235         if (*tmpuser) {
236                 strNcpy(sqlusername, tmpuser, MAX_STRING_LEN * 2);
237                 DEBUG2("sql_set_user:  escaped user --> '%s'", sqlusername);
238                 vp = pairmake("SQL-User-Name", sqlusername, 0);
239                 if (vp == NULL) {
240                         radlog(L_ERR, "%s", librad_errstr);
241                         return -1;
242                 }
243
244                 pairadd(&request->packet->vps, vp);
245                 return 0;
246         }
247         return -1;
248 }
249
250 /*
251  * sql groupcmp function. That way we can do group comparisons (in the users file for example)
252  * with the group memberships reciding in sql
253  * The group membership query should only return one element which is the username. The returned
254  * username will then be checked with the passed check string.
255  */
256
257 static int sql_groupcmp(void *instance, REQUEST *req, VALUE_PAIR *request, VALUE_PAIR *check,
258                         VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs)
259 {
260         SQLSOCK *sqlsocket;
261         SQL_ROW row;
262         SQL_INST *inst=instance;
263         char querystr[MAX_QUERY_LEN];
264         char sqlusername[2 * MAX_STRING_LEN + 10];
265
266         check_pairs = check_pairs;
267         reply_pairs = reply_pairs;
268
269         DEBUG("rlm_sql: - sql_groupcmp");
270         if (!check || !check->strvalue || !check->length){
271                 DEBUG("rlm_sql::sql_groupcmp: Illegal group name");
272                 return 1;
273         }
274         if (req == NULL){
275                 DEBUG("rlm_sql::sql_groupcmp: NULL request");
276                 return 1;
277         }
278         if (inst->config->groupmemb_query[0] == 0)
279                 return 1;
280         /*
281          * Set, escape, and check the user attr here
282          */
283         if (sql_set_user(inst, req, sqlusername, 0) < 0)
284                 return 1;
285         if (!radius_xlat(querystr, sizeof(querystr), inst->config->groupmemb_query, req, NULL)){
286                 radlog(L_ERR, "rlm_sql: xlat failed.");
287                 /* Remove the username we (maybe) added above */
288                 pairdelete(&req->packet->vps, PW_SQL_USER_NAME);
289                 return 1;
290         }
291         /* Remove the username we (maybe) added above */
292         pairdelete(&req->packet->vps, PW_SQL_USER_NAME);
293
294         sqlsocket = sql_get_socket(inst);
295         if (sqlsocket == NULL)
296                 return 1;
297         if ((inst->module->sql_select_query)(sqlsocket,inst->config,querystr) <0){
298                 radlog(L_ERR, "rlm_sql: database query error");
299                 sql_release_socket(inst,sqlsocket);
300                 return 1;
301         }
302         while (rlm_sql_fetch_row(sqlsocket, inst) == 0) {
303                 row = sqlsocket->row;
304                 if (row == NULL)
305                         break;
306                 if (row[0] == NULL){
307                         DEBUG("rlm_sql: row[0] returned NULL");
308                         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
309                         sql_release_socket(inst, sqlsocket);
310                         return 1;
311                 }
312                 if (strcmp(row[0],check->strvalue) == 0){
313                         DEBUG("rlm_sql: - sql_groupcmp finished: User belongs in group %s",(char *)check->strvalue);
314                         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
315                         sql_release_socket(inst, sqlsocket);
316                         return 0;
317                 }
318         }
319
320         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
321         sql_release_socket(inst,sqlsocket);
322
323         DEBUG("rlm_sql: - sql_groupcmp finished: User does not belong in group %s",(char *)check->strvalue);
324
325         return 1;
326 }
327
328
329 static int rlm_sql_instantiate(CONF_SECTION * conf, void **instance) {
330
331         SQL_INST *inst;
332         lt_dlhandle handle;
333         char *xlat_name;
334
335         inst = rad_malloc(sizeof(SQL_INST));
336         memset(inst, 0, sizeof(SQL_INST));
337
338         inst->config = rad_malloc(sizeof(SQL_CONFIG));
339         memset(inst->config, 0, sizeof(SQL_CONFIG));
340
341         /*
342          * If the configuration parameters can't be parsed, then
343          * fail.
344          */
345         if (cf_section_parse(conf, inst->config, module_config) < 0) {
346                 free(inst->config);
347                 free(inst);
348                 return -1;
349         }
350
351         if (inst->config->num_sql_socks > MAX_SQL_SOCKS) {
352                 radlog(L_ERR | L_CONS, "sql_instantiate:  number of sqlsockets cannot exceed MAX_SQL_SOCKS, %d", MAX_SQL_SOCKS);
353                 free(inst->config);
354                 free(inst);
355                 return -1;
356         }
357
358         handle = lt_dlopenext(inst->config->sql_driver);
359         if (handle == NULL) {
360                 radlog(L_ERR, "rlm_sql: Could not link driver %s: %s", inst->config->sql_driver, lt_dlerror());
361                 radlog(L_ERR, "rlm_sql: Make sure it (and all its dependent libraries!) are in the search path of your system's ld.");
362                 return -1;
363         }
364
365         inst->module = (rlm_sql_module_t *) lt_dlsym(handle, inst->config->sql_driver);
366         if (!inst->module) {
367                 radlog(L_ERR, "rlm_sql: Could not link symbol %s: %s", inst->config->sql_driver, lt_dlerror());
368                 return -1;
369         }
370
371         radlog(L_INFO, "rlm_sql: Driver %s loaded and linked", inst->config->sql_driver);
372         radlog(L_INFO, "rlm_sql: Attempting to connect to %s@%s:%s/%s", inst->config->sql_login, inst->config->sql_server, inst->config->sql_port, inst->config->sql_db);
373
374         if (sql_init_socketpool(inst) < 0) {
375                 free(inst->config);
376                 free(inst);
377                 return -1;
378         }
379         xlat_name = cf_section_name2(conf);
380         if (xlat_name == NULL)
381                 xlat_name = cf_section_name1(conf);
382         if (xlat_name){
383                 inst->config->xlat_name = strdup(xlat_name);
384                 xlat_register(xlat_name, sql_xlat, inst);
385         }
386         paircompare_register(PW_SQL_GROUP, PW_USER_NAME, sql_groupcmp, inst);
387
388         *instance = inst;
389
390         return RLM_MODULE_OK;
391 }
392
393 static int rlm_sql_destroy(void) {
394
395         return 0;
396 }
397
398 static int rlm_sql_detach(void *instance) {
399
400         SQL_INST *inst = instance;
401
402         sql_poolfree(inst);
403         if (inst->config->xlat_name)
404                 xlat_unregister(inst->config->xlat_name,sql_xlat);
405         paircompare_unregister(PW_SQL_GROUP, sql_groupcmp);
406         free(inst->config);
407         free(inst);
408
409         return 0;
410 }
411
412
413 static int rlm_sql_authorize(void *instance, REQUEST * request) {
414
415         VALUE_PAIR *check_tmp = NULL;
416         VALUE_PAIR *reply_tmp = NULL;
417         int     found = 0;
418         SQLSOCK *sqlsocket;
419         SQL_INST *inst = instance;
420         char    querystr[MAX_QUERY_LEN];
421
422         /* sqlusername holds the sql escaped username. The original
423          * username is at most MAX_STRING_LEN chars long and
424          * *sql_escape_string doubles its length in the worst case.
425          * Throw in an extra 10 to account for trailing NULs and to have
426          * a safety margin. */
427         char   sqlusername[2 * MAX_STRING_LEN + 10];
428
429         /*
430          *      They MUST have a user name to do SQL authorization.
431          */
432         if ((request->username == NULL) ||
433             (request->username->length == 0)) {
434                 radlog(L_ERR, "zero length username not permitted\n");
435                 return RLM_MODULE_INVALID;
436         }
437
438
439         /*
440          *  After this point, ALL 'return's MUST release the SQL socket!
441          */
442
443         /*
444          * Set, escape, and check the user attr here
445          */
446         if (sql_set_user(inst, request, sqlusername, NULL) < 0)
447                 return RLM_MODULE_FAIL;
448         radius_xlat(querystr, MAX_QUERY_LEN, inst->config->authorize_check_query, request, sql_escape_func);
449
450         sqlsocket = sql_get_socket(inst);
451         if (sqlsocket == NULL) {
452                 /* Remove the username we (maybe) added above */
453                 pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
454                 return(RLM_MODULE_FAIL);
455         }
456
457         found = sql_getvpdata(inst, sqlsocket, &check_tmp, querystr, PW_VP_USERDATA);
458         /*
459          *      Find the entry for the user.
460          */
461         if (found > 0) {
462                 radius_xlat(querystr, MAX_QUERY_LEN, inst->config->authorize_group_check_query, request, sql_escape_func);
463                 sql_getvpdata(inst, sqlsocket, &check_tmp, querystr, PW_VP_GROUPDATA);
464                 radius_xlat(querystr, MAX_QUERY_LEN, inst->config->authorize_reply_query, request, sql_escape_func);
465                 sql_getvpdata(inst, sqlsocket, &reply_tmp, querystr, PW_VP_USERDATA);
466                 radius_xlat(querystr, MAX_QUERY_LEN, inst->config->authorize_group_reply_query, request, sql_escape_func);
467                 sql_getvpdata(inst, sqlsocket, &reply_tmp, querystr, PW_VP_GROUPDATA);
468         } else if (found < 0) {
469                 radlog(L_ERR, "rlm_sql:  SQL query error; rejecting user");
470                 sql_release_socket(inst, sqlsocket);
471                 /* Remove the username we (maybe) added above */
472                 pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
473                 return RLM_MODULE_FAIL;
474
475         } else {
476                 int     gcheck;
477                 
478                 radlog(L_DBG, "rlm_sql: User %s not found", sqlusername);
479
480                 /*
481                  * We didn't find the user in radcheck, so we try looking
482                  * for radgroupcheck entry
483                  */
484                 radius_xlat(querystr, MAX_QUERY_LEN, inst->config->authorize_group_check_query, request, sql_escape_func);
485                 gcheck = sql_getvpdata(inst, sqlsocket, &check_tmp, querystr, PW_VP_GROUPDATA);
486                 radius_xlat(querystr, MAX_QUERY_LEN, inst->config->authorize_group_reply_query, request, sql_escape_func);
487                 sql_getvpdata(inst, sqlsocket, &reply_tmp, querystr, PW_VP_GROUPDATA);
488                 if (gcheck) {
489                         found = 1;
490                 } else {
491                         /*
492                         * We didn't find the user, so we try looking
493                         * for a DEFAULT entry
494                         */
495                         if (sql_set_user(inst, request, sqlusername, "DEFAULT") < 0) {
496                                 sql_release_socket(inst, sqlsocket);
497                                 return RLM_MODULE_FAIL;
498                         }
499                         radius_xlat(querystr, MAX_QUERY_LEN, inst->config->authorize_group_check_query, request, sql_escape_func);
500                         gcheck = sql_getvpdata(inst, sqlsocket, &check_tmp, querystr, PW_VP_GROUPDATA);
501                         radius_xlat(querystr, MAX_QUERY_LEN, inst->config->authorize_group_reply_query, request, sql_escape_func);
502                         gcheck = sql_getvpdata(inst, sqlsocket, &reply_tmp, querystr, PW_VP_GROUPDATA);
503                         if (gcheck)
504                                 found = 1;
505                 }
506         }
507         if (!found) {
508                 radlog(L_DBG, "rlm_sql: DEFAULT not found");
509                 sql_release_socket(inst, sqlsocket);
510                 /* Remove the username we (maybe) added above */
511                 pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
512                 return RLM_MODULE_NOTFOUND;
513         }
514
515         /*
516          * Uncomment these lines for debugging
517          * Recompile, and run 'radiusd -X'
518          */
519
520         /*
521         DEBUG2("rlm_sql:  check items");
522         vp_listdebug(check_tmp);
523         DEBUG2("rlm_sql:  reply items");
524         vp_listdebug(reply_tmp); 
525         */
526
527         if (paircmp(request, request->packet->vps, check_tmp, &reply_tmp) != 0) {
528                 radlog(L_INFO, "rlm_sql: Pairs do not match [%s]", sqlusername);
529                 /* Remove the username we (maybe) added above */
530                 pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
531                 sql_release_socket(inst, sqlsocket);
532                 pairfree(&reply_tmp);
533                 pairfree(&check_tmp);
534                 return RLM_MODULE_NOTFOUND;
535         }
536
537         pairmove(&request->reply->vps, &reply_tmp);
538         pairmove(&request->config_items, &check_tmp);
539         pairfree(&reply_tmp);
540         pairfree(&check_tmp);
541
542         /* Remove the username we (maybe) added above */
543         pairdelete(&request->packet->vps, PW_SQL_USER_NAME);
544         sql_release_socket(inst, sqlsocket);
545
546         return RLM_MODULE_OK;
547 }
548
549 /*
550  *      Accounting: save the account data to our sql table
551  */
552 static int rlm_sql_accounting(void *instance, REQUEST * request) {
553
554         SQLSOCK *sqlsocket = NULL;
555         VALUE_PAIR *pair;
556         SQL_INST *inst = instance;
557         int     numaffected = 0;
558         int     acctstatustype = 0;
559         char    querystr[MAX_QUERY_LEN];
560         char    logstr[MAX_QUERY_LEN];
561         char    sqlusername[MAX_STRING_LEN];
562
563 #ifdef CISCO_ACCOUNTING_HACK
564         int     acctsessiontime = 0;
565 #endif
566
567         memset(querystr, 0, MAX_QUERY_LEN);
568
569         /*
570          * Find the Acct Status Type
571          */
572         if ((pair = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE)) != NULL) {
573                 acctstatustype = pair->lvalue;
574         } else {
575                 radius_xlat(logstr, MAX_QUERY_LEN, "rlm_sql:  packet has no account status type.  [user '%{User-Name}', nas '%{NAS-IP-Address}']", request, sql_escape_func);
576                 radlog(L_ERR, logstr);
577                 return RLM_MODULE_INVALID;
578         }
579
580         switch (acctstatustype) {
581                         /*
582                          * The Terminal server informed us that it was rebooted
583                          * STOP all records from this NAS 
584                          */
585                 case PW_STATUS_ACCOUNTING_ON:
586                 case PW_STATUS_ACCOUNTING_OFF:
587                         radlog(L_INFO, "rlm_sql:  received Acct On/Off packet");
588                         radius_xlat(querystr, MAX_QUERY_LEN, inst->config->accounting_onoff_query, request, sql_escape_func);
589                         query_log(inst, querystr);
590
591                         sqlsocket = sql_get_socket(inst);
592                         if (sqlsocket == NULL)
593                                 return(RLM_MODULE_FAIL);
594                         if (querystr) {
595                                 if (rlm_sql_query(sqlsocket, inst, querystr))
596                                         radlog(L_ERR, "rlm_sql: Couldn't update SQL accounting for Acct On/Off packet - %s", (char *)(inst->module->sql_error)(sqlsocket, inst->config));
597                                 (inst->module->sql_finish_query)(sqlsocket, inst->config);
598                         }
599
600                         break;
601
602                         /*
603                          * Got an update accounting packet
604                          */
605                 case PW_STATUS_ALIVE:
606
607                         /*
608                          * Set, escape, and check the user attr here
609                          */
610                         sql_set_user(inst, request, sqlusername, NULL);
611
612                         radius_xlat(querystr, MAX_QUERY_LEN, inst->config->accounting_update_query, request, sql_escape_func);
613                         query_log(inst, querystr);
614
615                         sqlsocket = sql_get_socket(inst);
616                         if (sqlsocket == NULL)
617                                 return(RLM_MODULE_FAIL);
618                         if (querystr) {
619                                 if (rlm_sql_query(sqlsocket, inst, querystr))
620                                         radlog(L_ERR, "rlm_sql: Couldn't update SQL accounting for ALIVE packet - %s", (char *)(inst->module->sql_error)(sqlsocket, inst->config));
621                                 (inst->module->sql_finish_query)(sqlsocket, inst->config);
622                         }
623
624                         break;
625
626                         /*
627                          * Got accounting start packet
628                          */
629                 case PW_STATUS_START:
630
631                         /*
632                          * Set, escape, and check the user attr here
633                          */
634                         sql_set_user(inst, request, sqlusername, NULL);
635
636                         radius_xlat(querystr, MAX_QUERY_LEN, inst->config->accounting_start_query, request, sql_escape_func);
637                         query_log(inst, querystr);
638
639                         sqlsocket = sql_get_socket(inst);
640                         if (sqlsocket == NULL)
641                                 return(RLM_MODULE_FAIL);
642                         if (querystr) {
643                                 if (rlm_sql_query(sqlsocket, inst, querystr)) {
644                                         radlog(L_ERR, "rlm_sql: Couldn't update SQL accounting" " for START packet - %s", (char *)(inst->module->sql_error)(sqlsocket, inst->config));
645
646                                         /*
647                                          * We failed the insert above.  It's probably because 
648                                          * the stop record came before the start.  We try an
649                                          * our alternate query now (typically an UPDATE)
650                                          */
651                                         radius_xlat(querystr, MAX_QUERY_LEN, inst->config->accounting_start_query_alt, request, sql_escape_func);
652                                         query_log(inst, querystr);
653
654                                         if (querystr) {
655                                                 if (rlm_sql_query(sqlsocket, inst, querystr)) {
656                                                         radlog(L_ERR, "rlm_sql: Couldn't update SQL" "accounting START record - %s", (char *)(inst->module->sql_error)(sqlsocket, inst->config));
657                                                 }
658                                                 (inst->module->sql_finish_query)(sqlsocket, inst->config);
659                                         }
660                                 }
661                                 (inst->module->sql_finish_query)(sqlsocket, inst->config);
662                         }
663                         break;
664
665                         /*
666                          * Got accounting stop packet
667                          */
668                 case PW_STATUS_STOP:
669
670                         /*
671                          * Set, escape, and check the user attr here
672                          */
673                         sql_set_user(inst, request, sqlusername, NULL);
674
675                         radius_xlat(querystr, MAX_QUERY_LEN, inst->config->accounting_stop_query, request, sql_escape_func);
676                         query_log(inst, querystr);
677
678                         sqlsocket = sql_get_socket(inst);
679                         if (sqlsocket == NULL)
680                                 return(RLM_MODULE_FAIL);
681                         if (querystr) {
682                                 if (rlm_sql_query(sqlsocket, inst, querystr)) {
683                                         radlog(L_ERR, "rlm_sql: Couldn't update SQL accounting STOP record - %s", (char *)(inst->module->sql_error)(sqlsocket, inst->config));
684                                 }
685                                 else {
686                                         numaffected = (inst->module->sql_affected_rows)(sqlsocket, inst->config);
687                                         if (numaffected < 1) {
688                                                 /*
689                                                  * If our update above didn't match anything
690                                                  * we assume it's because we haven't seen a 
691                                                  * matching Start record.  So we have to
692                                                  * insert this stop rather than do an update
693                                                  */
694 #ifdef CISCO_ACCOUNTING_HACK
695                                                 /*
696                                                  * If stop but zero session length AND no previous
697                                                  * session found, drop it as in invalid packet
698                                                  * This is to fix CISCO's aaa from filling our
699                                                  * table with bogus crap
700                                                  */
701                                                 if ((pair = pairfind(request->packet->vps, PW_ACCT_SESSION_TIME)) != NULL)
702                                                         acctsessiontime = pair->lvalue;
703         
704                                                 if (acctsessiontime <= 0) {
705                                                         radius_xlat(logstr, MAX_QUERY_LEN, "rlm_sql:  Stop packet with zero session length.  (user '%{User-Name}', nas '%{NAS-IP-Address}')", request, sql_escape_func);
706                                                         radlog(L_ERR, logstr);
707                                                         sql_release_socket(inst, sqlsocket);
708                                                         return RLM_MODULE_NOOP;
709                                                 }
710 #endif
711
712                                                 radius_xlat(querystr, MAX_QUERY_LEN, inst->config->accounting_stop_query_alt, request, sql_escape_func);
713                                                 query_log(inst, querystr);
714
715                                                 if (querystr) {
716                                                         if (rlm_sql_query(sqlsocket, inst, querystr)) {
717                                                                 radlog(L_ERR, "rlm_sql: Couldn't insert SQL accounting STOP record - %s", (char *)(inst->module->sql_error)(sqlsocket, inst->config));
718                                                         }
719                                                         (inst->module->sql_finish_query)(sqlsocket, inst->config);
720                                                 }
721                                         }
722                                 }
723                                 (inst->module->sql_finish_query)(sqlsocket, inst->config);
724                         }
725                         break;
726         }
727
728         sql_release_socket(inst, sqlsocket);
729
730         return RLM_MODULE_OK;
731 }
732
733
734 /*
735  *        See if a user is already logged in. Sets request->simul_count to the
736  *        current session count for this user.
737  * 
738  *        Check twice. If on the first pass the user exceeds his
739  *        max. number of logins, do a second pass and validate all
740  *        logins by querying the terminal server (using eg. SNMP).
741  */
742
743 static int rlm_sql_checksimul(void *instance, REQUEST * request) {
744         SQLSOCK         *sqlsocket;
745         SQL_INST        *inst = instance;
746         SQL_ROW         row;
747         char            querystr[MAX_QUERY_LEN];
748         char            sqlusername[2*MAX_STRING_LEN+10];
749         int             check = 0;
750         uint32_t        ipno = 0;
751         char            *call_num = NULL;
752         VALUE_PAIR      *vp;
753         int             ret;
754         uint32_t        nas_addr = 0;
755         int             nas_port = 0;
756
757         /* If simul_count_query is not defined, we don't do any checking */
758         if (inst->config->simul_count_query[0] == 0) {
759                 return RLM_MODULE_NOOP;
760         }
761
762         if((request->username == NULL) || (request->username->length == 0)) {
763                 radlog(L_ERR, "Zero Length username not permitted\n");
764                 return RLM_MODULE_INVALID;
765         }
766
767
768         if(sql_set_user(inst, request, sqlusername, 0) <0)
769                 return RLM_MODULE_FAIL;
770
771         radius_xlat(querystr, MAX_QUERY_LEN, inst->config->simul_count_query, request, NULL);
772
773         /* initialize the sql socket */
774         sqlsocket = sql_get_socket(inst);
775         if(sqlsocket == NULL)
776                 return RLM_MODULE_FAIL;
777
778         if(rlm_sql_select_query(sqlsocket, inst, querystr)) {
779                 radlog(L_ERR, "sql_checksimul: Database query failed");
780                 sql_release_socket(inst, sqlsocket);
781                 return RLM_MODULE_FAIL;
782         }
783
784         ret = rlm_sql_fetch_row(sqlsocket, inst);
785
786         if (ret == 0) {
787                 row = sqlsocket->row;
788         }
789
790         if (ret) {
791                 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
792                 sql_release_socket(inst, sqlsocket);
793                 return RLM_MODULE_FAIL;
794         }
795
796         if (row == NULL) {
797                 (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
798                 sql_release_socket(inst, sqlsocket);
799                 return RLM_MODULE_FAIL;
800         }
801
802         request->simul_count = atoi(row[0]);
803         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
804
805         if(request->simul_count < request->simul_max) {
806                 sql_release_socket(inst, sqlsocket);
807                 return RLM_MODULE_OK;
808         }
809
810         /* Looks like too many sessions, so lets start verifying them */
811
812         if (inst->config->simul_verify_query[0] == 0) {
813                 /* No verify query defined, so skip verify step and rely on count query only */
814                 sql_release_socket(inst, sqlsocket);
815                 return RLM_MODULE_OK;
816         }
817
818         radius_xlat(querystr, MAX_QUERY_LEN, inst->config->simul_verify_query, request, NULL);
819         if(rlm_sql_select_query(sqlsocket, inst, querystr)) {
820                 radlog(L_ERR, "sql_checksimul: Database query error");
821                 sql_release_socket(inst, sqlsocket);
822                 return RLM_MODULE_FAIL;
823         }
824
825         /*
826          *      Setup some stuff, like for MPP detection.
827          */
828         request->simul_count = 0;
829
830         if ((vp = pairfind(request->packet->vps, PW_FRAMED_IP_ADDRESS)) != NULL)
831                 ipno = vp->lvalue;
832         if ((vp = pairfind(request->packet->vps, PW_CALLING_STATION_ID)) != NULL)
833                 call_num = vp->strvalue;        
834
835
836         while (rlm_sql_fetch_row(sqlsocket, inst) == 0) {
837                 row = sqlsocket->row;
838                 if (row == NULL)
839                         break;
840                 if (!row[2]){
841                         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
842                         sql_release_socket(inst, sqlsocket);
843                         DEBUG("rlm_sql: Cannot zap stale entry. No username present in entry.");
844                         return RLM_MODULE_FAIL;
845                 }
846                 if (!row[1]){
847                         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
848                         sql_release_socket(inst, sqlsocket);
849                         DEBUG("rlm_sql: Cannot zap stale entry. No session id in entry.");
850                         return RLM_MODULE_FAIL;
851                 }
852                 if (row[3])
853                         nas_addr = inet_addr(row[3]);
854                 if (row[4])
855                         nas_port = atoi(row[4]);
856
857                 check = rad_check_ts(nas_addr, nas_port, row[2], row[1]);
858
859                 /*
860                  *      Failed to check the terminal server for
861                  *      duplicate logins: Return an error.
862                  */
863                 if (check < 0) {
864                         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
865                         sql_release_socket(inst, sqlsocket);
866                         DEBUG("rlm_sql: rad_check_ts() failed.");
867                         return RLM_MODULE_FAIL;
868                 }
869
870                 if(check == 1) {
871                         ++request->simul_count;
872
873                         /*
874                          *      Does it look like a MPP attempt?
875                          */
876                         if (row[5] && ipno && inet_addr(row[5]) == ipno)
877                                 request->simul_mpp = 2;
878                         else if (row[6] && call_num &&
879                                 !strncmp(row[6],call_num,16))
880                                 request->simul_mpp = 2;
881                 }
882                 else {
883                         /*
884                          *      Stale record - zap it.
885                          */
886                         uint32_t framed_addr = 0;
887                         char proto = 'P';
888
889                         if (row[5])
890                                 framed_addr = inet_addr(row[5]);
891                         if (row[7])
892                                 if (strcmp(row[7],"SLIP") == 0)
893                                         proto = 'S';
894
895                         session_zap(request->packet->sockfd,
896                         nas_addr,nas_port,row[2],row[1],
897                         framed_addr, proto,0);
898
899                 }
900         }
901
902         (inst->module->sql_finish_select_query)(sqlsocket, inst->config);
903         sql_release_socket(inst, sqlsocket);
904
905         /* The Auth module apparently looks at request->simul_count, not the return value
906            of this module when deciding to deny a call for too many sessions */
907         return RLM_MODULE_OK;
908
909 }
910
911 /* globally exported name */
912 module_t rlm_sql = {
913         "SQL",
914         RLM_TYPE_THREAD_SAFE,   /* type: reserved */
915         rlm_sql_init,           /* initialization */
916         rlm_sql_instantiate,    /* instantiation */
917         {
918                 NULL,                   /* authentication */
919                 rlm_sql_authorize,      /* authorization */
920                 NULL,                   /* preaccounting */
921                 rlm_sql_accounting,     /* accounting */
922                 rlm_sql_checksimul,     /* checksimul */
923                 NULL,                   /* pre-proxy */
924                 NULL,                   /* post-proxy */
925                 NULL                    /* post-auth */
926         },
927         rlm_sql_detach,         /* detach */
928         rlm_sql_destroy,        /* destroy */
929 };