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