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.
23 char *wholog = WHOLOG_DEFAULT;
25 * initialize wholog file for tracking of user logins/logouts from
33 fd = open(wholog, O_CREAT | O_RDWR, 0600);
35 report(LOG_ERR, "Can't create: %s", wholog);
37 if (debug & DEBUG_MAXSESS_FLAG) {
38 report(LOG_DEBUG, "Initialize %s", wholog);
45 * Given a port description, return it in a canonical format.
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
57 if (!strncmp(p, "Async", 5) || !strncmp(p, "tty", 3)) {
58 while (!isdigit(*p) && *p) {
63 if (debug & DEBUG_ACCT_FLAG)
64 report(LOG_DEBUG, "Maxsess -- Malformed portname: %s", oldport);
71 * Seek to offset and write a buffer into the file pointed to by fp
74 write_record(name, fp, buf, size, offset)
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]);
85 if (fwrite(buf, size, 1, fp) != 1) {
86 report(LOG_ERR, "%s fd=%d Cannot write %d bytes",
87 name, fileno(fp), size);
92 process_stop_record(idp)
98 char *nasport = portname(idp->NAS_port);
100 /* If we can't access the file, skip all checks. */
101 fp = fopen(wholog, "r+");
103 report(LOG_ERR, "Can't open %s for updating", wholog);
106 tac_lockfd(wholog, fileno(fp));
108 for (recnum = 0; 1; recnum++) {
110 fseek(fp, recnum * sizeof(struct peruser), SEEK_SET);
112 if (fread(&pu, sizeof(pu), 1, fp) <= 0) {
116 /* A match for this record? */
117 if (!(STREQ(pu.NAS_name, idp->NAS_name) &&
118 STREQ(pu.NAS_port, nasport))) {
122 /* A match. Zero out this record */
123 bzero(&pu, sizeof(pu));
125 write_record(wholog, fp, &pu, sizeof(pu),
126 recnum * sizeof(struct peruser));
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);
137 process_start_record(idp)
138 struct identity *idp;
143 char *nasport = portname(idp->NAS_port);
147 /* If we can't access the file, skip all checks. */
148 fp = fopen(wholog, "r+");
150 report(LOG_ERR, "Can't open %s for updating", wholog);
153 tac_lockfd(wholog, fileno(fp));
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)) {
161 /* Found a free slot on the way */
162 if (pu.username[0] == '\0') {
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.
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);
181 if (debug & DEBUG_MAXSESS_FLAG) {
183 "START record -- overwrite existing %s entry %d for %s %s/%s",
184 wholog, foundrec, pu.NAS_name, pu.username, pu.NAS_port);
186 write_record(wholog, fp, &pu, sizeof(pu),
187 foundrec * sizeof(struct peruser));
192 /* Not found in DB, but we have a free slot */
195 write_record(wholog, fp, &pu, sizeof(pu),
196 freerec * sizeof(struct peruser));
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);
206 /* No free slot. Add record at the end */
207 write_record(wholog, fp, &pu, sizeof(pu),
208 recnum * sizeof(struct peruser));
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);
219 * Given a start or a stop accounting record, update the file of
220 * records which tracks who's logged on and where.
223 struct acct_rec *rec;
225 struct identity *idp;
228 /* We're only interested in start/stop records */
229 if ((rec->acct_type != ACCT_TYPE_START) &&
230 (rec->acct_type != ACCT_TYPE_STOP)) {
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) {
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
246 switch (rec->acct_type) {
247 case ACCT_TYPE_START:
248 process_start_record(idp);
252 process_stop_record(idp);
257 /* Read up to n bytes from descriptor fd into array ptr with timeout t
260 * Return -1 on error, eof or timeout. Otherwise return number of
264 timed_read(fd, ptr, nbytes, timeout)
271 fd_set readfds, exceptfds;
274 tout.tv_sec = timeout;
278 FD_SET(fd, &readfds);
281 FD_SET(fd, &exceptfds);
284 int status = select(fd + 1, &readfds, (fd_set *) NULL,
288 report(LOG_DEBUG, "%s: timeout reading fd %d", session.peer, fd);
294 report(LOG_DEBUG, "%s: error in select %s fd %d",
295 session.peer, sys_errlist[errno], fd);
298 if (FD_ISSET(fd, &exceptfds)) {
299 report(LOG_DEBUG, "%s: exception on fd %d",
303 if (!FD_ISSET(fd, &readfds)) {
304 report(LOG_DEBUG, "%s: spurious return from select",
308 nread = read(fd, ptr, nbytes);
311 if (errno == EINTR) {
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 */
319 return (-1); /* eof */
327 * Contact a NAS (using finger) to check how many sessions this USER
328 * is currently running on it.
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.
337 * This is extremely Cisco specific -- finger formats appear to vary wildly.
338 * The format we're expecting is:
340 Line User Host(s) Idle Location
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
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.
352 ckfinger(user, nas, idp)
354 struct identity *idp;
356 struct sockaddr_in sin;
357 struct servent *serv;
358 int count, s, bufsize;
360 int incr = 4096, slop = 32;
362 char *curport = portname(idp->NAS_port);
365 /* The finger service, aka port 79 */
366 serv = getservbyname("finger", "tcp");
368 sin.sin_port = serv->s_port;
373 /* Get IP addr for the NAS */
374 inaddr = inet_addr(nas);
376 /* A dotted decimal address */
377 bcopy(&inaddr, &sin.sin_addr, sizeof(inaddr));
378 sin.sin_family = AF_INET;
380 struct hostent *host = gethostbyname(nas);
383 report(LOG_ERR, "ckfinger: gethostbyname %s failure: %s",
384 nas, sys_errlist[errno]);
387 bcopy(host->h_addr, &sin.sin_addr, host->h_length);
388 sin.sin_family = host->h_addrtype;
391 s = socket(AF_INET, SOCK_STREAM, 0);
393 report(LOG_ERR, "ckfinger: socket: %s", sys_errlist[errno]);
396 if (connect(s, (struct sockaddr *) & sin, sizeof(sin)) < 0) {
397 report(LOG_ERR, "ckfinger: connect failure %s", sys_errlist[errno]);
401 /* Read in the finger output into a single flat buffer */
407 buf = tac_realloc(buf, bufsize + incr + slop);
408 x = timed_read(s, buf + bufsize, incr, 10);
415 /* Done talking here */
420 report(LOG_ERR, "ckfinger: finger failure");
424 /* skip first line in buffer */
425 p = strchr(buf, '\n');
433 /* Tally each time this user appears */
434 for (count = 0; p && *p; p = pn) {
439 pn = strchr(p, '\n');
443 /* Calculate line length */
450 /* Line too short -> ignore */
454 /* Always ignore the NAS/port we're currently trying to login on. */
455 if (isdigit(*curport)) {
458 if (sscanf(p + 1, " %d", &thisport) == 1) {
459 if ((atoi(curport) == thisport) &&
460 !strcmp(idp->NAS_name, nas)) {
462 if (debug & DEBUG_MAXSESS_FLAG) {
463 report(LOG_DEBUG, "%s session on %s/%s discounted",
464 user, idp->NAS_name, idp->NAS_port);
470 /* Extract username, up to 10 chars wide, starting at char 13 */
473 for (i = 0; *name && !isspace(*name) && (i < 10); i++) {
474 nmbuf[nmlen++] = *name++;
476 nmbuf[nmlen++] = '\0';
478 /* If name matches, up the count */
479 if (STREQ(user, nmbuf)) {
482 if (debug & DEBUG_MAXSESS_FLAG) {
486 report(LOG_DEBUG, "%s matches: %s", user, p);
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
501 countusers_by_finger(idp)
502 struct identity *idp;
506 int x, naddr, nsess, n;
507 char **addrs, *uname;
509 fp = fopen(wholog, "r+");
513 uname = idp->username;
516 tac_lockfd(wholog, fileno(fp));
521 while (fread(&pu, sizeof(pu), 1, fp) > 0) {
524 /* Ignore records for everyone except this user */
525 if (strcmp(pu.username, uname)) {
528 /* Only check a given NAS once */
529 for (dup = 0, x = 0; x < naddr; ++x) {
530 if (STREQ(addrs[x], pu.NAS_name)) {
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);
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);
549 n = ckfinger(uname, pu.NAS_name, idp);
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);
558 /* Clean up and return */
560 for (x = 0; x < naddr; ++x) {
569 * Estimate how many sessions a named user currently owns by looking in
574 struct identity *idp;
581 fp = fopen(wholog, "r+");
585 /* Count sessions. Skip any session associated with the current port. */
586 tac_lockfd(wholog, fileno(fp));
588 while (fread(&pu, sizeof(pu), 1, fp) > 0) {
591 if (strcmp(pu.username, idp->username)) {
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)) {
602 /* Clean up and return */
609 * Tell if the named NAS port is an async-like device.
611 * Finger reports async users, but not ISDN ones (yay). So we can do
612 * a "slow" double check for async, but not ISDN.
618 if (isdigit(*portname) || !strncmp(portname, "Async", 5) ||
619 !strncmp(portname, "tty", 3)) {
626 * See if this user can have more sessions.
629 maxsess_check_count(user, data)
631 struct author_data *data;
636 /* No max session configured--don't check */
639 maxsess = cfg_get_intvalue(user, TAC_IS_USER, S_maxsess, TAC_PLUS_RECURSE);
641 if (debug & (DEBUG_MAXSESS_FLAG | DEBUG_AUTHOR_FLAG)) {
642 report(LOG_DEBUG, "%s may run an unlimited number of sessions",
647 /* Count sessions for this user by looking in our wholog file */
648 sess = countuser(id);
650 if (debug & (DEBUG_MAXSESS_FLAG | DEBUG_AUTHOR_FLAG)) {
652 "user %s is running %d out of a maximum of %d sessions",
653 user, sess, maxsess);
655 if ((sess >= maxsess) && is_async(id->NAS_port)) {
656 /* If we have finger available, double check this count by contacting
658 sess = countusers_by_finger(id);
660 /* If it's really too high, don't authorize more services */
661 if (sess >= maxsess) {
665 "Login failed; too many active sessions (%d maximum)",
668 data->msg = tac_strdup(buf);
670 if (debug & (DEBUG_AUTHOR_FLAG | DEBUG_MAXSESS_FLAG)) {
671 report(LOG_DEBUG, data->msg);
673 data->status = AUTHOR_STATUS_FAIL;
674 data->output_args = NULL;
675 data->num_out_args = 0;
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
690 static int dummy = 0;