2 Copyright (c) 1995-1998 by Cisco systems, Inc.
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.
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.
27 #include <sys/socket.h>
28 #include <netinet/in.h>
29 #include <arpa/inet.h>
30 #include <sys/types.h>
37 #ifdef HAVE_SYS_TIME_H
54 void maxsess_loginit TAC_ARGS((void));
60 /* This is a shared file used to maintain a record of who's on
62 #define WHOLOG_DEFAULT "/var/log/tac_who.log"
66 * This is state kept per user/session
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 */
76 char *wholog = WHOLOG_DEFAULT;
78 * initialize wholog file for tracking of user logins/logouts from
86 fd = open(wholog, O_CREAT | O_RDWR, 0600);
88 report(LOG_ERR, "Can't create: %s", wholog);
90 if (debug & DEBUG_MAXSESS_FLAG) {
91 report(LOG_DEBUG, "Initialize %s", wholog);
97 static char *portname TAC_ARGS((char *oldport));
100 * Given a port description, return it in a canonical format.
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".
112 if (!strncmp(p, "Async", 5) || !strncmp(p, "tty", 3)) {
113 while (!isdigit(*p) && *p) {
118 if (debug & DEBUG_ACCT_FLAG)
119 report(LOG_DEBUG, "Maxsess -- Malformed portname: %s", oldport);
125 static void write_record TAC_ARGS((char *name, FILE *fp, void *buf, int size, long int offset));
128 * Seek to offset and write a buffer into the file pointed to by fp
131 write_record(name, fp, buf, size, offset)
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]);
142 if (fwrite(buf, size, 1, fp) != 1) {
143 report(LOG_ERR, "%s fd=%d Cannot write %d bytes",
144 name, fileno(fp), size);
148 static void process_stop_record TAC_ARGS((struct identity *idp));
151 process_stop_record(idp)
152 struct identity *idp;
157 char *nasport = portname(idp->NAS_port);
159 /* If we can't access the file, skip all checks. */
160 fp = fopen(wholog, "r+");
162 report(LOG_ERR, "Can't open %s for updating", wholog);
165 tac_lockfd(wholog, fileno(fp));
167 for (recnum = 0; 1; recnum++) {
169 fseek(fp, recnum * sizeof(struct peruser), SEEK_SET);
171 if (fread(&pu, sizeof(pu), 1, fp) <= 0) {
175 /* A match for this record? */
176 if (!(STREQ(pu.NAS_name, idp->NAS_name) &&
177 STREQ(pu.NAS_port, nasport))) {
181 /* A match. Zero out this record */
182 bzero(&pu, sizeof(pu));
184 write_record(wholog, fp, &pu, sizeof(pu),
185 recnum * sizeof(struct peruser));
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);
195 static void process_start_record TAC_ARGS((struct identity *idp));
198 process_start_record(idp)
199 struct identity *idp;
204 char *nasport = portname(idp->NAS_port);
208 /* If we can't access the file, skip all checks. */
209 fp = fopen(wholog, "r+");
211 report(LOG_ERR, "Can't open %s for updating", wholog);
214 tac_lockfd(wholog, fileno(fp));
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)) {
222 /* Found a free slot on the way */
223 if (pu.username[0] == '\0') {
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.
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);
242 if (debug & DEBUG_MAXSESS_FLAG) {
244 "START record -- overwrite existing %s entry %d for %s %s/%s",
245 wholog, foundrec, pu.NAS_name, pu.username, pu.NAS_port);
247 write_record(wholog, fp, &pu, sizeof(pu),
248 foundrec * sizeof(struct peruser));
253 /* Not found in DB, but we have a free slot */
256 write_record(wholog, fp, &pu, sizeof(pu),
257 freerec * sizeof(struct peruser));
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);
267 /* No free slot. Add record at the end */
268 write_record(wholog, fp, &pu, sizeof(pu),
269 recnum * sizeof(struct peruser));
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);
279 void loguser TAC_ARGS((struct acct_rec *rec));
282 * Given a start or a stop accounting record, update the file of
283 * records which tracks who's logged on and where.
287 struct acct_rec *rec;
289 struct identity *idp;
292 /* We're only interested in start/stop records */
293 if ((rec->acct_type != ACCT_TYPE_START) &&
294 (rec->acct_type != ACCT_TYPE_STOP)) {
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) {
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
310 switch (rec->acct_type) {
311 case ACCT_TYPE_START:
312 process_start_record(idp);
316 process_stop_record(idp);
321 /* Read up to n bytes from descriptor fd into array ptr with timeout t
324 * Return -1 on error, eof or timeout. Otherwise return number of
327 static int timed_read TAC_ARGS((int fd, void *ptr, int nbytes, int timeout));
330 timed_read(fd, ptr, nbytes, timeout)
337 fd_set readfds, exceptfds;
340 tout.tv_sec = timeout;
344 FD_SET(fd, &readfds);
347 FD_SET(fd, &exceptfds);
350 int status = select(fd + 1, &readfds, (fd_set *) NULL,
354 report(LOG_DEBUG, "%s: timeout reading fd %d", session.peer, fd);
360 report(LOG_DEBUG, "%s: error in select %s fd %d",
361 session.peer, sys_errlist[errno], fd);
364 if (FD_ISSET(fd, &exceptfds)) {
365 report(LOG_DEBUG, "%s: exception on fd %d",
369 if (!FD_ISSET(fd, &readfds)) {
370 report(LOG_DEBUG, "%s: spurious return from select",
374 nread = read(fd, ptr, nbytes);
377 if (errno == EINTR) {
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 */
385 return (-1); /* eof */
393 * Contact a NAS (using finger) to check how many sessions this USER
394 * is currently running on it.
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.
403 * This is extremely Cisco specific -- finger formats appear to vary wildly.
404 * The format we're expecting is:
406 Line User Host(s) Idle Location
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
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.
417 static int ckfinger TAC_ARGS((char *user, char *nas, struct identity *idp));
420 ckfinger(user, nas, idp)
422 struct identity *idp;
424 struct sockaddr_in sin;
425 struct servent *serv;
426 int count, s, bufsize;
428 int incr = 4096, slop = 32;
430 char *curport = portname(idp->NAS_port);
433 /* The finger service, aka port 79 */
434 serv = getservbyname("finger", "tcp");
436 sin.sin_port = serv->s_port;
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;
448 struct hostent *host = gethostbyname(nas);
451 report(LOG_ERR, "ckfinger: gethostbyname %s failure: %s",
452 nas, sys_errlist[errno]);
455 bcopy(host->h_addr, &sin.sin_addr, host->h_length);
456 sin.sin_family = host->h_addrtype;
459 s = socket(AF_INET, SOCK_STREAM, 0);
461 report(LOG_ERR, "ckfinger: socket: %s", sys_errlist[errno]);
464 if (connect(s, (struct sockaddr *) & sin, sizeof(sin)) < 0) {
465 report(LOG_ERR, "ckfinger: connect failure %s", sys_errlist[errno]);
469 /* Read in the finger output into a single flat buffer */
475 buf = tac_realloc(buf, bufsize + incr + slop);
476 x = timed_read(s, buf + bufsize, incr, 10);
483 /* Done talking here */
488 report(LOG_ERR, "ckfinger: finger failure");
492 /* skip first line in buffer */
493 p = strchr(buf, '\n');
501 /* Tally each time this user appears */
502 for (count = 0; p && *p; p = pn) {
507 pn = strchr(p, '\n');
511 /* Calculate line length */
518 /* Line too short -> ignore */
522 /* Always ignore the NAS/port we're currently trying to login on. */
523 if (isdigit(*curport)) {
526 if (sscanf(p + 1, " %d", &thisport) == 1) {
527 if ((atoi(curport) == thisport) &&
528 !strcmp(idp->NAS_name, nas)) {
530 if (debug & DEBUG_MAXSESS_FLAG) {
531 report(LOG_DEBUG, "%s session on %s/%s discounted",
532 user, idp->NAS_name, idp->NAS_port);
538 /* Extract username, up to 10 chars wide, starting at char 13 */
541 for (i = 0; *name && !isspace(*name) && (i < 10); i++) {
542 nmbuf[nmlen++] = *name++;
544 nmbuf[nmlen++] = '\0';
546 /* If name matches, up the count */
547 if (STREQ(user, nmbuf)) {
550 if (debug & DEBUG_MAXSESS_FLAG) {
554 report(LOG_DEBUG, "%s matches: %s", user, p);
563 static int countusers_by_finger TAC_ARGS((struct identity *idp));
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
571 countusers_by_finger(idp)
572 struct identity *idp;
576 int x, naddr, nsess, n;
577 char **addrs, *uname;
579 fp = fopen(wholog, "r+");
583 uname = idp->username;
586 tac_lockfd(wholog, fileno(fp));
591 while (fread(&pu, sizeof(pu), 1, fp) > 0) {
594 /* Ignore records for everyone except this user */
595 if (strcmp(pu.username, uname)) {
598 /* Only check a given NAS once */
599 for (dup = 0, x = 0; x < naddr; ++x) {
600 if (STREQ(addrs[x], pu.NAS_name)) {
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);
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);
619 n = ckfinger(uname, pu.NAS_name, idp);
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);
628 /* Clean up and return */
630 for (x = 0; x < naddr; ++x) {
638 static int countuser TAC_ARGS((struct identity *idp));
641 * Estimate how many sessions a named user currently owns by looking in
646 struct identity *idp;
653 fp = fopen(wholog, "r+");
657 /* Count sessions. Skip any session associated with the current port. */
658 tac_lockfd(wholog, fileno(fp));
660 while (fread(&pu, sizeof(pu), 1, fp) > 0) {
663 if (strcmp(pu.username, idp->username)) {
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)) {
674 /* Clean up and return */
679 static int is_async TAC_ARGS((char *portname));
683 * Tell if the named NAS port is an async-like device.
685 * Finger reports async users, but not ISDN ones (yay). So we can do
686 * a "slow" double check for async, but not ISDN.
692 if (isdigit(*portname) || !strncmp(portname, "Async", 5) ||
693 !strncmp(portname, "tty", 3)) {
699 int maxsess_check_count TAC_ARGS((char *user, struct author_data *data));
702 * See if this user can have more sessions.
705 maxsess_check_count(user, data)
707 struct author_data *data;
712 /* No max session configured--don't check */
715 maxsess = cfg_get_intvalue(S_user, user, S_maxsess, TAC_PLUS_RECURSE);
717 if (debug & (DEBUG_MAXSESS_FLAG | DEBUG_AUTHOR_FLAG)) {
718 report(LOG_DEBUG, "%s may run an unlimited number of sessions",
723 /* Count sessions for this user by looking in our wholog file */
724 sess = countuser(id);
726 if (debug & (DEBUG_MAXSESS_FLAG | DEBUG_AUTHOR_FLAG)) {
728 "user %s is running %d out of a maximum of %d sessions",
729 user, sess, maxsess);
731 if ((sess >= maxsess) && is_async(id->NAS_port)) {
732 /* If we have finger available, double check this count by contacting
734 sess = countusers_by_finger(id);
736 /* If it's really too high, don't authorize more services */
737 if (sess >= maxsess) {
741 "Login failed; too many active sessions (%d maximum)",
744 data->msg = tac_strdup(buf);
746 if (debug & (DEBUG_AUTHOR_FLAG | DEBUG_MAXSESS_FLAG)) {
747 report(LOG_DEBUG, data->msg);
749 data->status = AUTHOR_STATUS_FAIL;
750 data->output_args = NULL;
751 data->num_out_args = 0;