Added a second mode of operation to cf_section_parse, where it takes a base
[freeradius.git] / src / modules / rlm_radutmp / rlm_radutmp.c
1 /*
2  * rlm_radutmp.c        
3  *
4  * Version:     $Id$
5  *
6  *   This program is free software; you can redistribute it and/or modify
7  *   it under the terms of the GNU General Public License as published by
8  *   the Free Software Foundation; either version 2 of the License, or
9  *   (at your option) any later version.
10  *
11  *   This program is distributed in the hope that it will be useful,
12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *   GNU General Public License for more details.
15  *
16  *   You should have received a copy of the GNU General Public License
17  *   along with this program; if not, write to the Free Software
18  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  * Copyright 2000  The FreeRADIUS server project
21  * FIXME add copyrights
22  */
23
24 #include        "autoconf.h"
25
26 #include        <sys/types.h>
27 #include        <stdio.h>
28 #include        <string.h>
29 #include        <stdlib.h>
30 #include        <unistd.h>
31 #include        <fcntl.h>
32 #include        <time.h>
33 #include        <errno.h>
34 #include        <limits.h>
35
36 #include "config.h"
37
38 #include        "radiusd.h"
39 #include        "radutmp.h"
40 #include        "modules.h"
41
42 #define LOCK_LEN sizeof(struct radutmp)
43
44 static const char porttypes[] = "ASITX";
45
46 /*
47  *      Internal wrapper for locking, to minimize the number of ifdef's
48  *      in the source.
49  *
50  *      Lock the utmp file, prefer lockf() over flock()
51  */
52 static void radutmp_lock(int fd)
53 {
54 #if defined(F_LOCK) && !defined(BSD)
55         (void)lockf(fd, F_LOCK, LOCK_LEN);
56 #else
57         (void)flock(fd, LOCK_EX);
58 #endif
59 }
60
61 /*
62  *      Internal wrapper for unlocking, to minimize the number of ifdef's
63  *      in the source.
64  *
65  *      Unlock the utmp file, prefer lockf() over flock()
66  */
67 static void radutmp_unlock(int fd)
68 {
69 #if defined(F_LOCK) && !defined(BSD)
70         (void)lockf(fd, F_ULOCK, LOCK_LEN);
71 #else
72         (void)flock(fd, LOCK_UN);
73 #endif
74 }
75
76 /*
77  *      used for caching radutmp lookups in the accounting component. The
78  *      session (checksimul) component doesn't use it, but probably should.
79  */
80 typedef struct nas_port {
81         uint32_t                nasaddr;
82         int                     port;
83         off_t                   offset;
84         struct nas_port         *next;
85 } NAS_PORT;
86
87 struct radutmp_instance {
88   NAS_PORT *nas_port_list;
89   char *radutmp_fn;
90   int permission;
91   int callerid_ok;
92 };
93
94 static CONF_PARSER module_config[] = {
95   { "filename", PW_TYPE_STRING_PTR,
96     offsetof(struct radutmp_instance,radutmp_fn), NULL,  RADUTMP },
97   { "perm",     PW_TYPE_INTEGER,
98     offsetof(struct radutmp_instance,permission), NULL,  "0644" },
99   { "callerid", PW_TYPE_BOOLEAN,
100     offsetof(struct radutmp_instance,callerid_ok), NULL, "no" },
101   { NULL, -1, 0, NULL, NULL }           /* end the list */
102 };
103
104 static int radutmp_instantiate(CONF_SECTION *conf, void **instance)
105 {
106         struct radutmp_instance *inst;
107         inst = rad_malloc(sizeof(*inst));
108         if (cf_section_parse(conf, inst, module_config)) {
109                 free(inst);
110                 return -1;
111         }
112         inst->nas_port_list = NULL;
113         *instance = inst;
114         return 0;
115 }
116
117
118 /*
119  *      Detach.
120  */
121 static int radutmp_detach(void *instance)
122 {
123         NAS_PORT *p, *next;
124         struct radutmp_instance *inst = instance;
125
126         for(p=inst->nas_port_list ; p ; p=next) {
127                 next=p->next;
128                 free(p);
129         }
130         free(inst->radutmp_fn);
131         free(inst);
132         return 0;
133 }
134
135 /*
136  *      Zap all users on a NAS from the radutmp file.
137  */
138 static int radutmp_zap(struct radutmp_instance *inst, uint32_t nasaddr, time_t t)
139 {
140         struct radutmp  u;
141         int             fd;
142
143         if (t == 0) time(&t);
144
145         fd = open(inst->radutmp_fn, O_RDWR);
146         if (fd >= 0) {
147                 /*
148                  *      Lock the utmp file, prefer lockf() over flock().
149                  */
150                 radutmp_lock(fd);
151
152                 /*
153                  *      Find the entry for this NAS / portno combination.
154                  */
155                 while (read(fd, &u, sizeof(u)) == sizeof(u)) {
156                         if ((nasaddr != 0 && nasaddr != u.nas_address) ||
157                               u.type != P_LOGIN)
158                                 continue;
159                         /*
160                          *      Match. Zap it.
161                          */
162                         if (lseek(fd, -(off_t)sizeof(u), SEEK_CUR) < 0) {
163                                 radlog(L_ERR, "Accounting: radutmp_zap: "
164                                               "negative lseek!\n");
165                                 lseek(fd, (off_t)0, SEEK_SET);
166                         }
167                         u.type = P_IDLE;
168                         u.time = t;
169                         write(fd, &u, sizeof(u));
170                 }
171                 close(fd);
172         } else {
173                 radlog(L_ERR, "Accounting: %s: %m", inst->radutmp_fn);
174         }
175
176         return 0;
177 }
178
179 /*
180  *      Lookup a NAS_PORT in the nas_port_list
181  */
182 static NAS_PORT *nas_port_find(NAS_PORT *nas_port_list, uint32_t nasaddr, int port)
183 {
184         NAS_PORT        *cl;
185
186         for(cl = nas_port_list; cl; cl = cl->next)
187                 if (nasaddr == cl->nasaddr &&
188                         port == cl->port)
189                         break;
190         return cl;
191 }
192
193
194 /*
195  *      Store logins in the RADIUS utmp file.
196  */
197 static int radutmp_accounting(void *instance, REQUEST *request)
198 {
199         struct radutmp  ut, u;
200         VALUE_PAIR      *vp;
201         int             rb_record = 0;
202         int             status = -1;
203         uint32_t        nas_address = 0;
204         uint32_t        framed_address = 0;
205         int             protocol = -1;
206         time_t          t;
207         int             fd;
208         int             just_an_update = 0;
209         int             port_seen = 0;
210         int             nas_port_type = 0;
211         int             off;
212         struct radutmp_instance *inst = instance;
213
214         /*
215          *      Which type is this.
216          */
217         if ((vp = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE)) == NULL) {
218                 radlog(L_ERR, "Accounting: no Accounting-Status-Type record.");
219                 return RLM_MODULE_NOOP;
220         }
221         status = vp->lvalue;
222         if (status == PW_STATUS_ACCOUNTING_ON ||
223             status == PW_STATUS_ACCOUNTING_OFF) rb_record = 1;
224
225         if (!rb_record &&
226             (vp = pairfind(request->packet->vps, PW_USER_NAME)) == NULL) do {
227                 int check1 = 0;
228                 int check2 = 0;
229
230                 /*
231                  *      ComOS (up to and including 3.5.1b20) does not send
232                  *      standard PW_STATUS_ACCOUNTING_XXX messages.
233                  *
234                  *      Check for:  o no Acct-Session-Time, or time of 0
235                  *                  o Acct-Session-Id of "00000000".
236                  *
237                  *      We could also check for NAS-Port, that attribute
238                  *      should NOT be present (but we don't right now).
239                  */
240                 if ((vp = pairfind(request->packet->vps, PW_ACCT_SESSION_TIME))
241                      == NULL || vp->lvalue == 0)
242                         check1 = 1;
243                 if ((vp = pairfind(request->packet->vps, PW_ACCT_SESSION_ID))
244                      != NULL && vp->length == 8 &&
245                      memcmp(vp->strvalue, "00000000", 8) == 0)
246                         check2 = 1;
247                 if (check1 == 0 || check2 == 0) {
248 #if 0 /* Cisco sometimes sends START records without username. */
249                         radlog(L_ERR, "Accounting: no username in record");
250                         return RLM_MODULE_FAIL;
251 #else
252                         break;
253 #endif
254                 }
255                 radlog(L_INFO, "Accounting: converting reboot records.");
256                 if (status == PW_STATUS_STOP)
257                         status = PW_STATUS_ACCOUNTING_OFF;
258                 if (status == PW_STATUS_START)
259                         status = PW_STATUS_ACCOUNTING_ON;
260                 rb_record = 1;
261         } while(0);
262
263         time(&t);
264         memset(&ut, 0, sizeof(ut));
265         ut.porttype = 'A';
266
267         /*
268          *      First, find the interesting attributes.
269          */
270         for (vp = request->packet->vps; vp; vp = vp->next) {
271                 switch (vp->attribute) {
272                         case PW_USER_NAME:
273                                 strncpy(ut.login, vp->strvalue, RUT_NAMESIZE);
274                                 break;
275                         case PW_LOGIN_IP_HOST:
276                         case PW_FRAMED_IP_ADDRESS:
277                                 framed_address = vp->lvalue;
278                                 ut.framed_address = vp->lvalue;
279                                 break;
280                         case PW_FRAMED_PROTOCOL:
281                                 protocol = vp->lvalue;
282                                 break;
283                         case PW_NAS_IP_ADDRESS:
284                                 nas_address = vp->lvalue;
285                                 ut.nas_address = vp->lvalue;
286                                 break;
287                         case PW_NAS_PORT_ID:
288                                 ut.nas_port = vp->lvalue;
289                                 port_seen = 1;
290                                 break;
291                         case PW_ACCT_DELAY_TIME:
292                                 ut.delay = vp->lvalue;
293                                 break;
294                         case PW_ACCT_SESSION_ID:
295                                 /*
296                                  *      If length > 8, only store the
297                                  *      last 8 bytes.
298                                  */
299                                 off = vp->length - sizeof(ut.session_id);
300                                 /*
301                                  *      Ascend is br0ken - it adds a \0
302                                  *      to the end of any string.
303                                  *      Compensate.
304                                  */
305                                 if (vp->length > 0 &&
306                                     vp->strvalue[vp->length - 1] == 0)
307                                         off--;
308                                 if (off < 0) off = 0;
309                                 memcpy(ut.session_id, vp->strvalue + off,
310                                         sizeof(ut.session_id));
311                                 break;
312                         case PW_NAS_PORT_TYPE:
313                                 if (vp->lvalue >= 0 && vp->lvalue <= 4)
314                                         ut.porttype = porttypes[vp->lvalue];
315                                 nas_port_type = vp->lvalue;
316                                 break;
317                         case PW_CALLING_STATION_ID:
318                                 if(inst->callerid_ok)
319                                         strNcpy(ut.caller_id,
320                                                 (char *)vp->strvalue,
321                                                 sizeof(ut.caller_id));
322                                 break;
323                 }
324         }
325
326         /*
327          *      If we didn't find out the NAS address, use the
328          *      originator's IP address.
329          */
330         if (nas_address == 0) {
331                 nas_address = request->packet->src_ipaddr;
332                 ut.nas_address = nas_address;
333         }
334
335         if (protocol == PW_PPP)
336                 ut.proto = 'P';
337         else if (protocol == PW_SLIP)
338                 ut.proto = 'S';
339         else
340                 ut.proto = 'T';
341         ut.time = t - ut.delay;
342
343         /*
344          *      See if this was a portmaster reboot.
345          */
346         if (status == PW_STATUS_ACCOUNTING_ON && nas_address) {
347                 radlog(L_INFO, "NAS %s restarted (Accounting-On packet seen)",
348                         nas_name(nas_address));
349                 radutmp_zap(inst, nas_address, ut.time);
350                 return RLM_MODULE_OK;
351         }
352         if (status == PW_STATUS_ACCOUNTING_OFF && nas_address) {
353                 radlog(L_INFO, "NAS %s rebooted (Accounting-Off packet seen)",
354                         nas_name(nas_address));
355                 radutmp_zap(inst, nas_address, ut.time);
356                 return RLM_MODULE_OK;
357         }
358
359         /*
360          *      If we don't know this type of entry pretend we succeeded.
361          */
362         if (status != PW_STATUS_START &&
363             status != PW_STATUS_STOP &&
364             status != PW_STATUS_ALIVE) {
365                 radlog(L_ERR, "NAS %s port %d unknown packet type %d)",
366                         nas_name(nas_address), ut.nas_port, status);
367                 return RLM_MODULE_NOOP;
368         }
369
370         /*
371          *      Perhaps we don't want to store this record into
372          *      radutmp. We skip records:
373          *
374          *      - without a NAS-Port-Id (telnet / tcp access)
375          *      - with the username "!root" (console admin login)
376          */
377         if (!port_seen || strncmp(ut.login, "!root", RUT_NAMESIZE) == 0)
378                 return RLM_MODULE_NOOP;
379
380         /*
381          *      Enter into the radutmp file.
382          */
383         fd = open(inst->radutmp_fn, O_RDWR|O_CREAT, inst->permission);
384         if (fd >= 0) {
385                 NAS_PORT *cache;
386                 int r;
387
388                 /*
389                  *      Lock the utmp file, prefer lockf() over flock().
390                  */
391                 radutmp_lock(fd);
392
393                 /*
394                  *      Find the entry for this NAS / portno combination.
395                  */
396                 if ((cache = nas_port_find(inst->nas_port_list, ut.nas_address,
397                                            ut.nas_port)) != NULL)
398                         lseek(fd, (off_t)cache->offset, SEEK_SET);
399
400                 r = 0;
401                 off = 0;
402                 while (read(fd, &u, sizeof(u)) == sizeof(u)) {
403                         off += sizeof(u);
404                         if (u.nas_address != ut.nas_address ||
405                             u.nas_port    != ut.nas_port)
406                                 continue;
407
408                         if (status == PW_STATUS_STOP &&
409                             strncmp(ut.session_id, u.session_id,
410                              sizeof(u.session_id)) != 0) {
411                                 /*
412                                  *      Don't complain if this is not a
413                                  *      login record (some clients can
414                                  *      send _only_ logout records).
415                                  */
416                                 if (u.type == P_LOGIN)
417                                         radlog(L_ERR,
418                 "Accounting: logout: entry for NAS %s port %d has wrong ID",
419                                         nas_name(nas_address), u.nas_port);
420                                 r = -1;
421                                 break;
422                         }
423
424                         if (status == PW_STATUS_START &&
425                             strncmp(ut.session_id, u.session_id,
426                              sizeof(u.session_id)) == 0  &&
427                             u.time >= ut.time) {
428                                 if (u.type == P_LOGIN) {
429                                         radlog(L_INFO,
430                 "Accounting: login: entry for NAS %s port %d duplicate",
431                                         nas_name(nas_address), u.nas_port);
432                                         r = -1;
433                                         break;
434                                 }
435                                 radlog(L_ERR,
436                 "Accounting: login: entry for NAS %s port %d wrong order",
437                                 nas_name(nas_address), u.nas_port);
438                                 r = -1;
439                                 break;
440                         }
441
442                         /*
443                          *      FIXME: the ALIVE record could need
444                          *      some more checking, but anyway I'd
445                          *      rather rewrite this mess -- miquels.
446                          */
447                         if (status == PW_STATUS_ALIVE &&
448                             strncmp(ut.session_id, u.session_id,
449                              sizeof(u.session_id)) == 0  &&
450                             u.type == P_LOGIN) {
451                                 /*
452                                  *      Keep the original login time.
453                                  */
454                                 ut.time = u.time;
455                                 if (u.login[0] != 0)
456                                         just_an_update = 1;
457                         }
458
459                         if (lseek(fd, -(off_t)sizeof(u), SEEK_CUR) < 0) {
460                                 radlog(L_ERR, "Accounting: negative lseek!\n");
461                                 lseek(fd, (off_t)0, SEEK_SET);
462                                 off = 0;
463                         } else
464                                 off -= sizeof(u);
465                         r = 1;
466                         break;
467                 }
468
469                 if (r >= 0 &&  (status == PW_STATUS_START ||
470                                 status == PW_STATUS_ALIVE)) {
471                         if (cache == NULL) {
472                            cache = rad_malloc(sizeof(NAS_PORT));
473                            cache->nasaddr = ut.nas_address;
474                            cache->port = ut.nas_port;
475                            cache->offset = off;
476                            cache->next = inst->nas_port_list;
477                            inst->nas_port_list = cache;
478                         }
479                         ut.type = P_LOGIN;
480                         write(fd, &ut, sizeof(u));
481                 }
482                 if (status == PW_STATUS_STOP) {
483                         if (r > 0) {
484                                 u.type = P_IDLE;
485                                 u.time = ut.time;
486                                 u.delay = ut.delay;
487                                 write(fd, &u, sizeof(u));
488                         } else if (r == 0) {
489                                 radlog(L_ERR,
490                 "Accounting: logout: login entry for NAS %s port %d not found",
491                                 nas_name(nas_address), ut.nas_port);
492                                 r = -1;
493                         }
494                 }
495                 close(fd);
496         } else {
497                 radlog(L_ERR, "Accounting: %s: %m", inst->radutmp_fn);
498                 return RLM_MODULE_FAIL;
499         }
500
501         return RLM_MODULE_OK;
502 }
503
504 /*
505  *      See if a user is already logged in. Sets request->simul_count to the
506  *      current session count for this user and sets request->simul_mpp to 2
507  *      if it looks like a multilink attempt based on the requested IP
508  *      address, otherwise leaves request->simul_mpp alone.
509  *
510  *      Check twice. If on the first pass the user exceeds his
511  *      max. number of logins, do a second pass and validate all
512  *      logins by querying the terminal server (using eg. SNMP).
513  */
514 static int radutmp_checksimul(void *instance, REQUEST *request)
515 {
516         struct radutmp  u;
517         int             fd;
518         VALUE_PAIR      *fra;
519         uint32_t        ipno = 0;
520         int             rcode;
521         const char *name = (char *)request->username->strvalue;
522         struct radutmp_instance *inst = instance;
523
524         if ((fd = open(inst->radutmp_fn, O_RDWR)) < 0) {
525                 if(errno!=ENOENT)
526                         return RLM_MODULE_FAIL;
527                 request->simul_count=0;
528                 return RLM_MODULE_OK;
529         }
530
531         request->simul_count = 0;
532         while(read(fd, &u, sizeof(u)) == sizeof(u)) {
533                 if (strncmp(name, u.login, RUT_NAMESIZE) == 0
534                     && u.type == P_LOGIN)
535                         ++request->simul_count;
536         }
537
538         if(request->simul_count < request->simul_max) {
539                 close(fd);
540                 return RLM_MODULE_OK;
541         }
542         lseek(fd, (off_t)0, SEEK_SET);
543
544         /*
545          *      Setup some stuff, like for MPP detection.
546          */
547         if ((fra = pairfind(request->packet->vps, PW_FRAMED_IP_ADDRESS)) != NULL)
548                 ipno = fra->lvalue;
549
550         /*
551          *      lockf() the file while reading/writing.
552          */
553                 radutmp_lock(fd);
554
555         request->simul_count = 0;
556         while (read(fd, &u, sizeof(u)) == sizeof(u)) {
557                 if (strncmp(name, u.login, RUT_NAMESIZE) == 0
558                     && u.type == P_LOGIN) {
559                         char login[sizeof u.login+1];
560                         char session_id[sizeof u.session_id+1];
561                         strNcpy(login, u.login, sizeof login);
562                         strNcpy(session_id, u.session_id, sizeof session_id);
563
564                         /*
565                          *      rad_check_ts may take seconds to return,
566                          *      and we don't want to block everyone else
567                          *      while that's happening.
568                          */
569                         radutmp_unlock(fd);
570                         rcode = rad_check_ts(u.nas_address, u.nas_port, login, 
571                                              session_id);
572                         radutmp_lock(fd);
573
574                         if (rcode == 1) {
575                                 ++request->simul_count;
576                                 /*
577                                  *      Does it look like a MPP attempt?
578                                  */
579                                 if (strchr("SCPA", u.proto) &&
580                                     ipno && u.framed_address == ipno)
581                                         request->simul_mpp = 2;
582                         }
583                         else {
584                                 /*
585                                  *      False record - zap it.
586                                  */
587
588                                 session_zap(u.nas_address, u.nas_port, login,
589                                             session_id, u.framed_address,
590                                             u.proto, 0);
591                         }
592                 }
593         }
594         close(fd);
595
596         return RLM_MODULE_OK;
597 }
598
599 /* globally exported name */
600 module_t rlm_radutmp = {
601   "radutmp",
602   0,                            /* type: reserved */
603   NULL,                         /* initialization */
604   radutmp_instantiate,          /* instantiation */
605   NULL,                         /* authorization */
606   NULL,                         /* authentication */
607   NULL,                         /* preaccounting */
608   radutmp_accounting,           /* accounting */
609   radutmp_checksimul,           /* checksimul */
610   radutmp_detach,               /* detach */
611   NULL,                         /* destroy */
612 };
613