Forgot to clean up mutexes, semaphores and conditional variables on sql_poolfree()
[freeradius.git] / src / modules / rlm_sql / sql.c
1 /*
2  *  sql.c               rlm_sql - FreeRADIUS SQL Module
3  *              Main code directly taken from ICRADIUS
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
27 #include        <sys/types.h>
28 #include        <sys/socket.h>
29 #include        <sys/time.h>
30 #include        <sys/file.h>
31 #include        <string.h>
32 #include        <sys/stat.h>
33 #include        <netinet/in.h>
34
35 #include        <stdio.h>
36 #include        <stdlib.h>
37 #include        <netdb.h>
38 #include        <pwd.h>
39 #include        <time.h>
40 #include        <ctype.h>
41 #include        <unistd.h>
42 #include        <signal.h>
43 #include        <errno.h>
44 #include        <sys/wait.h>
45
46 #if HAVE_PTHREAD_H
47 #include        <pthread.h>
48 #endif
49
50 #include        "radiusd.h"
51 #include        "conffile.h"
52 #include        "rlm_sql.h"
53
54 /*************************************************************************
55  *
56  *      Function: sql_init_socket
57  *
58  *      Purpose: Connect to the sql server
59  *
60  *************************************************************************/
61 int sql_init_socketpool(SQL_INST *inst) {
62
63         SQLSOCK *sqlsocket;
64         int     i;
65
66         inst->used = 0;
67         inst->sqlpool = NULL;
68
69         for (i = 0; i < inst->config->num_sql_socks; i++) {
70                 if ((sqlsocket = sql_create_socket(inst)) == NULL) {
71                         radlog(L_CONS | L_ERR, "rlm_sql:  Failed to connect sqlsocket %d", i);
72                         return -1;
73                 } else {
74                         sqlsocket->id = i;
75 #if HAVE_PTHREAD_H
76                         sqlsocket->semaphore = (sem_t *)malloc(sizeof(sem_t));
77                         sem_init(sqlsocket->semaphore, 0, SQLSOCK_UNLOCKED);
78 #else
79                         sqlsocket->in_use = 0;
80 #endif
81                         sqlsocket->next = inst->sqlpool;
82                         inst->sqlpool = sqlsocket;
83                 }
84         }
85
86         return 1;
87 }
88
89 /*************************************************************************
90  *
91  *     Function: sql_poolfree
92  *
93  *     Purpose: Clean up and free sql pool
94  *
95  *************************************************************************/
96 void sql_poolfree(SQL_INST *inst) {
97
98         SQLSOCK *cur;
99
100         for (cur = inst->sqlpool; cur; cur = cur->next) {
101                 sql_close_socket(cur);
102         }
103 #if HAVE_PTHREAD_H
104         pthread_mutex_destroy(inst->lock);
105         pthread_cond_destroy(inst->notfull);
106 #endif
107 }
108
109
110 /*************************************************************************
111  *
112  *      Function: sql_close_socket
113  *
114  *      Purpose: Close and free a sql sqlsocket
115  *
116  *************************************************************************/
117 int sql_close_socket(SQLSOCK *sqlsocket) {
118
119         radlog(L_DBG,"rlm_sql: Closing sqlsocket %d", sqlsocket->id);
120         sql_close(sqlsocket);
121 #if HAVE_PTHREAD_H
122         sem_destroy(sqlsocket->semaphore);
123 #endif
124         free(sqlsocket);
125         return 1;
126 }
127
128
129 /*************************************************************************
130  *
131  *      Function: sql_get_socket
132  *
133  *      Purpose: Return a SQL sqlsocket from the connection pool           
134  *
135  *************************************************************************/
136 SQLSOCK *sql_get_socket(SQL_INST *inst) {
137
138
139         SQLSOCK *cur;
140
141 #if HAVE_PTHREAD_H
142         pthread_mutex_lock(inst->lock);
143 #endif
144         while (inst->used == inst->config->num_sql_socks) {
145                 radlog(L_DBG, "rlm_sql: Waiting for open sql socket");
146 #if HAVE_PTHREAD_H
147                 pthread_cond_wait(inst->notfull, inst->lock);
148 #else
149                 /* FIXME: Subsecond sleep needed here */
150                 sleep(1);
151 #endif
152         }
153
154         for (cur = inst->sqlpool; cur; cur = cur->next) {
155 #if HAVE_PTHREAD_H
156                 if (sem_trywait(cur->semaphore) == 0) {
157 #else
158                 if (cur->in_use == SQLSOCK_UNLOCKED) {
159 #endif
160                         (inst->used)++;
161 #if HAVE_PTHREAD_H
162                         pthread_mutex_unlock(inst->lock);
163 #else
164                         cur->in_use = SQLSOCK_LOCKED;
165 #endif
166                         radlog(L_DBG,"rlm_sql: Reserved sql socket id: %d", cur->id);
167                         return cur;
168                 }
169         }
170
171 #if HAVE_PTHREAD_H
172         pthread_mutex_unlock(inst->lock);
173 #endif
174
175         /* Should never get here, but what the hey */
176         return NULL;
177 }
178
179 /*************************************************************************
180  *
181  *      Function: sql_release_socket
182  *
183  *      Purpose: Frees a SQL sqlsocket back to the connection pool           
184  *
185  *************************************************************************/
186 int sql_release_socket(SQL_INST *inst, SQLSOCK *sqlsocket) {
187
188 #if HAVE_PTHREAD_H
189         pthread_mutex_lock(inst->lock);
190 #endif
191         (inst->used)--;
192 #if HAVE_PTHREAD_H
193         sem_post(sqlsocket->semaphore);
194 #else
195         sqlsocket->in_use = 0;
196 #endif
197
198         radlog(L_DBG,"rlm_sql: Released sql socket id: %d", sqlsocket->id);
199
200 #if HAVE_PTHREAD_H
201         pthread_mutex_unlock(inst->lock);
202         pthread_cond_signal(inst->notfull);
203 #endif
204
205         return 1;
206 }
207
208
209 /*************************************************************************
210  *
211  *      Function: sql_save_acct
212  *
213  *      Purpose: Write data from the sqlrecord structure to the database
214  *
215  *************************************************************************/
216
217 int sql_save_acct(SQL_INST *inst, SQLSOCK *sqlsocket, SQLACCTREC *sqlrecord) {
218
219         char    querystr[2048];
220         FILE   *sqlfile=0;
221         int     num = 0;
222         int                     acctunique = 0;
223
224 #ifdef NT_DOMAIN_HACK
225         char   *ptr;
226         char    newname[AUTH_STRING_LEN];
227 #endif
228
229         acctunique = strlen(sqlrecord->AcctUniqueId);
230
231         if(inst->config->sqltrace) {
232                 if ((sqlfile = fopen(inst->config->tracefile, "a")) == (FILE *) NULL) {
233                         radlog(L_ERR, "rlm_sql: Couldn't open file %s", inst->config->tracefile);
234                 } else {
235 #if defined(F_LOCK) && !defined(BSD)
236                         (void) lockf((int) sqlfile, (int) F_LOCK, (off_t) SQL_LOCK_LEN);
237 #else
238                         (void) flock(sqlfile, SQL_LOCK_EX);
239 #endif
240                 }
241         }
242
243 #ifdef NT_DOMAIN_HACK
244         /*
245          *      Windows NT machines often authenticate themselves as
246          *      NT_DOMAIN\username. Try to be smart about this.
247          *
248          *      FIXME: should we handle this as a REALM ?
249          */
250         if ((ptr = strchr(sqlrecord->UserName, '\\')) != NULL) {
251                 strncpy(newname, ptr + 1, sizeof(newname));
252                 newname[sizeof(newname) - 1] = 0;
253                 strcpy(sqlrecord->UserName, newname);
254         }
255 #endif /*
256                           * NT_DOMAIN_HACK 
257                           */
258
259         if (sqlrecord->AcctStatusTypeId == PW_STATUS_ACCOUNTING_ON ||
260                         sqlrecord->AcctStatusTypeId == PW_STATUS_ACCOUNTING_OFF) {
261                 radlog(L_INFO, "rlm_sql:  Portmaster %s rebooted at %s", sqlrecord->NASIPAddress,
262                                          sqlrecord->AcctTimeStamp);
263
264                 /*
265                  * The Terminal server informed us that it was rebooted
266                  * * STOP all records from this NAS 
267                  */
268
269                 sprintf(querystr,
270                                                 "UPDATE %s SET AcctStopTime='%s', AcctSessionTime=unix_timestamp('%s') - unix_timestamp(AcctStartTime), AcctTerminateCause='%s', AcctStopDelay = %ld WHERE AcctSessionTime=0 AND AcctStopTime=0 AND NASIPAddress= '%s' AND AcctStartTime <= '%s'",
271                                                 inst->config->sql_acct_table, sqlrecord->AcctTimeStamp,
272                                                 sqlrecord->AcctTimeStamp, sqlrecord->AcctTerminateCause,
273                                                 sqlrecord->AcctDelayTime, sqlrecord->NASIPAddress,
274                                                 sqlrecord->AcctTimeStamp);
275
276                 if (sql_query(inst, sqlsocket, querystr) < 0)
277                         radlog(L_ERR, "rlm_sql: Couldn't update SQL accounting after NAS reboot - %s",
278                                                  sql_error(sqlsocket));
279                 sql_finish_query(sqlsocket);
280
281                 if (sqlfile) {
282                         fputs(querystr, sqlfile);
283                         fputs(";\n", sqlfile);
284                         fclose(sqlfile);
285                 }
286                 return 0;
287         }
288
289         if (sqlrecord->AcctStatusTypeId == PW_STATUS_ALIVE) {
290                 /* 
291                  * Use acct unique session identifier if present
292                  */
293                 if(acctunique) { 
294                         sprintf(querystr, "UPDATE %s SET FramedIPAddress = '%s' WHERE AcctUniqueId = '%s'",
295                                                         inst->config->sql_acct_table, sqlrecord->FramedIPAddress,
296                                                         sqlrecord->AcctUniqueId);
297
298                 } else {
299                         sprintf(querystr, "UPDATE %s SET FramedIPAddress = '%s' WHERE AcctSessionId = '%s' AND UserName = '%s' AND NASIPAddress= '%s'",
300                                                         inst->config->sql_acct_table, sqlrecord->FramedIPAddress,
301                                                         sqlrecord->AcctSessionId, sqlrecord->UserName,
302                                                         sqlrecord->NASIPAddress);
303                 }
304
305                 if (sql_query(inst, sqlsocket, querystr) < 0)
306                         radlog(L_ERR, "rlm_sql: Couldn't update SQL accounting for ALIVE packet - %s",
307                                                  sql_error(sqlsocket));
308                 sql_finish_query(sqlsocket);
309
310                 if (sqlfile) {
311                         fputs(querystr, sqlfile);
312                         fputs(";\n", sqlfile);
313                         fclose(sqlfile);
314                 }
315                 return 0;
316         }
317
318         /*
319          * Got start record 
320          */
321         if (sqlrecord->AcctStatusTypeId == PW_STATUS_START) {
322
323                 /*
324                  * Insert new record with blank stop time until stop record is got 
325                  */
326                 snprintf(querystr, 2048,
327                                                  "INSERT INTO %s VALUES (0, '%s', '%s', '%s', '%s', '%s', %ld, '%s', '%s', 0, 0, '%s', '%s', '', 0, 0, '%s', '%s', '', '%s', '%s', '%s', %ld, 0)",
328                                                  inst->config->sql_acct_table, sqlrecord->AcctSessionId, 
329                                                  sqlrecord->AcctUniqueId, sqlrecord->UserName, 
330                                                  sqlrecord->Realm, sqlrecord->NASIPAddress,
331                                                  sqlrecord->NASPortId, sqlrecord->NASPortType,
332                                                  sqlrecord->AcctTimeStamp, sqlrecord->AcctAuthentic,
333                                                  sqlrecord->ConnectInfo, sqlrecord->CalledStationId,
334                                                  sqlrecord->CallingStationId, sqlrecord->ServiceType,
335                                                  sqlrecord->FramedProtocol, sqlrecord->FramedIPAddress,
336                                                  sqlrecord->AcctDelayTime);
337
338                 if (sql_query(inst, sqlsocket, querystr) < 0) {
339                         radlog(L_ERR, "rlm_sql: Couldn't insert SQL accounting START record - %s",
340                                                          sql_error(sqlsocket));
341
342                         /*
343                          * We failed the insert above.  It's probably because 
344                          * the stop record came before the start.  We try an
345                          * update here to be sure
346                          */
347                         if(acctunique) {
348                                 snprintf(querystr, 2048, "UPDATE %s SET AcctStartTime = '%s', AcctStartDelay = %ld, ConnectInfo_start = '%s' WHERE AcctUniqueId = '%s'",
349                                                                  inst->config->sql_acct_table, sqlrecord->AcctTimeStamp,
350                                                                  sqlrecord->AcctDelayTime, sqlrecord->ConnectInfo,
351                                                                  sqlrecord->AcctUniqueId);
352                         } else {
353                                 snprintf(querystr, 2048, "UPDATE %s SET AcctStartTime = '%s', AcctStartDelay = %ld, ConnectInfo_start = '%s' WHERE AcctSessionId = '%s' AND UserName = '%s' AND NASIPAddress = '%s'",
354                                                                  inst->config->sql_acct_table, sqlrecord->AcctTimeStamp,
355                                                                  sqlrecord->AcctDelayTime, sqlrecord->ConnectInfo,
356                                                                  sqlrecord->AcctSessionId, sqlrecord->UserName, 
357                                                                  sqlrecord->NASIPAddress);
358                         }
359                         if (sql_query(inst, sqlsocket, querystr) < 0)
360                                 radlog(L_ERR, "rlm_sql: Couldn't update SQL accounting START record - %s",
361                                                          sql_error(sqlsocket));
362
363                 } 
364                 sql_finish_query(sqlsocket);
365
366                 /*
367                  * Got stop record 
368                  */
369         } else {
370
371                 /*
372                  * Set stop time on matching record with start time 
373                  */
374                 if(acctunique) {
375                         snprintf(querystr, 2048,
376                                                          "UPDATE %s SET AcctStopTime = '%s', AcctSessionTime = '%lu', AcctInputOctets = '%lu', AcctOutputOctets = '%lu', AcctTerminateCause = '%s', AcctStopDelay = %ld, ConnectInfo_stop = '%s' WHERE AcctUniqueId = '%s'",
377                                                          inst->config->sql_acct_table, sqlrecord->AcctTimeStamp,
378                                                          sqlrecord->AcctSessionTime, sqlrecord->AcctInputOctets,
379                                                          sqlrecord->AcctOutputOctets, sqlrecord->AcctTerminateCause,
380                                                          sqlrecord->AcctDelayTime, sqlrecord->ConnectInfo,
381                                                          sqlrecord->AcctUniqueId);
382
383                 } else {
384                         snprintf(querystr, 2048,
385                                                          "UPDATE %s SET AcctStopTime = '%s', AcctSessionTime = '%lu', AcctInputOctets = '%lu', AcctOutputOctets = '%lu', AcctTerminateCause = '%s', AcctStopDelay = %ld, ConnectInfo_stop = '%s' WHERE AcctSessionId = '%s' AND UserName = '%s' AND NASIPAddress = '%s'",
386                                                          inst->config->sql_acct_table, sqlrecord->AcctTimeStamp,
387                                                          sqlrecord->AcctSessionTime, sqlrecord->AcctInputOctets,
388                                                          sqlrecord->AcctOutputOctets, sqlrecord->AcctTerminateCause,
389                                                          sqlrecord->AcctDelayTime, sqlrecord->ConnectInfo,
390                                                          sqlrecord->AcctSessionId, sqlrecord->UserName, 
391                                                          sqlrecord->NASIPAddress);
392                 }
393
394
395                 if (sql_query(inst, sqlsocket, querystr) < 0)
396                         radlog(L_ERR, "rlm_sql: Couldn't update SQL accounting STOP record - %s",
397                                                  sql_error(sqlsocket));
398                 sql_finish_query(sqlsocket);
399
400
401                 /* 
402                  * If our update above didn't match anything
403                  * we assume it's because we haven't seen a 
404                  * matching Start record.  So we have to
405                  * insert this stop rather than do an update
406                  */
407                 num = sql_affected_rows(sqlsocket);
408                 if(num < 1) {
409
410 #ifdef CISCO_ACCOUNTING_HACK
411                         /*
412                          * If stop but zero session length AND no previous 
413                          * session found, drop it as in invalid packet 
414                          * This is to fix CISCO's aaa from filling our 
415                          * table with bogus crap 
416                          */
417                         if (sqlrecord->AcctSessionTime <= 0) {
418                                 radlog(L_ERR, "rlm_sql: Invalid STOP record. [%s] STOP record but zero session length? (nas %s)",
419                                                          sqlrecord->UserName, sqlrecord->NASIPAddress);
420                                 return 0;
421                         }
422 #endif
423
424                         /*
425                          * Insert record with no start time until matching start record comes 
426                          */
427                         snprintf(querystr, 2048,
428                                                          "INSERT INTO %s VALUES (0, '%s', '%s', '%s', '%s', '%s', %ld, '%s', 0, '%s', '%lu', '%s', '', '%s', '%lu', '%lu', '%s', '%s', '%s', '%s', '%s', '%s', 0, %ld)",
429                                                          inst->config->sql_acct_table, sqlrecord->AcctSessionId,
430                                                          sqlrecord->AcctUniqueId, sqlrecord->UserName, 
431                                                          sqlrecord->Realm, sqlrecord->NASIPAddress,
432                                                          sqlrecord->NASPortId, sqlrecord->NASPortType,
433                                                          sqlrecord->AcctTimeStamp, sqlrecord->AcctSessionTime,
434                                                          sqlrecord->AcctAuthentic, sqlrecord->ConnectInfo,
435                                                          sqlrecord->AcctInputOctets, sqlrecord->AcctOutputOctets,
436                                                          sqlrecord->CalledStationId, sqlrecord->CallingStationId,
437                                                          sqlrecord->AcctTerminateCause, sqlrecord->ServiceType,
438                                                          sqlrecord->FramedProtocol, sqlrecord->FramedIPAddress,
439                                                          sqlrecord->AcctDelayTime);
440
441                         if (sql_query(inst, sqlsocket, querystr) < 0)
442                                 radlog(L_ERR, "rlm_sql: Couldn't insert SQL accounting STOP record - %s",
443                                                          sql_error(sqlsocket));
444                         sql_finish_query(sqlsocket);
445                 }
446
447         }
448         if (sqlfile) {
449                 fputs(querystr, sqlfile);
450                 fputs(";\n", sqlfile);
451                 fflush(sqlfile);
452                 fclose(sqlfile);
453         }
454
455         return 0;
456
457 }
458
459
460 /*************************************************************************
461  *
462  *      Function: sql_userparse
463  *
464  *      Purpose: Read entries from the database and fill VALUE_PAIR structures
465  *
466  *************************************************************************/
467 int sql_userparse(VALUE_PAIR ** first_pair, SQL_ROW row, int mode) {
468
469         DICT_ATTR *attr;
470         VALUE_PAIR *pair, *check;
471
472
473         if ((attr = dict_attrbyname(row[2])) == (DICT_ATTR *) NULL) {
474                 radlog(L_ERR | L_CONS, "rlm_sql: unknown attribute %s", row[2]);
475                 return (-1);
476         }
477
478         /*
479          * If attribute is already there, skip it because we checked usercheck first 
480          * and we want user settings to over ride group settings 
481          */
482         if ((check = pairfind(*first_pair, attr->attr)) != NULL &&
483 #if defined( BINARY_FILTERS )
484                         attr->type != PW_TYPE_ABINARY &&
485 #endif
486                         mode == PW_VP_GROUPDATA) return 0;
487
488         pair = pairmake(row[2], row[3], T_OP_CMP_EQ);
489         pairadd(first_pair, pair);
490
491         return 0;
492 }
493
494
495 /*************************************************************************
496  *
497  *      Function: sql_getvpdata
498  *
499  *      Purpose: Get any group check or reply pairs
500  *
501  *************************************************************************/
502 int sql_getvpdata(SQL_INST *inst, SQLSOCK *sqlsocket, char *table, VALUE_PAIR ** vp, char *user, int mode)
503 {
504
505         char    querystr[256];
506         char    authstr[256];
507         char    username[AUTH_STRING_LEN * 2 + 1];
508         SQL_ROW row;
509         int     rows;
510         int     length;
511
512         if (strlen(user) > AUTH_STRING_LEN)
513                 length = AUTH_STRING_LEN;
514         else
515                 length = strlen(user);
516
517         /*
518          * FIXME CHECK user for weird charactors!! 
519          */
520         sql_escape_string(username, user, length);
521
522         if (mode == PW_VP_USERDATA) {
523                 if (inst->config->sensitiveusername)
524                         sprintf(authstr, "STRCMP(Username, '%s') = 0", username);
525                 else
526                         sprintf(authstr, "UserName = '%s'", username);
527                 sprintf(querystr, "SELECT * FROM %s WHERE %s ORDER BY id", table,
528                                                 authstr);
529         } else if (mode == PW_VP_GROUPDATA) {
530                 if (inst->config->sensitiveusername)
531                         sprintf(authstr, "STRCMP(%s.Username, '%s') = 0",
532                                                         inst->config->sql_usergroup_table, username);
533                 else
534                         sprintf(authstr, "%s.UserName = '%s'", inst->config->sql_usergroup_table,
535                                                         username);
536                 sprintf(querystr,
537                                                 "SELECT %s.* FROM %s, %s WHERE %s AND %s.GroupName = %s.GroupName ORDER BY %s.id",
538                                                 table, table, inst->config->sql_usergroup_table, authstr,
539                                                 inst->config->sql_usergroup_table, table, table);
540         } else if (mode == PW_VP_REALMDATA)
541                 sprintf(querystr,
542                                                 "SELECT %s.* FROM %s, %s WHERE %s.RealmName = '%s' AND %s.GroupName = %s.GroupName ORDER BY %s.id",
543                                                 table, table, inst->config->sql_realmgroup_table,
544                                                 inst->config->sql_realmgroup_table, username,
545                                                 inst->config->sql_realmgroup_table, table, table);
546         sql_select_query(inst, sqlsocket, querystr);
547         rows = sql_num_rows(sqlsocket);
548         while ((row = sql_fetch_row(sqlsocket))) {
549
550                 if (sql_userparse(vp, row, mode) != 0) {
551                         radlog(L_ERR | L_CONS, "rlm_sql:  Error getting data from database");
552                         sql_finish_select_query(sqlsocket);
553                         return -1;
554                 }
555         }
556         sql_finish_select_query(sqlsocket);
557
558         return rows;
559
560 }
561
562
563 static int got_alrm;
564 static void
565 alrm_handler()
566 {
567         got_alrm = 1;
568 }
569
570 /*************************************************************************
571  *
572  *      Function: sql_check_ts
573  *
574  *      Purpose: Checks the terminal server for a spacific login entry
575  *
576  *************************************************************************/
577 static int sql_check_ts(SQL_ROW row) {
578
579         int     pid, st, e;
580         int     n;
581         NAS    *nas;
582         char    session_id[12];
583         char   *s;
584         void    (*handler) (int);
585
586         /*
587          *      Find NAS type.
588          */
589         if ((nas = nas_find(ip_addr(row[4]))) == NULL) {
590                 radlog(L_ERR, "rlm_sql:  unknown NAS [%s]", row[4]);
591                 return -1;
592         }
593
594         /*
595          *      Fork.
596          */
597         handler = signal(SIGCHLD, SIG_DFL);
598         if ((pid = fork()) < 0) {
599                 radlog(L_ERR, "rlm_sql: fork: %s", strerror(errno));
600                 signal(SIGCHLD, handler);
601                 return -1;
602         }
603
604         if (pid > 0) {
605                 /*
606                  *      Parent - Wait for checkrad to terminate.
607                  *      We timeout in 10 seconds.
608                  */
609                 got_alrm = 0;
610                 signal(SIGALRM, alrm_handler);
611                 alarm(10);
612                 while ((e = waitpid(pid, &st, 0)) != pid)
613                         if (e < 0 && (errno != EINTR || got_alrm))
614                                 break;
615                 alarm(0);
616                 signal(SIGCHLD, handler);
617                 if (got_alrm) {
618                         kill(pid, SIGTERM);
619                         sleep(1);
620                         kill(pid, SIGKILL);
621                         radlog(L_ERR, "rlm_sql:  Check-TS: timeout waiting for checkrad");
622                         return 2;
623                 }
624                 if (e < 0) {
625                         radlog(L_ERR, "rlm_sql:  Check-TS: unknown error in waitpid()");
626                         return 2;
627                 }
628                 return WEXITSTATUS(st);
629         }
630
631         /*
632          *      Child - exec checklogin with the right parameters.
633          */
634         for (n = 32; n >= 3; n--)
635                 close(n);
636
637         sprintf(session_id, "%.8s", row[1]);
638
639         s = CHECKRAD2;
640         execl(CHECKRAD2, "checkrad", nas->nastype, row[4], row[5],
641                                 row[2], session_id, NULL);
642         if (errno == ENOENT) {
643                 s = CHECKRAD1;
644                 execl(CHECKRAD1, "checklogin", nas->nastype, row[4], row[5],
645                                         row[2], session_id, NULL);
646         }
647         radlog(L_ERR, "rlm_sql:  Check-TS: exec %s: %s", s, strerror(errno));
648
649         /*
650          *      Exit - 2 means "some error occured".
651          */
652         exit(2);
653
654 }
655
656
657 /*************************************************************************
658  *
659  *      Function: sql_check_multi
660  *
661  *      Purpose: Check radius accounting for duplicate logins
662  *
663  *************************************************************************/
664 int sql_check_multi(SQL_INST *inst, SQLSOCK *sqlsocket, char *name, VALUE_PAIR * request, int maxsimul) {
665
666         char    querystr[256];
667         char    authstr[256];
668         VALUE_PAIR *fra;
669         SQL_ROW row;
670         int     count = 0;
671         uint32_t ipno = 0;
672         int     mpp = 1;
673
674         if (inst->config->sensitiveusername)
675                 sprintf(authstr, "STRCMP(UserName, '%s') = 0", name);
676         else
677                 sprintf(authstr, "UserName = '%s'", name);
678         sprintf(querystr, "SELECT COUNT(*) FROM %s WHERE %s AND AcctStopTime = 0",
679                                         inst->config->sql_acct_table, authstr);
680         sql_select_query(inst, sqlsocket, querystr);
681         row = sql_fetch_row(sqlsocket);
682         count = atoi(row[0]);
683         sql_finish_select_query(sqlsocket);
684
685         if (count < maxsimul)
686                 return 0;
687
688         /*
689          * *      Setup some stuff, like for MPP detection.
690          */
691         if ((fra = pairfind(request, PW_FRAMED_IP_ADDRESS)) != NULL)
692                 ipno = htonl(fra->lvalue);
693
694         count = 0;
695         sprintf(querystr, "SELECT * FROM %s WHERE %s AND AcctStopTime = 0",
696                                         inst->config->sql_acct_table, authstr);
697         sql_select_query(inst, sqlsocket, querystr);
698         while ((row = sql_fetch_row(sqlsocket))) {
699                 int     check = sql_check_ts(row);
700
701                 if (check == 1) {
702                         count++;
703
704                         if (ipno && atoi(row[19]) == ipno)
705                                 mpp = 2;
706
707                 } else if (check == 2)
708                         radlog(L_ERR, "rlm_sql:  Problem with checkrad [%s] (from nas %s)", name, row[4]);
709                 else {
710                         /*
711                          *      False record - zap it
712                          */
713
714                         if (inst->config->deletestalesessions) {
715                                 SQLSOCK *sqlsocket1;
716
717                                 radlog(L_ERR, "rlm_sql:  Deleteing stale session [%s] (from nas %s/%s)", row[2],
718                                                          row[4], row[5]);
719                                 sqlsocket1 = sql_get_socket(inst);
720                                 sprintf(querystr, "DELETE FROM %s WHERE RadAcctId = '%s'",
721                                                                 inst->config->sql_acct_table, row[0]);
722                                 sql_query(inst, sqlsocket1, querystr);
723                                 sql_finish_query(sqlsocket1);
724                                 sql_release_socket(inst, sqlsocket1);
725                         }
726                 }
727         }
728         sql_finish_select_query(sqlsocket);
729
730         return (count < maxsimul) ? 0 : mpp;
731
732 }