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