home *** CD-ROM | disk | FTP | other *** search
/ ftp.shrubbery.net / 2015-02-07.ftp.shrubbery.net.tar / ftp.shrubbery.net / pub / tac_plus / tacacs+-F4.0.4.27a.tar.gz / tacacs+-F4.0.4.27a.tar / tacacs+-F4.0.4.27a / maxsess.c < prev    next >
C/C++ Source or Header  |  2012-01-23  |  15KB  |  604 lines

  1. /*
  2.  * $Id: maxsess.c,v 1.12 2009-07-16 18:13:19 heas Exp $
  3.  *
  4.  * Copyright (c) 1995-1998 by Cisco systems, Inc.
  5.  *
  6.  * Permission to use, copy, modify, and distribute this software for
  7.  * any purpose and without fee is hereby granted, provided that this
  8.  * copyright and permission notice appear on all copies of the
  9.  * software and supporting documentation, the name of Cisco Systems,
  10.  * Inc. not be used in advertising or publicity pertaining to
  11.  * distribution of the program without specific prior permission, and
  12.  * notice be given in supporting documentation that modification,
  13.  * copying and distribution is by permission of Cisco Systems, Inc.
  14.  *
  15.  * Cisco Systems, Inc. makes no representations about the suitability
  16.  * of this software for any purpose.  THIS SOFTWARE IS PROVIDED ``AS
  17.  * IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
  18.  * WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
  19.  * FITNESS FOR A PARTICULAR PURPOSE.
  20.  */
  21.  
  22. #include "tac_plus.h"
  23.  
  24. #if HAVE_CTYPE_H
  25. # include <ctype.h>
  26. #endif
  27. #include <poll.h>
  28. #include <signal.h>
  29.  
  30. char *wholog = TACPLUS_WHOLOGFILE;
  31.  
  32. static int timed_read(int, unsigned char *, int, int);
  33.  
  34. /*
  35.  * initialize wholog file for tracking of user logins/logouts from
  36.  * accounting records.
  37.  */
  38. void
  39. maxsess_loginit(void)
  40. {
  41.     int fd;
  42.  
  43.     fd = open(wholog, O_CREAT | O_RDWR, 0600);
  44.     if (fd < 0) {
  45.     report(LOG_ERR, "Can't create: %s", wholog);
  46.     } else {
  47.     if (debug & DEBUG_MAXSESS_FLAG) {
  48.         report(LOG_DEBUG, "Initialize %s", wholog);
  49.     }
  50.     close(fd);
  51.     }
  52. }
  53.  
  54. /*
  55.  * Given a port description, return it in a canonical format.
  56.  *
  57.  * This piece of goo is to cover the fact that an async line in EXEC
  58.  * mode is known as "ttyXX", but the same line doing PPP or SLIP is
  59.  * known as "AsyncXX".
  60.  */
  61. static char *
  62. portname(char *oldport)
  63. {
  64.     char *p = oldport;
  65.  
  66.     if (!strncmp(p, "Async", 5) || !strncmp(p, "tty", 3)) {
  67.     while (!isdigit((int) *p) && *p) {
  68.         ++p;
  69.     }
  70.     }
  71.     if (!*p) {
  72.     if (debug & DEBUG_ACCT_FLAG)
  73.         report(LOG_DEBUG, "Maxsess -- Malformed portname: %s", oldport);
  74.     return(oldport);
  75.     }
  76.     return(p);
  77. }
  78.  
  79. /*
  80.  * Seek to offset and write a buffer into the file pointed to by fp
  81.  */
  82. static void
  83. write_record(char *name, FILE *fp, void *buf, int size, long offset)
  84. {
  85.     if (fseek(fp, offset, SEEK_SET) < 0) {
  86.     report(LOG_ERR, "%s fd=%d Cannot seek to %d %s",
  87.            name, fileno(fp), offset, strerror(errno));
  88.     }
  89.     if (fwrite(buf, size, 1, fp) != 1) {
  90.     report(LOG_ERR, "%s fd=%d Cannot write %d bytes",
  91.            name, fileno(fp), size);
  92.     }
  93. }
  94.  
  95. static void
  96. process_stop_record(struct identity *idp)
  97. {
  98.     int recnum;
  99.     struct peruser pu;
  100.     FILE *fp;
  101.     char *nasport = portname(idp->NAS_port);
  102.  
  103.     /* If we can't access the file, skip all checks. */
  104.     fp = fopen(wholog, "r+");
  105.     if (fp == NULL) {
  106.     report(LOG_ERR, "Can't open %s for updating", wholog);
  107.     return;
  108.     }
  109.     tac_lockfd(wholog, fileno(fp));
  110.  
  111.     for (recnum = 0; 1; recnum++) {
  112.     fseek(fp, recnum * sizeof(struct peruser), SEEK_SET);
  113.  
  114.     if (fread(&pu, sizeof(pu), 1, fp) <= 0) {
  115.         break;
  116.     }
  117.  
  118.     /* A match for this record? */
  119.     if (!(STREQ(pu.NAS_name, idp->NAS_name) &&
  120.         STREQ(pu.NAS_port, nasport))) {
  121.         continue;
  122.     }
  123.  
  124.     /* A match. Zero out this record */
  125.     memset(&pu, 0, sizeof(pu));
  126.  
  127.     write_record(wholog, fp, &pu, sizeof(pu),
  128.              recnum * sizeof(struct peruser));
  129.  
  130.     if (debug & DEBUG_MAXSESS_FLAG) {
  131.         report(LOG_DEBUG, "STOP record -- clear %s entry %d for %s/%s",
  132.            wholog, recnum, idp->username, nasport);
  133.     }
  134.     }
  135.     fclose(fp);
  136. }
  137.  
  138. static void
  139. process_start_record(struct identity *idp)
  140. {
  141.     int recnum;
  142.     int foundrec = -1;
  143.     int freerec = -1;
  144.     char *nasport = portname(idp->NAS_port);
  145.     struct peruser pu;
  146.     FILE *fp;
  147.  
  148.     /* If we can't access the file, skip all checks. */
  149.     fp = fopen(wholog, "r+");
  150.     if (fp == NULL) {
  151.     report(LOG_ERR, "Can't open %s for updating", wholog);
  152.     return;
  153.     }
  154.     tac_lockfd(wholog, fileno(fp));
  155.  
  156.     for (recnum = 0; (fread(&pu, sizeof(pu), 1, fp) > 0); recnum++) {
  157.     /* Match for this NAS/Port record? */
  158.     if (STREQ(pu.NAS_name, idp->NAS_name) && STREQ(pu.NAS_port, nasport)) {
  159.         foundrec = recnum;
  160.         break;
  161.     }
  162.     /* Found a free slot on the way */
  163.     if (pu.username[0] == '\0') {
  164.         freerec = recnum;
  165.     }
  166.     }
  167.  
  168.     /*
  169.      * This is a START record, so write a new record or update the existing
  170.      * one.  Note that we zero the memory, so the strncpy()'s will truncate
  171.      * long names and always leave a null-terminated string.
  172.      */
  173.     memset(&pu, 0, sizeof(pu));
  174.     strncpy(pu.username, idp->username, sizeof(pu.username) - 1);
  175.     strncpy(pu.NAS_name, idp->NAS_name, sizeof(pu.NAS_name) - 1);
  176.     strncpy(pu.NAS_port, nasport, sizeof(pu.NAS_port) - 1);
  177.     strncpy(pu.NAC_address, idp->NAC_address, sizeof(pu.NAC_address) - 1);
  178.  
  179.     /* Already in DB? */
  180.     if (foundrec >= 0) {
  181.     if (debug & DEBUG_MAXSESS_FLAG) {
  182.         report(LOG_DEBUG,
  183.            "START record -- overwrite existing %s entry %d for %s "
  184.            "%s/%s", wholog, foundrec, pu.NAS_name, pu.username,
  185.            pu.NAS_port);
  186.     }
  187.     write_record(wholog, fp, &pu, sizeof(pu),
  188.              foundrec * sizeof(struct peruser));
  189.     fclose(fp);
  190.     return;
  191.     }
  192.  
  193.     /* Not found in DB, but we have a free slot */
  194.     if (freerec >= 0) {
  195.  
  196.     write_record(wholog, fp, &pu, sizeof(pu),
  197.              freerec * sizeof(struct peruser));
  198.  
  199.     if (debug & DEBUG_MAXSESS_FLAG) {
  200.         report(LOG_DEBUG, "START record -- %s entry %d for %s %s/%s added",
  201.            wholog, freerec, pu.NAS_name, pu.username, pu.NAS_port);
  202.     }
  203.     fclose(fp);
  204.     return;
  205.     }
  206.  
  207.     /* No free slot. Add record at the end */
  208.     write_record(wholog, fp, &pu, sizeof(pu),
  209.          recnum * sizeof(struct peruser));
  210.  
  211.     if (debug & DEBUG_MAXSESS_FLAG) {
  212.     report(LOG_DEBUG, "START record -- %s entry %d for %s %s/%s added",
  213.            wholog, recnum, pu.NAS_name, pu.username, pu.NAS_port);
  214.     }
  215.     fclose(fp);
  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. void
  223. loguser(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. /*
  258.  * Read up to n bytes from descriptor fd into array ptr with timeout t
  259.  * seconds.
  260.  *
  261.  * Return -1 on error, eof or timeout. Otherwise return number of bytes read.
  262.  */
  263. static int
  264. timed_read(int fd, unsigned char *ptr, int nbytes, int timeout)
  265. {
  266.     int nread;
  267.     struct pollfd pfds;
  268.  
  269.     pfds.fd = fd;
  270.     pfds.events = POLLIN | POLLERR | POLLHUP | POLLNVAL;
  271.  
  272.     while (1) {
  273.     int status = poll(&pfds, 1, timeout * 1000);
  274.  
  275.     if (status == 0) {
  276.         status = errno;
  277.         report(LOG_DEBUG, "%s: timeout reading fd %d", session.peer, fd);
  278.         errno = status;
  279.         return(-1);
  280.     }
  281.     if (status < 0) {
  282.         if (errno == EINTR)
  283.         continue;
  284.         status = errno;
  285.         report(LOG_DEBUG, "%s: error in poll %s fd %d", session.peer,
  286.            strerror(errno), fd);
  287.         errno = status;
  288.         return(-1);
  289.     }
  290.     if (pfds.revents & (POLLERR | POLLHUP | POLLNVAL)) {
  291.         status = errno;
  292.         report(LOG_DEBUG, "%s: exception on fd %d", session.peer, fd);
  293.         errno = status;
  294.         return(-1);
  295.     }
  296.     if (!(pfds.revents & POLLIN)) {
  297.         status = errno;
  298.         report(LOG_DEBUG, "%s: spurious return from poll", session.peer);
  299.         errno = status;
  300.         continue;
  301.     }
  302.     nread = read(fd, ptr, nbytes);
  303.  
  304.     if (nread < 0) {
  305.         if (errno == EINTR) {
  306.         continue;
  307.         }
  308.         status = errno;
  309.         report(LOG_DEBUG, "%s %s: error reading fd %d nread=%d %s",
  310.            session.peer, session.port, fd, nread, strerror(errno));
  311.         errno = status;
  312.         return(-1);        /* error */
  313.     }
  314.     if (nread == 0) {
  315.         errno = 0;
  316.         return(-1);        /* eof */
  317.     }
  318.     return(nread);
  319.     }
  320.     /* NOTREACHED */
  321. }
  322.  
  323. /*
  324.  * Contact a NAS (using finger) to check how many sessions this USER
  325.  * is currently running on it.
  326.  *
  327.  * Note that typically you run this code when you are in the middle of
  328.  * trying to login to a Cisco NAS on a given port. Because you are
  329.  * part way through a login when you do this, you can get inconsistent
  330.  * reports for that particular port about whether the user is
  331.  * currently logged in on it or not, so we ignore output which claims
  332.  * that the user is using that line currently.
  333.  *
  334.  * This is extremely Cisco specific -- finger formats appear to vary wildly.
  335.  * The format we're expecting is:
  336.  
  337.     Line     User      Host(s)            Idle Location
  338.    0 con 0           idle            never
  339.   18 vty 0   usr0      idle               30 barley.cisco.com
  340.   19 vty 1   usr0      Virtual Exec        2
  341.   20 vty 2           idle            0 barley.cisco.com
  342.  
  343.  * Column zero contains a space or an asterisk character.  The line number
  344.  * starts at column 1 and is 3 digits wide.  User names start at column 13,
  345.  * with a maximum possible width of 10.
  346.  *
  347.  * Returns the number of sessions/connections, or zero on error.
  348.  */
  349. static int
  350. ckfinger(char *user, char *nas, struct identity *idp)
  351. {
  352.     struct addrinfo hints, *res, *resp;
  353.     int count, s, bufsize, ecode;
  354.     char *buf, *p, *pn;
  355.     int incr = 4096, slop = 32;
  356.     char *curport = portname(idp->NAS_port);
  357.     char *name;
  358.  
  359.     memset(&hints, 0, sizeof(struct addrinfo));
  360.     hints.ai_family = PF_UNSPEC;
  361.     hints.ai_socktype = SOCK_STREAM;
  362.  
  363.     if ((ecode = getaddrinfo(nas, "finger", &hints, &res)) != 0) {
  364.     report(LOG_ERR, "ckfinger: getaddrinfo %s failure: %s", nas,
  365.            gai_strerror(ecode));
  366.     return(0);
  367.     }
  368.  
  369.     ecode = 0;
  370.     for (resp = res; resp != NULL; resp = resp->ai_next) {
  371.     s = socket(resp->ai_family, resp->ai_socktype, resp->ai_protocol);
  372.     if (s < 0) {
  373.         if (errno == EAFNOSUPPORT || errno == EPROTONOSUPPORT)
  374.         continue;
  375.         report(LOG_ERR, "ckfinger: socket: %s", strerror(errno));
  376.         freeaddrinfo(res);
  377.         return(0);
  378.     }
  379.     if ((ecode = connect(s, resp->ai_addr, res->ai_addrlen)) < 0) {
  380.         close(s);
  381.         continue;
  382.     } else
  383.         break;
  384.     }
  385.     freeaddrinfo(res);
  386.     /* socket failure / no supported address families */
  387.     if (resp == NULL && ecode == 0) {
  388.     report(LOG_ERR, "ckfinger: socket: %s", strerror(errno));
  389.     return(0);
  390.     }
  391.     if (ecode != 0) {
  392.     report(LOG_ERR, "ckfinger: connect %s: %s", nas, strerror(errno));
  393.     return(0);
  394.     }
  395.     /* Read the finger output into a single flat buffer */
  396.     buf = NULL;
  397.     bufsize = 0;
  398.     for (;;) {
  399.     int x;
  400.  
  401.     buf = tac_realloc(buf, bufsize + incr + slop);
  402.     x = timed_read(s, (unsigned char *)(buf + bufsize), incr, 10);
  403.     if (x <= 0) {
  404.         break;
  405.     }
  406.     bufsize += x;
  407.     }
  408.  
  409.     /* Done talking here */
  410.     close(s);
  411.     buf[bufsize] = '\0';
  412.  
  413.     if (bufsize <= 0) {
  414.     report(LOG_ERR, "ckfinger: finger failure");
  415.     free(buf);
  416.     return(0);
  417.     }
  418.     /* skip first line in buffer */
  419.     p = strchr(buf, '\n');
  420.     if (p) {
  421.     p++;
  422.     }
  423.     p = strchr(p, '\n');
  424.     if (p) {
  425.     p++;
  426.     }
  427.     /* Tally each time this user appears */
  428.     for (count = 0; p && *p; p = pn) {
  429.     int i, len, nmlen;
  430.     char nmbuf[11];
  431.  
  432.     /* Find next line */
  433.     pn = strchr(p, '\n');
  434.     if (pn) {
  435.         ++pn;
  436.     }
  437.     /* Calculate line length */
  438.     if (pn) {
  439.         len = pn - p;
  440.     } else {
  441.         len = strlen(p);
  442.     }
  443.  
  444.     /* Line too short -> ignore */
  445.     if (len < 14) {
  446.         continue;
  447.     }
  448.     /* Always ignore the NAS/port we're currently trying to login on. */
  449.     if (isdigit((int) *curport)) {
  450.         int thisport;
  451.  
  452.         if (sscanf(p + 1, " %d", &thisport) == 1) {
  453.         if ((atoi(curport) == thisport) &&
  454.             !strcmp(idp->NAS_name, nas)) {
  455.  
  456.             if (debug & DEBUG_MAXSESS_FLAG) {
  457.             report(LOG_DEBUG, "%s session on %s/%s discounted",
  458.                    user, idp->NAS_name, idp->NAS_port);
  459.             }
  460.             continue;
  461.         }
  462.         }
  463.     }
  464.     /* Extract username, up to 10 chars wide, starting at char 13 */
  465.     nmlen = 0;
  466.     name = p + 13;
  467.     /*
  468.      * If this is not IOS version 11, the username MAY begin at the
  469.      * 15th column in the line.  So, skip up to 2 leading whitespaces.
  470.      */
  471.     for (i = 0; i < 2; i++) {
  472.         if (! isspace((int)*name))
  473.         break;
  474.     }
  475.     for (i = 0; *name && !isspace((int) *name) && (i < 10); i++) {
  476.         nmbuf[nmlen++] = *name++;
  477.     }
  478.     nmbuf[nmlen++] = '\0';
  479.  
  480.     /* If name matches, up the count */
  481.     if (STREQ(user, nmbuf)) {
  482.         count++;
  483.  
  484.         if (debug & DEBUG_MAXSESS_FLAG) {
  485.         char c = *pn;
  486.  
  487.         *pn = '\0';
  488.         report(LOG_DEBUG, "%s matches: %s", user, p);
  489.         *pn = c;
  490.         }
  491.     }
  492.     }
  493.     free(buf);
  494.     return(count);
  495. }
  496.  
  497. /*
  498.  * Verify how many sessions a user has according to the wholog file.
  499.  * Use finger to contact each NAS that wholog says has this user
  500.  * logged on.
  501.  */
  502. int
  503. countusers_by_finger(struct identity *idp)
  504. {
  505.     FILE *fp;
  506.     struct peruser pu;
  507.     int x, naddr, nsess, n;
  508.     char **addrs;
  509.  
  510.     fp = fopen(wholog, "r+");
  511.     if (fp == NULL) {
  512.     return(0);
  513.     }
  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, idp->username)) {
  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, idp->username, idp->NAS_port);
  548.     }
  549.     n = ckfinger(idp->username, 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"), idp->username, 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. int
  573. countuser(struct identity *idp)
  574. {
  575.     FILE *fp;
  576.     struct peruser pu;
  577.     int nsess;
  578.  
  579.     /* Access log */
  580.     fp = fopen(wholog, "r+");
  581.     if (fp == NULL) {
  582.     return(0);
  583.     }
  584.     /* Count sessions. Skip any session associated with the current port. */
  585.     tac_lockfd(wholog, fileno(fp));
  586.     nsess = 0;
  587.     while (fread(&pu, sizeof(pu), 1, fp) > 0) {
  588.     /* Current user */
  589.     if (strcmp(pu.username, idp->username)) {
  590.         continue;
  591.     }
  592.     /* skip current port on current NAS */
  593.     if (STREQ(portname(pu.NAS_port), portname(idp->NAS_port)) &&
  594.         STREQ(pu.NAS_name, idp->NAS_name)) {
  595.         continue;
  596.     }
  597.     nsess += 1;
  598.     }
  599.  
  600.     /* Clean up and return */
  601.     fclose(fp);
  602.     return(nsess);
  603. }
  604.