Remove knowledge of struct radutmp from rad_check_ts() as a first step toward
[freeradius.git] / src / main / radutmp.c
1 /*
2  * radutmp.c    Radius session management.
3  *
4  * Version:     $Id$
5  *
6  */
7
8 static const char rcsid[] =
9 "$Id$";
10
11 #include        "autoconf.h"
12
13 #include        <sys/file.h>
14 #include        <sys/stat.h>
15
16 #include        <fcntl.h>
17 #include        <stdlib.h>
18 #include        <string.h>
19 #include        <ctype.h>
20 #include        <signal.h>
21
22 #if HAVE_MALLOC_H
23 #  include <malloc.h>
24 #endif
25
26 #if HAVE_SYS_WAIT_H
27 # include <sys/wait.h>
28 #endif
29 #ifndef WEXITSTATUS
30 # define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8)
31 #endif
32 #ifndef WIFEXITED
33 # define WIFEXITED(stat_val) (((stat_val) & 255) == 0)
34 #endif
35
36 #include        "radiusd.h"
37 #include        "radutmp.h"
38
39 static const char porttypes[] = "ASITX";
40
41 #define LOCK_LEN sizeof(struct radutmp)
42
43 /*
44  *      used for caching radutmp lookups.
45  */
46 typedef struct nas_port {
47         uint32_t                nasaddr;
48         int                     port;
49         off_t                   offset;
50         struct nas_port         *next;
51 } NAS_PORT;
52 static NAS_PORT *nas_port_list = NULL;
53
54
55 /*
56  *      Lookup a NAS_PORT in the nas_port_list
57  */
58 static NAS_PORT *nas_port_find(uint32_t nasaddr, int port)
59 {
60         NAS_PORT        *cl;
61
62         for(cl = nas_port_list; cl; cl = cl->next)
63                 if (nasaddr == cl->nasaddr &&
64                         port == cl->port)
65                         break;
66         return cl;
67 }
68
69
70 /*
71  *      Zap a user, or all users on a NAS, from the radutmp file.
72  */
73 int radutmp_zap(uint32_t nasaddr, int port, char *user, time_t t)
74 {
75         struct radutmp  u;
76         FILE            *fp;
77         int             fd;
78
79         if (t == 0) time(&t);
80         fp = fopen(RADWTMP, "a");
81
82         if ((fd = open(RADUTMP, O_RDWR|O_CREAT, 0644)) >= 0) {
83                 int r;
84
85                 /*
86                  *      Lock the utmp file, prefer lockf() over flock().
87                  */
88 #if defined(F_LOCK) && !defined(BSD)
89                 (void)lockf(fd, F_LOCK, LOCK_LEN);
90 #else
91                 (void)flock(fd, LOCK_EX);
92 #endif
93                 /*
94                  *      Find the entry for this NAS / portno combination.
95                  */
96                 r = 0;
97                 while (read(fd, &u, sizeof(u)) == sizeof(u)) {
98                         if (((nasaddr != 0 && nasaddr != u.nas_address) ||
99                               (port >= 0   && port    != u.nas_port) ||
100                               (user != NULL &&
101                                strncmp(u.login, user, sizeof(u.login)) != 0) ||
102                                u.type != P_LOGIN))
103                                 continue;
104                         /*
105                          *      Match. Zap it.
106                          */
107                         if (lseek(fd, -(off_t)sizeof(u), SEEK_CUR) < 0) {
108                                 radlog(L_ERR, "Accounting: radutmp_zap: "
109                                            "negative lseek!\n");
110                                 lseek(fd, (off_t)0, SEEK_SET);
111                         }
112                         u.type = P_IDLE;
113                         u.time = t;
114                         write(fd, &u, sizeof(u));
115
116 #if 0 /* FIXME: should we fixup radwtmp as well or not ? */
117                         /*
118                          *      Add a logout entry to the wtmp file.
119                          */
120                         if (fp != NULL)  {
121                                 make_wtmp(&u, &wt, PW_STATUS_STOP);
122                                 fwrite(&wt, sizeof(wt), 1, fp);
123                         }
124 #endif
125                 }
126                 close(fd);
127         }
128         if (fp) fclose(fp);
129
130         return 0;
131 }
132
133
134 /*
135  *      Store logins in the RADIUS utmp file.
136  */
137 int radutmp_add(REQUEST *request)
138 {
139         struct radutmp  ut, u;
140         VALUE_PAIR      *vp;
141         int             rb_record = 0;
142         int             status = -1;
143         int             nas_address = 0;
144         int             framed_address = 0;
145         int             protocol = -1;
146         time_t          t;
147         int             fd;
148         int             ret = 0;
149         int             just_an_update = 0;
150         int             port_seen = 0;
151         int             nas_port_type = 0;
152         int             off;
153
154         /*
155          *      Which type is this.
156          */
157         if ((vp = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE)) == NULL) {
158                 radlog(L_ERR, "Accounting: no Accounting-Status-Type record.");
159                 return -1;
160         }
161         status = vp->lvalue;
162         if (status == PW_STATUS_ACCOUNTING_ON ||
163             status == PW_STATUS_ACCOUNTING_OFF) rb_record = 1;
164
165         if (!rb_record &&
166             (vp = pairfind(request->packet->vps, PW_USER_NAME)) == NULL) do {
167                 int check1 = 0;
168                 int check2 = 0;
169
170                 /*
171                  *      ComOS (up to and including 3.5.1b20) does not send
172                  *      standard PW_STATUS_ACCOUNTING_XXX messages.
173                  *
174                  *      Check for:  o no Acct-Session-Time, or time of 0
175                  *                  o Acct-Session-Id of "00000000".
176                  *
177                  *      We could also check for NAS-Port, that attribute
178                  *      should NOT be present (but we don't right now).
179                  */
180                 if ((vp = pairfind(request->packet->vps, PW_ACCT_SESSION_TIME))
181                      == NULL || vp->lvalue == 0)
182                         check1 = 1;
183                 if ((vp = pairfind(request->packet->vps, PW_ACCT_SESSION_ID))
184                      != NULL && vp->length == 8 &&
185                      memcmp(vp->strvalue, "00000000", 8) == 0)
186                         check2 = 1;
187                 if (check1 == 0 || check2 == 0) {
188 #if 0 /* Cisco sometimes sends START records without username. */
189                         radlog(L_ERR, "Accounting: no username in record");
190                         return -1;
191 #else
192                         break;
193 #endif
194                 }
195                 radlog(L_INFO, "Accounting: converting reboot records.");
196                 if (status == PW_STATUS_STOP)
197                         status = PW_STATUS_ACCOUNTING_OFF;
198                 if (status == PW_STATUS_START)
199                         status = PW_STATUS_ACCOUNTING_ON;
200                 rb_record = 1;
201         } while(0);
202
203         time(&t);
204         memset(&ut, 0, sizeof(ut));
205         ut.porttype = 'A';
206
207         /*
208          *      First, find the interesting attributes.
209          */
210         for (vp = request->packet->vps; vp; vp = vp->next) {
211                 switch (vp->attribute) {
212                         case PW_USER_NAME:
213                                 strncpy(ut.login, (char *)vp->strvalue,
214                                         RUT_NAMESIZE);
215                                 break;
216                         case PW_LOGIN_IP_HOST:
217                         case PW_FRAMED_IP_ADDRESS:
218                                 framed_address = vp->lvalue;
219                                 ut.framed_address = vp->lvalue;
220                                 break;
221                         case PW_FRAMED_PROTOCOL:
222                                 protocol = vp->lvalue;
223                                 break;
224                         case PW_NAS_IP_ADDRESS:
225                                 nas_address = vp->lvalue;
226                                 ut.nas_address = vp->lvalue;
227                                 break;
228                         case PW_NAS_PORT_ID:
229                                 ut.nas_port = vp->lvalue;
230                                 port_seen = 1;
231                                 break;
232                         case PW_ACCT_DELAY_TIME:
233                                 ut.delay = vp->lvalue;
234                                 break;
235                         case PW_ACCT_SESSION_ID:
236                                 /*
237                                  *      If length > 8, only store the
238                                  *      last 8 bytes.
239                                  */
240                                 off = vp->length - sizeof(ut.session_id);
241                                 if (off < 0) off = 0;
242                                 memcpy(ut.session_id, vp->strvalue + off,
243                                         sizeof(ut.session_id));
244                                 break;
245                         case PW_NAS_PORT_TYPE:
246                                 if (vp->lvalue >= 0 && vp->lvalue <= 4)
247                                         ut.porttype = porttypes[vp->lvalue];
248                                 nas_port_type = vp->lvalue;
249                                 break;
250                         case PW_CALLING_STATION_ID:
251                                 strncpy(ut.caller_id, (char *)vp->strvalue,
252                                         sizeof(ut.caller_id));
253                                 ut.caller_id[sizeof(ut.caller_id) - 1] = 0;
254                                 break;
255                 }
256         }
257
258         /*
259          *      If we didn't find out the NAS address, use the
260          *      originator's IP address.
261          */
262         if (nas_address == 0) {
263                 nas_address = request->packet->src_ipaddr;
264                 ut.nas_address = nas_address;
265         }
266
267         if (protocol == PW_PPP)
268                 ut.proto = 'P';
269         else if (protocol == PW_SLIP)
270                 ut.proto = 'S';
271         else
272                 ut.proto = 'T';
273         ut.time = t - ut.delay;
274
275         /*
276          *      See if this was a portmaster reboot.
277          */
278         if (status == PW_STATUS_ACCOUNTING_ON && nas_address) {
279                 radlog(L_INFO, "NAS %s restarted (Accounting-On packet seen)",
280                         nas_name(nas_address));
281                 radutmp_zap(nas_address, -1, NULL, ut.time);
282                 return 0;
283         }
284         if (status == PW_STATUS_ACCOUNTING_OFF && nas_address) {
285                 radlog(L_INFO, "NAS %s rebooted (Accounting-Off packet seen)",
286                         nas_name(nas_address));
287                 radutmp_zap(nas_address, -1, NULL, ut.time);
288                 return 0;
289         }
290
291         /*
292          *      If we don't know this type of entry pretend we succeeded.
293          */
294         if (status != PW_STATUS_START &&
295             status != PW_STATUS_STOP &&
296             status != PW_STATUS_ALIVE) {
297                 radlog(L_ERR, "NAS %s port %d unknown packet type %d)",
298                         nas_name(nas_address), ut.nas_port, status);
299                 return 0;
300         }
301
302         /*
303          *      Perhaps we don't want to store this record into
304          *      radutmp. We skip records:
305          *
306          *      - without a NAS-Port-Id (telnet / tcp access)
307          *      - with the username "!root" (console admin login)
308          */
309         if (!port_seen || strncmp(ut.login, "!root", RUT_NAMESIZE) == 0)
310                 return 0;
311
312         /*
313          *      Enter into the radutmp file.
314          */
315         if ((fd = open(RADUTMP, O_RDWR|O_CREAT, 0644)) >= 0) {
316                 NAS_PORT *cache;
317                 int r;
318
319                 /*
320                  *      Lock the utmp file, prefer lockf() over flock().
321                  */
322 #if defined(F_LOCK) && !defined(BSD)
323                 (void)lockf(fd, F_LOCK, LOCK_LEN);
324 #else
325                 (void)flock(fd, LOCK_EX);
326 #endif
327                 /*
328                  *      Find the entry for this NAS / portno combination.
329                  */
330                 if ((cache = nas_port_find(ut.nas_address, ut.nas_port)) != NULL)
331                         lseek(fd, (off_t)cache->offset, SEEK_SET);
332
333                 r = 0;
334                 off = 0;
335                 while (read(fd, &u, sizeof(u)) == sizeof(u)) {
336                         off += sizeof(u);
337                         if (u.nas_address != ut.nas_address ||
338                             u.nas_port    != ut.nas_port)
339                                 continue;
340
341                         if (status == PW_STATUS_STOP &&
342                             strncmp(ut.session_id, u.session_id,
343                              sizeof(u.session_id)) != 0) {
344                                 /*
345                                  *      Don't complain if this is not a
346                                  *      login record (some clients can
347                                  *      send _only_ logout records).
348                                  */
349                                 if (u.type == P_LOGIN)
350                                         radlog(L_ERR,
351                 "Accounting: logout: entry for NAS %s port %d has wrong ID",
352                                         nas_name(nas_address), u.nas_port);
353                                 r = -1;
354                                 break;
355                         }
356
357                         if (status == PW_STATUS_START &&
358                             strncmp(ut.session_id, u.session_id,
359                              sizeof(u.session_id)) == 0  &&
360                             u.time >= ut.time) {
361                                 if (u.type == P_LOGIN) {
362                                         radlog(L_INFO,
363                 "Accounting: login: entry for NAS %s port %d duplicate",
364                                         nas_name(nas_address), u.nas_port);
365                                         r = -1;
366                                         break;
367                                 }
368                                 radlog(L_ERR,
369                 "Accounting: login: entry for NAS %s port %d wrong order",
370                                 nas_name(nas_address), u.nas_port);
371                                 r = -1;
372                                 break;
373                         }
374
375                         /*
376                          *      FIXME: the ALIVE record could need
377                          *      some more checking, but anyway I'd
378                          *      rather rewrite this mess -- miquels.
379                          */
380                         if (status == PW_STATUS_ALIVE &&
381                             strncmp(ut.session_id, u.session_id,
382                              sizeof(u.session_id)) == 0  &&
383                             u.type == P_LOGIN) {
384                                 /*
385                                  *      Keep the original login time.
386                                  */
387                                 ut.time = u.time;
388                                 if (u.login[0] != 0)
389                                         just_an_update = 1;
390                         }
391
392                         if (lseek(fd, -(off_t)sizeof(u), SEEK_CUR) < 0) {
393                                 radlog(L_ERR, "Accounting: negative lseek!\n");
394                                 lseek(fd, (off_t)0, SEEK_SET);
395                                 off = 0;
396                         } else
397                                 off -= sizeof(u);
398                         r = 1;
399                         break;
400                 }
401
402                 if (r >= 0 &&  (status == PW_STATUS_START ||
403                                 status == PW_STATUS_ALIVE)) {
404                         if (cache == NULL) {
405                            if ((cache = malloc(sizeof(NAS_PORT))) != NULL) {
406                                    cache->nasaddr = ut.nas_address;
407                                    cache->port = ut.nas_port;
408                                    cache->offset = off;
409                                    cache->next = nas_port_list;
410                                    nas_port_list = cache;
411                            }
412                         }
413                         ut.type = P_LOGIN;
414                         write(fd, &ut, sizeof(u));
415                 }
416                 if (status == PW_STATUS_STOP) {
417                         if (r > 0) {
418                                 u.type = P_IDLE;
419                                 u.time = ut.time;
420                                 u.delay = ut.delay;
421                                 write(fd, &u, sizeof(u));
422                         } else if (r == 0) {
423                                 radlog(L_ERR,
424                 "Accounting: logout: login entry for NAS %s port %d not found",
425                                 nas_name(nas_address), ut.nas_port);
426                                 r = -1;
427                         }
428                 }
429                 close(fd);
430         } else {
431                 radlog(L_ERR, "Accounting: %s: %s", RADUTMP, strerror(errno));
432                 ret = -1;
433         }
434
435         return ret;
436 }
437
438
439 /*
440  *      Timeout handler (10 secs)
441  */
442 static int got_alrm;
443 static void alrm_handler(int sig)
444 {
445         sig = sig; /* -Wunused */
446         got_alrm = 1;
447 }
448
449 /*
450  *      Check one terminal server to see if a user is logged in.
451  */
452 static int rad_check_ts(uint32_t nasaddr, int portnum, const char *user,
453                         const char *session_id)
454 {
455         int     pid, st, e;
456         int     n;
457         NAS     *nas;
458         char    address[16];
459         char    port[8];
460         void    (*handler)(int);
461
462         /*
463          *      Find NAS type.
464          */
465         if ((nas = nas_find(nasaddr)) == NULL) {
466                 radlog(L_ERR, "Accounting: unknown NAS");
467                 return -1;
468         }
469
470         /*
471          *      Fork.
472          */
473         handler = signal(SIGCHLD, SIG_DFL);
474         if ((pid = fork()) < 0) {
475                 radlog(L_ERR, "Accounting: fork: %s", strerror(errno));
476                 signal(SIGCHLD, handler);
477                 return -1;
478         }
479
480         if (pid > 0) {
481                 /*
482                  *      Parent - Wait for checkrad to terminate.
483                  *      We timeout in 10 seconds.
484                  */
485                 got_alrm = 0;
486                 signal(SIGALRM, alrm_handler);
487                 alarm(10);
488                 while((e = waitpid(pid, &st, 0)) != pid)
489                         if (e < 0 && (errno != EINTR || got_alrm))
490                                 break;
491                 alarm(0);
492                 signal(SIGCHLD, handler);
493                 if (got_alrm) {
494                         kill(pid, SIGTERM);
495                         sleep(1);
496                         kill(pid, SIGKILL);
497                         radlog(L_ERR, "Check-TS: timeout waiting for checkrad");
498                         return 2;
499                 }
500                 if (e < 0) {
501                         radlog(L_ERR, "Check-TS: unknown error in waitpid()");
502                         return 2;
503                 }
504                 return WEXITSTATUS(st);
505         }
506
507         /*
508          *      Child - exec checklogin with the right parameters.
509          */
510         for (n = 32; n >= 3; n--)
511                 close(n);
512
513         ip_ntoa(address, nasaddr);
514         sprintf(port, "%d", portnum);
515
516 #ifdef __EMX__
517         /* OS/2 can't directly execute scripts then we call the command
518            processor to execute checkrad
519         */
520         execl(getenv("COMSPEC"), "", "/C","checkrad",nas->nastype, address, port,
521                 user, session_id, NULL);
522 #else
523         execl(CHECKRAD, "checkrad",nas->nastype, address, port,
524                 user, session_id, NULL);
525 #endif
526         radlog(L_ERR, "Check-TS: exec %s: %s", CHECKRAD, strerror(errno));
527
528         /*
529          *      Exit - 2 means "some error occured".
530          */
531         exit(2);
532 }
533
534 /*
535  *      See if a user is already logged in.
536  *
537  *      Check twice. If on the first pass the user exceeds his
538  *      max. number of logins, do a second pass and validate all
539  *      logins by querying the terminal server (using eg. SNMP).
540  *
541  *      Returns: 0 == OK, 1 == double logins, 2 == multilink attempt
542  */
543 int radutmp_checksimul(char *name, VALUE_PAIR *request, int maxsimul)
544 {
545         VALUE_PAIR      *fra;
546         struct radutmp  u;
547         uint32_t        ipno = 0;
548         int             fd;
549         int             count;
550         int             mpp = 1;
551
552         if ((fd = open(RADUTMP, O_CREAT|O_RDWR, 0644)) < 0)
553                 return 0;
554
555         /*
556          *      We don't lock in the first pass.
557          */
558         count = 0;
559         while(read(fd, &u, sizeof(u)) == sizeof(u))
560                 if (strncmp(name, u.login, RUT_NAMESIZE) == 0
561                     && u.type == P_LOGIN)
562                         count++;
563
564         if (count < maxsimul) {
565                 close(fd);
566                 return 0;
567         }
568         lseek(fd, (off_t)0, SEEK_SET);
569
570         /*
571          *      Setup some stuff, like for MPP detection.
572          */
573         if ((fra = pairfind(request, PW_FRAMED_IP_ADDRESS)) != NULL)
574                 ipno = fra->lvalue;
575
576         /*
577          *      lockf() the file while reading/writing.
578          */
579 #if defined(F_LOCK) && !defined(BSD)
580                 (void)lockf(fd, F_LOCK, LOCK_LEN);
581 #else
582                 (void)flock(fd, LOCK_EX);
583 #endif
584
585         /*
586          *      Allright, there are too many concurrent logins.
587          *      Check all registered logins by querying the
588          *      terminal server directly.
589          *      FIXME: rad_check_ts() runs with locked radutmp file!
590          */
591         count = 0;
592         while (read(fd, &u, sizeof(u)) == sizeof(u)) {
593                 if (strncmp(name, u.login, RUT_NAMESIZE) == 0
594                     && u.type == P_LOGIN) {
595                         char session_id[sizeof u.session_id+1];
596                         strNcpy(session_id, u.session_id, sizeof session_id);
597                         if (rad_check_ts(u.nas_address, u.nas_port,
598                                          u.login, session_id) == 1) {
599                                 count++;
600                                 /*
601                                  *      Does it look like a MPP attempt?
602                                  */
603                                 if (strchr("SCPA", u.proto) &&
604                                     ipno && u.framed_address == ipno)
605                                         mpp = 2;
606                         }
607                         else {
608                                 /*
609                                  *      False record - zap it.
610                                  */
611
612                                 lseek(fd, -(off_t)sizeof(u), SEEK_CUR);
613                                 u.type = P_IDLE;
614                                 write(fd, &u, sizeof(u));
615
616 #if 0 /* FIXME: should we fixup radwtmp as well or not ? */
617                                 if ((wfp = fopen(RADWTMP, "a")) != NULL) {
618                                         make_wtmp(&u, &wt, PW_STATUS_STOP);
619                                         fwrite(&wt, sizeof(wt), 1, wfp);
620                                         fclose(wfp);
621                                 }
622 #endif
623                         }
624                 }
625         }
626         close(fd);
627
628         return (count < maxsimul) ? 0 : mpp;
629 }
630