Release bumped to "gts4".
[tac_plus.git] / maxsess.c
1 /*
2    Copyright (c) 1995-1998 by Cisco systems, Inc.
3
4    Permission to use, copy, modify, and distribute this software for
5    any purpose and without fee is hereby granted, provided that this
6    copyright and permission notice appear on all copies of the
7    software and supporting documentation, the name of Cisco Systems,
8    Inc. not be used in advertising or publicity pertaining to
9    distribution of the program without specific prior permission, and
10    notice be given in supporting documentation that modification,
11    copying and distribution is by permission of Cisco Systems, Inc.
12
13    Cisco Systems, Inc. makes no representations about the suitability
14    of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
15    IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
16    WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
17    FITNESS FOR A PARTICULAR PURPOSE.
18 */
19
20
21 #include "tac_plus.h"
22
23 #ifdef MAXSESS
24
25 #include <ctype.h>
26 #include <stdlib.h>
27 #include <sys/socket.h>
28 #include <netinet/in.h>
29 #include <arpa/inet.h>
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #ifdef HAVE_FCNTL_H
33 #include <fcntl.h>
34 #endif
35 #include <string.h>
36 #include <errno.h>
37 #ifdef HAVE_SYS_TIME_H
38 #include <sys/time.h>
39 #endif
40 #ifdef HAVE_UNISTD_H
41 #include <unistd.h>
42 #endif
43 #include <netdb.h>
44
45 #include "maxsess.h"
46 #include "report.h"
47 #include "utils.h"
48 #include "cfgfile.h"
49 #include "main.h"
50 #include "do_acct.h"
51 #include "parse.h"
52
53
54 void maxsess_loginit TAC_ARGS((void));
55
56
57 /* Configurable:
58  */
59
60 /* This is a shared file used to maintain a record of who's on
61  */
62 #define WHOLOG_DEFAULT "/var/log/tac_who.log"
63
64
65 /*
66  * This is state kept per user/session
67  */
68 struct peruser {
69     char username[64];          /* User name */
70     char NAS_name[32];          /* NAS user logged into */
71     char NAS_port[32];          /*  ...port on that NAS */
72     char NAC_address[32];       /*  ...IP address of NAS */
73 };
74
75
76 char *wholog = WHOLOG_DEFAULT;
77 /*
78  * initialize wholog file for tracking of user logins/logouts from
79  * accounting records.
80  */
81 void
82 maxsess_loginit()
83 {
84     int fd;
85
86     fd = open(wholog, O_CREAT | O_RDWR, 0600);
87     if (fd < 0) {
88         report(LOG_ERR, "Can't create: %s", wholog);
89     } else {
90         if (debug & DEBUG_MAXSESS_FLAG) {
91             report(LOG_DEBUG, "Initialize %s", wholog);
92         }
93         close(fd);
94     }
95 }
96
97 static char *portname TAC_ARGS((char *oldport));
98
99 /*
100  * Given a port description, return it in a canonical format.
101  *
102  * This piece of goo is to cover the fact that an async line in EXEC
103  * mode is known as "ttyXX", but the same line doing PPP or SLIP is
104  * known as "AsyncXX".
105  */
106 static char *
107 portname(oldport)
108 char *oldport;
109 {
110     char *p = oldport;
111
112     if (!strncmp(p, "Async", 5) || !strncmp(p, "tty", 3)) {
113         while (!isdigit(*p) && *p) {
114             ++p;
115         }
116     }
117     if (!*p) {
118         if (debug & DEBUG_ACCT_FLAG)
119             report(LOG_DEBUG, "Maxsess -- Malformed portname: %s", oldport);
120         return (oldport);
121     }
122     return (p);
123 }
124
125 static void write_record TAC_ARGS((char *name, FILE *fp, void *buf, int size, long int offset));
126
127 /*
128  * Seek to offset and write a buffer into the file pointed to by fp
129  */
130 static void
131 write_record(name, fp, buf, size, offset)
132 FILE *fp;
133 long offset;
134 int size;
135 void *buf;
136 char *name;
137 {
138     if (fseek(fp, offset, SEEK_SET) < 0) {
139         report(LOG_ERR, "%s fd=%d Cannot seek to %ld %s",
140                name, fileno(fp), offset, sys_errlist[errno]);
141     }
142     if (fwrite(buf, size, 1, fp) != 1) {
143         report(LOG_ERR, "%s fd=%d Cannot write %d bytes",
144                name, fileno(fp), size);
145     }
146 }
147
148 static void process_stop_record TAC_ARGS((struct identity *idp));
149
150 static void
151 process_stop_record(idp)
152 struct identity *idp;
153 {
154     int recnum;
155     struct peruser pu;
156     FILE *fp;
157     char *nasport = portname(idp->NAS_port);
158
159     /* If we can't access the file, skip all checks. */
160     fp = fopen(wholog, "r+");
161     if (fp == NULL) {
162         report(LOG_ERR, "Can't open %s for updating", wholog);
163         return;
164     }
165     tac_lockfd(wholog, fileno(fp));
166
167     for (recnum = 0; 1; recnum++) {
168
169         fseek(fp, recnum * sizeof(struct peruser), SEEK_SET);
170
171         if (fread(&pu, sizeof(pu), 1, fp) <= 0) {
172             break;
173         }
174
175         /* A match for this record? */
176         if (!(STREQ(pu.NAS_name, idp->NAS_name) &&
177               STREQ(pu.NAS_port, nasport))) {
178             continue;
179         }
180
181         /* A match. Zero out this record */
182         bzero(&pu, sizeof(pu));
183
184         write_record(wholog, fp, &pu, sizeof(pu),
185                      recnum * sizeof(struct peruser));
186
187         if (debug & DEBUG_MAXSESS_FLAG) {
188             report(LOG_DEBUG, "STOP record -- clear %s entry %d for %s/%s",
189                    wholog, recnum, idp->username, nasport);
190         }
191     }
192     fclose(fp);
193 }
194
195 static void process_start_record TAC_ARGS((struct identity *idp));
196
197 static void
198 process_start_record(idp)
199 struct identity *idp;
200 {
201     int recnum;
202     int foundrec = -1;
203     int freerec = -1;
204     char *nasport = portname(idp->NAS_port);
205     struct peruser pu;
206     FILE *fp;
207
208     /* If we can't access the file, skip all checks. */
209     fp = fopen(wholog, "r+");
210     if (fp == NULL) {
211         report(LOG_ERR, "Can't open %s for updating", wholog);
212         return;
213     }
214     tac_lockfd(wholog, fileno(fp));
215
216     for (recnum = 0; (fread(&pu, sizeof(pu), 1, fp) > 0); recnum++) {
217         /* Match for this NAS/Port record? */
218         if (STREQ(pu.NAS_name, idp->NAS_name) && STREQ(pu.NAS_port, nasport)) {
219             foundrec = recnum;
220             break;
221         }
222         /* Found a free slot on the way */
223         if (pu.username[0] == '\0') {
224             freerec = recnum;
225         }
226     }
227
228     /* This is a START record, so write a new record or update the existing
229      * one.  Note that we bzero(), so the strncpy()'s will truncate long
230      * names and always leave a null-terminated string.
231      */
232
233     bzero(&pu, sizeof(pu));
234     strncpy(pu.username, idp->username, sizeof(pu.username) - 1);
235     strncpy(pu.NAS_name, idp->NAS_name, sizeof(pu.NAS_name) - 1);
236     strncpy(pu.NAS_port, nasport, sizeof(pu.NAS_port) - 1);
237     strncpy(pu.NAC_address, idp->NAC_address, sizeof(pu.NAC_address) - 1);
238
239     /* Already in DB? */
240     if (foundrec >= 0) {
241
242         if (debug & DEBUG_MAXSESS_FLAG) {
243             report(LOG_DEBUG,
244               "START record -- overwrite existing %s entry %d for %s %s/%s",
245                    wholog, foundrec, pu.NAS_name, pu.username, pu.NAS_port);
246         }
247         write_record(wholog, fp, &pu, sizeof(pu),
248                      foundrec * sizeof(struct peruser));
249         fclose(fp);
250         return;
251     }
252
253     /* Not found in DB, but we have a free slot */
254     if (freerec >= 0) {
255
256         write_record(wholog, fp, &pu, sizeof(pu),
257                      freerec * sizeof(struct peruser));
258
259         if (debug & DEBUG_MAXSESS_FLAG) {
260             report(LOG_DEBUG, "START record -- %s entry %d for %s %s/%s added",
261                    wholog, freerec, pu.NAS_name, pu.username, pu.NAS_port);
262         }
263         fclose(fp);
264         return;
265     }
266
267     /* No free slot. Add record at the end */
268     write_record(wholog, fp, &pu, sizeof(pu),
269                  recnum * sizeof(struct peruser));
270
271     if (debug & DEBUG_MAXSESS_FLAG) {
272         report(LOG_DEBUG, "START record -- %s entry %d for %s %s/%s added",
273                wholog, recnum, pu.NAS_name, pu.username, pu.NAS_port);
274     }
275     fclose(fp);
276 }
277
278
279 void loguser TAC_ARGS((struct acct_rec *rec));
280
281 /*
282  * Given a start or a stop accounting record, update the file of
283  * records which tracks who's logged on and where.
284  */
285 void
286 loguser(rec)
287 struct acct_rec *rec;
288 {
289     struct identity *idp;
290     int i;
291
292     /* We're only interested in start/stop records */
293     if ((rec->acct_type != ACCT_TYPE_START) &&
294         (rec->acct_type != ACCT_TYPE_STOP)) {
295         return;
296     }
297     /* ignore command accounting records */
298     for (i = 0; i < rec->num_args; i++) {
299         char *avpair = rec->args[i];
300         if ((strncmp(avpair, "cmd=", 4) == 0) && strlen(avpair) > 4) {
301             return;
302         }
303     }
304
305     /* Extract and store just the port number, since the port names are
306      * different depending on whether this is an async interface or an exec
307      * line. */
308     idp = rec->identity;
309
310     switch (rec->acct_type) {
311     case ACCT_TYPE_START:
312         process_start_record(idp);
313         return;
314
315     case ACCT_TYPE_STOP:
316         process_stop_record(idp);
317         return;
318     }
319 }
320
321 /* Read up to n bytes from descriptor fd into array ptr with timeout t
322  * seconds.
323  *
324  * Return -1 on error, eof or timeout. Otherwise return number of
325  * bytes read. */
326
327 static int timed_read TAC_ARGS((int fd, void *ptr, int nbytes, int timeout));
328
329 static int
330 timed_read(fd, ptr, nbytes, timeout)
331 int fd;
332 void *ptr;
333 int nbytes;
334 int timeout;
335 {
336     int nread;
337     fd_set readfds, exceptfds;
338     struct timeval tout;
339
340     tout.tv_sec = timeout;
341     tout.tv_usec = 0;
342
343     FD_ZERO(&readfds);
344     FD_SET(fd, &readfds);
345
346     FD_ZERO(&exceptfds);
347     FD_SET(fd, &exceptfds);
348
349     while (1) {
350         int status = select(fd + 1, &readfds, (fd_set *) NULL,
351                             &exceptfds, &tout);
352
353         if (status == 0) {
354             report(LOG_DEBUG, "%s: timeout reading fd %d", session.peer, fd);
355             return (-1);
356         }
357         if (status < 0) {
358             if (errno == EINTR)
359                 continue;
360             report(LOG_DEBUG, "%s: error in select %s fd %d",
361                    session.peer, sys_errlist[errno], fd);
362             return (-1);
363         }
364         if (FD_ISSET(fd, &exceptfds)) {
365             report(LOG_DEBUG, "%s: exception on fd %d",
366                    session.peer, fd);
367             return (-1);
368         }
369         if (!FD_ISSET(fd, &readfds)) {
370             report(LOG_DEBUG, "%s: spurious return from select",
371                    session.peer);
372             continue;
373         }
374         nread = read(fd, ptr, nbytes);
375
376         if (nread < 0) {
377             if (errno == EINTR) {
378                 continue;
379             }
380             report(LOG_DEBUG, "%s %s: error reading fd %d nread=%d %s",
381                  session.peer, session.port, fd, nread, sys_errlist[errno]);
382             return (-1);        /* error */
383         }
384         if (nread == 0) {
385             return (-1);        /* eof */
386         }
387         return (nread);
388     }
389     /* NOTREACHED */
390 }
391
392 /*
393  * Contact a NAS (using finger) to check how many sessions this USER
394  * is currently running on it.
395  *
396  * Note that typically you run this code when you are in the middle of
397  * trying to login to a Cisco NAS on a given port. Because you are
398  * part way through a login when you do this, you can get inconsistent
399  * reports for that particular port about whether the user is
400  * currently logged in on it or not, so we ignore output which claims
401  * that the user is using that line currently.
402  *
403  * This is extremely Cisco specific -- finger formats appear to vary wildly.
404  * The format we're expecting is:
405
406     Line     User      Host(s)               Idle Location
407    0 con 0             idle                 never
408   18 vty 0   usr0      idle                    30 barley.cisco.com
409   19 vty 1   usr0      Virtual Exec             2
410   20 vty 2             idle                     0 barley.cisco.com
411
412  * Column zero contains a space or an asterisk character.  The line number
413  * starts at column 1 and is 3 digits wide.  User names start at column 13,
414  * with a maximum possible width of 10.
415  */
416
417 static int ckfinger TAC_ARGS((char *user, char *nas, struct identity *idp));
418
419 static int
420 ckfinger(user, nas, idp)
421 char *user, *nas;
422 struct identity *idp;
423 {
424     struct sockaddr_in sin;
425     struct servent *serv;
426     int count, s, bufsize;
427     char *buf, *p, *pn;
428     int incr = 4096, slop = 32;
429     u_long inaddr;
430     char *curport = portname(idp->NAS_port);
431     char *name;
432
433     /* The finger service, aka port 79 */
434     serv = getservbyname("finger", "tcp");
435     if (serv) {
436         sin.sin_port = serv->s_port;
437     } else {
438         sin.sin_port = 79;
439     }
440
441     /* Get IP addr for the NAS */
442     inaddr = inet_addr(nas);
443     if (inaddr != (u_long)-1) {
444         /* A dotted decimal address */
445         bcopy(&inaddr, &sin.sin_addr, sizeof(inaddr));
446         sin.sin_family = AF_INET;
447     } else {
448         struct hostent *host = gethostbyname(nas);
449
450         if (host == NULL) {
451             report(LOG_ERR, "ckfinger: gethostbyname %s failure: %s",
452                    nas, sys_errlist[errno]);
453             return (0);
454         }
455         bcopy(host->h_addr, &sin.sin_addr, host->h_length);
456         sin.sin_family = host->h_addrtype;
457     }
458
459     s = socket(AF_INET, SOCK_STREAM, 0);
460     if (s < 0) {
461         report(LOG_ERR, "ckfinger: socket: %s", sys_errlist[errno]);
462         return (0);
463     }
464     if (connect(s, (struct sockaddr *) & sin, sizeof(sin)) < 0) {
465         report(LOG_ERR, "ckfinger: connect failure %s", sys_errlist[errno]);
466         close(s);
467         return (0);
468     }
469     /* Read in the finger output into a single flat buffer */
470     buf = NULL;
471     bufsize = 0;
472     for (;;) {
473         int x;
474
475         buf = tac_realloc(buf, bufsize + incr + slop);
476         x = timed_read(s, buf + bufsize, incr, 10);
477         if (x <= 0) {
478             break;
479         }
480         bufsize += x;
481     }
482
483     /* Done talking here */
484     close(s);
485     buf[bufsize] = '\0';
486
487     if (bufsize <= 0) {
488         report(LOG_ERR, "ckfinger: finger failure");
489         free(buf);
490         return (0);
491     }
492     /* skip first line in buffer */
493     p = strchr(buf, '\n');
494     if (p) {
495         p++;
496     }
497     p = strchr(p, '\n');
498     if (p) {
499         p++;
500     }
501     /* Tally each time this user appears */
502     for (count = 0; p && *p; p = pn) {
503         int i, len, nmlen;
504         char nmbuf[11];
505
506         /* Find next line */
507         pn = strchr(p, '\n');
508         if (pn) {
509             ++pn;
510         }
511         /* Calculate line length */
512         if (pn) {
513             len = pn - p;
514         } else {
515             len = strlen(p);
516         }
517
518         /* Line too short -> ignore */
519         if (len < 14) {
520             continue;
521         }
522         /* Always ignore the NAS/port we're currently trying to login on. */
523         if (isdigit(*curport)) {
524             int thisport;
525
526             if (sscanf(p + 1, " %d", &thisport) == 1) {
527                 if ((atoi(curport) == thisport) &&
528                     !strcmp(idp->NAS_name, nas)) {
529
530                     if (debug & DEBUG_MAXSESS_FLAG) {
531                         report(LOG_DEBUG, "%s session on %s/%s discounted",
532                                user, idp->NAS_name, idp->NAS_port);
533                     }
534                     continue;
535                 }
536             }
537         }
538         /* Extract username, up to 10 chars wide, starting at char 13 */
539         nmlen = 0;
540         name = p + 13;
541         for (i = 0; *name && !isspace(*name) && (i < 10); i++) {
542             nmbuf[nmlen++] = *name++;
543         }
544         nmbuf[nmlen++] = '\0';
545
546         /* If name matches, up the count */
547         if (STREQ(user, nmbuf)) {
548             count++;
549
550             if (debug & DEBUG_MAXSESS_FLAG) {
551                 char c = *pn;
552
553                 *pn = '\0';
554                 report(LOG_DEBUG, "%s matches: %s", user, p);
555                 *pn = c;
556             }
557         }
558     }
559     free(buf);
560     return (count);
561 }
562
563 static int countusers_by_finger TAC_ARGS((struct identity *idp));
564
565 /*
566  * Verify how many sessions a user has according to the wholog file.
567  * Use finger to contact each NAS that wholog says has this user
568  * logged on.
569  */
570 static int
571 countusers_by_finger(idp)
572 struct identity *idp;
573 {
574     FILE *fp;
575     struct peruser pu;
576     int x, naddr, nsess, n;
577     char **addrs, *uname;
578
579     fp = fopen(wholog, "r+");
580     if (fp == NULL) {
581         return (0);
582     }
583     uname = idp->username;
584
585     /* Count sessions */
586     tac_lockfd(wholog, fileno(fp));
587     nsess = 0;
588     naddr = 0;
589     addrs = NULL;
590
591     while (fread(&pu, sizeof(pu), 1, fp) > 0) {
592         int dup;
593
594         /* Ignore records for everyone except this user */
595         if (strcmp(pu.username, uname)) {
596             continue;
597         }
598         /* Only check a given NAS once */
599         for (dup = 0, x = 0; x < naddr; ++x) {
600             if (STREQ(addrs[x], pu.NAS_name)) {
601                 dup = 1;
602                 break;
603             }
604         }
605         if (dup) {
606             continue;
607         }
608         /* Add this address to our list */
609         addrs = (char **) tac_realloc((char *) addrs,
610                                       (naddr + 1) * sizeof(char *));
611         addrs[naddr] = tac_strdup(pu.NAS_name);
612         naddr += 1;
613
614         /* Validate via finger */
615         if (debug & DEBUG_MAXSESS_FLAG) {
616             report(LOG_DEBUG, "Running finger on %s for user %s/%s",
617                    pu.NAS_name, uname, idp->NAS_port);
618         }
619         n = ckfinger(uname, pu.NAS_name, idp);
620
621         if (debug & DEBUG_MAXSESS_FLAG) {
622             report(LOG_DEBUG, "finger reports %d active session%s for %s on %s",
623                    n, (n == 1 ? "" : "s"), uname, pu.NAS_name);
624         }
625         nsess += n;
626     }
627
628     /* Clean up and return */
629     fclose(fp);
630     for (x = 0; x < naddr; ++x) {
631         free(addrs[x]);
632     }
633     free(addrs);
634
635     return (nsess);
636 }
637
638 static int countuser TAC_ARGS((struct identity *idp));
639
640 /*
641  * Estimate how many sessions a named user currently owns by looking in
642  * the wholog file.
643  */
644 static int
645 countuser(idp)
646 struct identity *idp;
647 {
648     FILE *fp;
649     struct peruser pu;
650     int nsess;
651
652     /* Access log */
653     fp = fopen(wholog, "r+");
654     if (fp == NULL) {
655         return (0);
656     }
657     /* Count sessions. Skip any session associated with the current port. */
658     tac_lockfd(wholog, fileno(fp));
659     nsess = 0;
660     while (fread(&pu, sizeof(pu), 1, fp) > 0) {
661
662         /* Current user */
663         if (strcmp(pu.username, idp->username)) {
664             continue;
665         }
666         /* skip current port on current NAS */
667         if (STREQ(portname(pu.NAS_port), portname(idp->NAS_port)) &&
668             STREQ(pu.NAS_name, idp->NAS_name)) {
669             continue;
670         }
671         nsess += 1;
672     }
673
674     /* Clean up and return */
675     fclose(fp);
676     return (nsess);
677 }
678
679 static int is_async TAC_ARGS((char *portname));
680
681 /*
682  * is_async()
683  * Tell if the named NAS port is an async-like device.
684  *
685  * Finger reports async users, but not ISDN ones (yay).  So we can do
686  * a "slow" double check for async, but not ISDN.
687  */
688 static int
689 is_async(portname)
690 char *portname;
691 {
692     if (isdigit(*portname) || !strncmp(portname, "Async", 5) ||
693         !strncmp(portname, "tty", 3)) {
694         return (1);
695     }
696     return (0);
697 }
698
699 int maxsess_check_count TAC_ARGS((char *user, struct author_data *data));
700
701 /*
702  * See if this user can have more sessions.
703  */
704 int
705 maxsess_check_count(user, data)
706 char *user;
707 struct author_data *data;
708 {
709     int sess, maxsess;
710     struct identity *id;
711
712     /* No max session configured--don't check */
713     id = data->id;
714
715     maxsess = cfg_get_intvalue(S_user, user, S_maxsess, TAC_PLUS_RECURSE);
716     if (!maxsess) {
717         if (debug & (DEBUG_MAXSESS_FLAG | DEBUG_AUTHOR_FLAG)) {
718             report(LOG_DEBUG, "%s may run an unlimited number of sessions",
719                    user);
720         }
721         return (0);
722     }
723     /* Count sessions for this user by looking in our wholog file */
724     sess = countuser(id);
725
726     if (debug & (DEBUG_MAXSESS_FLAG | DEBUG_AUTHOR_FLAG)) {
727         report(LOG_DEBUG,
728                "user %s is running %d out of a maximum of %d sessions",
729                user, sess, maxsess);
730     }
731     if ((sess >= maxsess) && is_async(id->NAS_port)) {
732         /* If we have finger available, double check this count by contacting
733          * the NAS */
734         sess = countusers_by_finger(id);
735     }
736     /* If it's really too high, don't authorize more services */
737     if (sess >= maxsess) {
738         char buf[80];
739
740         sprintf(buf,
741                 "Login failed; too many active sessions (%d maximum)",
742                 maxsess);
743
744         data->msg = tac_strdup(buf);
745
746         if (debug & (DEBUG_AUTHOR_FLAG | DEBUG_MAXSESS_FLAG)) {
747             report(LOG_DEBUG, data->msg);
748         }
749         data->status = AUTHOR_STATUS_FAIL;
750         data->output_args = NULL;
751         data->num_out_args = 0;
752         return (1);
753     }
754     return (0);
755 }
756
757 #else /* MAXSESS */
758
759 TAC_SOURCEFILE_EMPTY
760
761 #endif /* MAXSESS */