/************************************************************************
 * linux.c                                                              *
 *                                                                      *
 * Copyright (C) 2002 - 2020 Joshua Birnbaum <engineer@noorg.org>.      *
 * All Rights Reserved.                                                 *
 *                                                                      *
 * This program is free software; you can redistribute it and/or modify *
 * it under the terms of the GNU General Public License as published    *
 * by the Free Software Foundation; either version 2 of the License, or *
 * (at your option) any later version.                                  *
 *                                                                      *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS    *
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED    *
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE   *
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY      *
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL   *
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE    *
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS        *
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,         *
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING            *
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS   *
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.         *
 *                                                                      *
 * You should have received a copy of the GNU General Public License    *
 * along with this program; see the file COPYING. If not, write to the  *
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, *
 * MA 02111-1307, USA.                                                  *
 ************************************************************************/

/***************************************************************
 * Project ifchk -- Host based network interface promiscuous   *
 *                  mode detection and handling.               *
 *                                                             *
 * Version:         1.1.2                                      *
 *                                                             *
 * ----------------------------------------------------------- *
 *                                                             *
 * linux.c:         ifchk Linux module - sysfs implementation. *
 *                                                             *
 * Module                                                      *
 * Functions:     - Gets and sets per-interface status flags.  *
 *                - Displays per-interface status flags.       *
 *                - Gets per-interface traffic metrics.        *
 *                - Displays per-interface traffic metrics.    *
 ***************************************************************/

/**********************
 * include directives *
 **********************/

#include "linux.h"

/***********************
 * global declarations *
 ***********************/

int bFlag = FALSE;


/*************************************************************
 * ifStat()    - Establishes a communication channel between *
 *               ifchk and the sysfs filesystem. Performs    *
 *               interface status discovery, disabling and   *
 *               ifMetric() preprocessing.                   *
 *                                                           *
 * Parameters:   None.                                       *
 *                                                           *
 * Returns:      0 on success.                               *
 *               -1 on failure.                              *
 *************************************************************/

int ifStat( void )
{
    char mntPath[SYSFS_PATH_MAX] = "";      /* Buffer for sysfs filesystem mount point pathname. */
    const char *className = NULL;           /* sysfs class to open (e.g., "net"). */
    struct sysfs_class *class;              /* sysfs class structure. */
    struct dlist *classDev;                 /* sysfs class device list. */
    char pathName[SYSFS_PATH_MAX] = "";     /* Pathname of sysfs object to query. */
    char *bondPtr = NULL;                   /* Pathname of bond interface sysfs object to query. */
    char slaveFile[BONDBUFSZ] = "";         /* Pathname of sysfs filesystem bonding interface slaves. */
    char actSlave[BONDBUFSZ] = "";          /* Pathname of sysfs filesystem active slave interface. */
    FILE *bondslavefd = NULL;               /* File pointer that points to bonding interface slaves. */
    char buf[BONDBUFSZ] = "";               /* Buffer for sysfs filesystem bonding interface slaves. */
    char slaveBuf[BONDBUFSZ] = "";          /* Buffer for sysfs filesystem binding active slave interface. */
    char *nlPtr = NULL;                     /* Ptr for trailing newline processing. */
    int ifCount = 0;                        /* Interface count. */
    struct sysfs_attribute *sysAttr;        /* sysfs attribute to open (e.g., /sys/class/net/eth0/flags). */
    char *endPtr = NULL;                    /* For strtol(). */
    int i = 0;                              /* For ifMetric(). */
    long j = 0;                             /* Interface flags mask. */
    int sockfd = 0;                         /* Socket descriptor for ioctl(). */
    struct ifreq ifr;                       /* Per-interface characteristics. */
    struct ifList *head = NULL;             /* Linked list of interface \ */
    struct ifList *tail = NULL;             /* name/unit number pairings. \ */
    struct ifList *new = NULL;              /* Constructed locally & passed \ */
    struct ifList *cur = NULL;              /* to ifMetric() for processing \ */
    struct ifList *freeList = NULL;         /* & output. */
    struct fsObj slaveObject;               /* File attribute checking policy. */

    /*
     * Reset all statFile() file checks.
     */
    slaveObject.openRead = FALSE;
    slaveObject.procLen = FALSE;
    slaveObject.procOwner = FALSE;
    slaveObject.procMode = FALSE;
    slaveObject.regLen = FALSE;
    slaveObject.regMode = FALSE;

    memset( mntPath, '\000', sizeof( mntPath ) );

    /*
     * Preset the file query policy for fileStat(), below. Specifically,
     * we want to probe the sysfs file attributes of bonding interface
     * slaves (e.g., em1, em2), if any.
     */
    slaveObject.openRead = TRUE;
    slaveObject.regLen = TRUE;
    slaveObject.regMode = TRUE;

    /*
     * Determine the sysfs mount point.
     */
    if( sysfs_get_mnt_path( mntPath, SYSFS_PATH_MAX ) != 0 )
    {
        errHandler( "ERROR: sysfs_get_mnt_path(): could not get sysfs mount path: %s\n",
                                                                       strerror( errno ) );
        return (-1);
     }

    /*
     * The sysfs class we want to open.
     * In this case, we're interested in /sys/class/net.
     */
    className = "net";

    /*
     * Open the /sys/class/net sysfs class.
     */
    if( ( class = sysfs_open_class( className ) ) == NULL )
    {
        errHandler( "ERROR: sysfs_open_class(): could not open sysfs class \"%s\": %s\n",
                                                             className, strerror( errno ) );
        return (-1);
     }

    /*
     * Get a list of class devices for
     * /sys/class/net (e.g., eth0, lo).
     */
    if( ( classDev = sysfs_get_class_devices( class ) ) == NULL )
    {
        errHandler( "ERROR: sysfs_get_class_devices(): could not get class device list: %s\n",
                                                                             strerror( errno ) );
        return (-1);
     }

    /*
     * Compute and print interface count.
     */
    ifCount = classDev -> count;

    if( iFlag != TRUE )
    {
        printf( "interfaces: %d\n", ifCount );

        if( ( writeLog( LOG_INFO, "interfaces: %d\n", ifCount ) ) != 0 )
        {
            return (-1);
         }

        if( ( writeLog( LOG_INFO, "interface status: dump initiated by user %s\n",
                                                            pw -> pw_name ) ) != 0 )
        {
            return (-1);
         }
     }

    /*
     * Loop through all interfaces. Either we're examining per-interface
     * flags or gathering interface names to pass to ifMetric().
     */
    dlist_for_each_data( classDev, class, struct sysfs_class )
    {
        /*
         * Build a pathname to the current interface's sysfs
         * flags file (e.g., /sys/class/net/eth0/flags).
         */
        memset( pathName, '\000', sizeof( pathName ) );
        strncpy( pathName, class -> path, sizeof( pathName ) );
        pathName[SYSFS_PATH_MAX - 1] = '\000';
        strncat( pathName, "/", ( sizeof( pathName ) - strlen( pathName ) ) - 1 );
        strncat( pathName, "flags", ( sizeof( pathName ) - strlen( pathName ) ) - 1 );

        /*
         * Handle logical bonding interfaces (e.g., bond0), if present.
         */
        if( ( bondPtr = strstr( pathName, "bond" ) ) != NULL )
        {
            bFlag = TRUE;

            /*
             * Build a full path to the bonding interface slave sysfs file.
             */
            strncpy( slaveFile, "/sys/devices/virtual/net/", sizeof( slaveFile ) );
            strncat( slaveFile, class -> name, sizeof( slaveFile ) );
            strncat( slaveFile, "/bonding/slaves", sizeof( slaveFile ) );

            /*
             * Prepare to securely open the slave sysfs file.
             */
            if( ( bondslavefd = statFile( slaveFile, slaveObject ) ) == NULL )
            {
                return (-1);
             }

            /*
             * Once opened, read the file's contents (e.g., "em1 em2").
             */
            while( ( fgets( buf, sizeof( buf ), bondslavefd ) ) != NULL )
            {
                if( ( nlPtr = strchr( buf, '\n' ) ) )
                {
                    *nlPtr = '\000';
                 }
             }

            /*
             * Once read, close the slave sysfs file.
             */
            if( fclose( bondslavefd ) != 0 )
            {
                errHandler( "ERROR: fclose(): could not close %s: %s\n", slaveFile, strerror( errno ) );
                return (-1);
             }

            /*
             * Build a full path to the active slave interface sysfs file.
             */
            strncpy( actSlave, "/sys/devices/virtual/net/", sizeof( actSlave ) );
            strncat( actSlave, class -> name, sizeof( actSlave ) );
            strncat( actSlave, "/bonding/active_slave", sizeof( actSlave ) );

            /*
             * Prepare to securely open the active slave sysfs file.
             */
            if( ( bondslavefd = statFile( actSlave, slaveObject ) ) == NULL )
            {
                return (-1);
             }

            /*
             * Once opened, read the file's contents (e.g., "em1").
             */
            while( ( fgets( slaveBuf, sizeof( slaveBuf ), bondslavefd ) ) != NULL )
            {
                if( ( nlPtr = strchr( slaveBuf, '\n' ) ) )
                {
                    *nlPtr = '\000';
                 }
             }

            /*
             * Once read, close the slave sysfs file.
             */
            if( fclose( bondslavefd ) != 0 )
            {
                errHandler( "ERROR: fclose(): could not close %s: %s\n", slaveFile, strerror( errno ) );
                return (-1);
             }
         }

        /*
         * Open the current interface's sysfs flags file.
         */
        if( ( sysAttr = sysfs_open_attribute( pathName ) ) == NULL )
        {
            errHandler( "ERROR: sysfs_open_attribute(): could not get configured interface flags: %s\n",
                                                                                       strerror( errno ) );
            return (-1);
         }

        /*
         * Read the current interface's sysfs flags file.
         */
        if( sysfs_read_attribute( sysAttr ) == -1 )
        {
            errHandler( "ERROR: sysfs_read_attribute(): could not get configured interface flags: %s\n",
                                                                                       strerror( errno ) );
            return (-1);
         }

        j = strtol( sysAttr -> value, &endPtr, 16 );

        /*
         * The current flags value contains illegal characters.
         */
        if( ( sysAttr -> value == endPtr ) || ( *endPtr != '\n' ) )
        {
            errHandler( "ERROR: strtol(): flags conversion for interface %s failed\n", class -> name );
            return (-1);
         }

        /*
         * Interface is promiscuous - print & log state.
         */
        if( ( j & IFF_PROMISC ) && ( nullFlag == TRUE ) )
        {
            if( bFlag == TRUE )
            {
                printf( "%6s: PROMISC (slave: %s)\n", class -> name, buf );

                if( ( writeLog( LOG_WARNING, "%4s: PROMISC (slave: %s)\n", class -> name, buf ) ) != 0 )
                {
                    return (-1);
                 }
             }

            else
            {
                printf( "%6s: PROMISC\n", class -> name );

                if( ( writeLog( LOG_WARNING, "%4s: PROMISC\n", class -> name ) ) != 0 )
                {
                    return (-1);
                 }
             }
         }

        /*
         * Interface is promiscuous - disable it, print & log state.
         */
        else if( ( j & IFF_PROMISC ) && ( dFlag == TRUE ) )
        {
            if( ( sockfd = socket( AF_INET, SOCK_DGRAM, 0 ) ) == -1 )
            {
                errHandler( "ERROR: socket(): could not create communication endpoint: %s\n",
                                                                            strerror( errno ) );
                return (-1);
             }

            strncpy( ifr.ifr_name, class -> name, sizeof( ifr.ifr_name ) );

            ifr.ifr_name[IFNAMSIZ - 1] = '\000';

            if( ioctl( sockfd, SIOCGIFFLAGS, &ifr ) < 0 )
            {
                errHandler( "ERROR: ioctl(): could not get configured interface flags: %s\n",
                                                                            strerror( errno ) );
                return (-1);
             }

            ifr.ifr_flags &= ~(IFF_UP|IFF_RUNNING);

            if( ioctl( sockfd, SIOCSIFFLAGS, &ifr ) < 0 )
            {
                errHandler( "ERROR: ioctl(): could not set configured interface flags: %s\n",
                                                                            strerror( errno ) );
                return (-1); 
             }

            if( ( close( sockfd ) ) != 0 )
            {
                errHandler( "ERROR: close(): could not close socket: %s\n", strerror( errno ) );
                return (-1); 
             }

            if( bFlag == TRUE )
            {
                printf( "%6s: PROMISC [*] (slave: %s)\n", class -> name, buf );

                if( ( writeLog( LOG_WARNING, "%4s: PROMISC [*] (slave: %s)\n", class -> name, buf ) ) != 0 )
                {
                    return (-1);
                 }
             }

            else
            {
                printf( "%6s: PROMISC [*]\n", class -> name );

                if( ( writeLog( LOG_WARNING, "%4s: PROMISC [*]\n", class -> name ) ) != 0 )
                {
                    return (-1);
                 }
             }
         }

        /*
         * Construct linked list of interface names for ifMetric().
         */
        else if( iFlag == TRUE )
        {
            if( ( new = malloc( sizeof( struct ifList ) ) ) == NULL )
            {
                errHandler( "ERROR: malloc(): memory allocation failure: %s\n", strerror( errno ) );
                return (-1);
             }

            /*
             * Create a new node.
             */
            memset( new, '\000', sizeof( new ) );
            strncpy( new -> data, class -> name, sizeof( new -> data ) );
            new -> data[IFNAMSIZ - 1] = '\000';
            cur = new;

            /*
             * Append new node to list end.
             */
            if( head == NULL )
            {
                head = cur; 
             }

            else
            {
                tail -> next = cur;
             }

            tail = cur;
            cur -> next = NULL;

            i++;

            /*
             * When the list is complete, call ifMetric(),
             * passing it a pointer to the head node.
             */
            if( i == ifCount )
            {
                if( ifMetric( head ) != 0 )
                {
                    return (-1);
                 }

                /*
                 * Deallocate list memory after use.
                 */
                for( cur = head; cur != NULL; cur = freeList )
                {
                    freeList = cur -> next;
                    free( cur );
                 }
             }
         }

        /*
         * Interface is up - print & log state.
         */
        else if( j & IFF_UP )
        {
            if( bFlag == TRUE )
            {
                printf( "%6s: normal <slave: %s, actv, %s>\n", class -> name, buf, slaveBuf );

                if( ( writeLog( LOG_INFO, "%4s: normal <slave: %s, actv: %s>\n",
                                               class -> name, buf, slaveBuf ) ) != 0 )
                {
                    return (-1);
                 }
             }

            else
            {
                printf( "%6s: normal\n", class -> name );

                if( ( writeLog( LOG_INFO, "%4s: normal\n", class -> name ) ) != 0 )
                {
                    return (-1);
                 }
             }
         }

        /*
         * Interface is down - print & log state.
         */
        else
        {
            if( bFlag == TRUE )
            {
                if( ( writeLog( LOG_INFO, "%4s: *down* <slave: %s, actv: %s>\n",
                                               class -> name, buf, slaveBuf ) ) != 0 )
                {
                    return (-1);
                 }
             }

            else
            {
                printf( "%6s: *down*\n", class -> name );

                if( ( writeLog( LOG_INFO, "%4s: *down*\n", class -> name ) ) != 0 )
                {
                    return (-1);
                 }
             }
         }

        /*
         * Close the current interface's sysfs flags file.
         */
        sysfs_close_attribute( sysAttr );

        bFlag = FALSE;
     }

    /*
     * Close the sysfs /sys/class/net class.
     */
    sysfs_close_class( class );

    return (0);
}


/********************************************************
 * ifMetric()  - Generates and prints interface traffic *
 *               metrics for all configured interfaces. *
 *                                                      *
 * Parameters:   *ifList: first node of linked list of  *
 *                        interface name/unit number    *
 *                        pairings.                     *
 *                                                      *
 * Returns:      0 on success.                          *
 *               -1 on failure.                         *
 ********************************************************/

int ifMetric( struct ifList *list )
{
    const char *className = NULL;           /* sysfs class to open (e.g., "net"). */
    struct sysfs_class *class;              /* sysfs class structure. */
    struct dlist *classDev;                 /* sysfs class device list. */
    char pathName[SYSFS_PATH_MAX] = "";     /* Pathname of sysfs object to query. */
    char *classPath = NULL;                 /* Pathname of sysfs class (e.g., /sys/class/net). */
    struct sysfs_class_device *devAttrs;    /* Describes class device (e.g., eth0) attributes. */
    struct sysfs_attribute *attr;           /* sysfs attribute to open (e.g., /sys/class/net/eth0/ifindex). */
    char *ifIndex = NULL;                   /* Interface index. */
    char *rxPackets = NULL;                 /* Received packet count. */
    char *txPackets = NULL;                 /* Transmitted packet count. */
    char ifMetricBuf[METRICBUFSZ];          /* Buffer for interface metrics data. */
    char *lfPtr = NULL;                     /* Ptr for trailing new line processing. */
    struct ifList *cur = NULL;              /* Current node in linked list. */

    cur = list;

    /*
     * Exit if passed in interface list is empty.
     */
    if( cur -> data == NULL )
    {
        errHandler( "%s", "ERROR: interface list is empty\n" );
        return (-1);
     }

    /*
     * The sysfs class we want to open. In this
     * case, we're interested in /sys/class/net.
     */
    className = "net";

    /*
     * Open the /sys/class/net sysfs class.
     */
    if( ( class = sysfs_open_class( className ) ) == NULL )
    {
        errHandler( "ERROR: sysfs_open_class(): could not open sysfs class \"%s\": %s\n",
                                                             className, strerror( errno ) );
        return (-1);
     }

    /*
     * Get a list of class devices for
     * /sys/class/net (e.g., eth0, lo).
     */
    if( ( classDev = sysfs_get_class_devices( class ) ) == NULL )
    {
        errHandler( "ERROR: sysfs_get_class_devices(): could not get class device list: %s\n",
                                                                             strerror( errno ) );
        return (-1);
     }

    classPath = class -> path;

    /*
     * Prepare to display metrics data.
     */
    printf( "***   Network Interface Metrics   ***\n" );
    printf( "Name    Index   RX-OK           TX-OK\n" );

    /*
     * Log that a metrics dump was invoked.
     */
    if( ( writeLog( LOG_INFO, "interface metrics: dump initiated by user %s\n",
                                                         pw -> pw_name ) ) != 0 )
    {
        return (-1);
     }

    if( ( writeLog( LOG_INFO, "Name           Index   RX-OK           TX-OK\n" ) ) != 0 )
    {
        return (-1);
     }

    /*
     * Loop through sysfs metrics & output one
     * line of metrics data for each interface.
     */
    dlist_for_each_data( classDev, class, struct sysfs_class )
    {
        if( ( strstr( class -> name, cur -> data ) ) == NULL )
        {
            errHandler( "%s", "ERROR: current metrics do not describe current interface\n" );
            return (-1);
         }

       /*
        * Build a pathname to the current interface's
        * sysfs directory (e.g., /sys/class/net/eth0 ).
        */
        memset( pathName, '\000', sizeof( pathName ) );
        strncpy( pathName, classPath, sizeof( pathName ) );
        pathName[SYSFS_PATH_MAX - 1] = '\000';
        strncat( pathName, "/", ( sizeof( pathName ) - strlen( pathName ) ) - 1 );
        strncat( pathName, cur -> data, ( sizeof( pathName ) - strlen( pathName ) ) - 1 );

        /*
         * Open the directory.
         */
        if( ( devAttrs = sysfs_open_class_device_path( pathName ) ) == NULL )
        {
            errHandler( "ERROR: sysfs_open_class_device_path(): could not open sysfs class device: %s\n",
                                                                                        strerror( errno ) );
            return (-1);
         }
    
        /*
         * Get the index for the current interface
         * (e.g., /sys/class/net/eth0/ifindex).
         */
        ifIndex = "ifindex";
    
        if( ( attr = sysfs_get_classdev_attr( devAttrs, ifIndex ) ) == NULL )
        {
            errHandler( "ERROR: sysfs_get_classdev_attr(): could not get sysfs class device attribute: %s\n",
                                                                                            strerror( errno ) );
            return (-1);
         }
    
        ifIndex = attr -> value;
    
        if( ( lfPtr = strchr( ifIndex, '\n' ) ) == NULL )
        {
            errHandler( "%s", "ERROR: strchr(): could not process interface metrics data: no newline terminator\n" );
            return (-1);
         }
    
        else
        {
            *lfPtr = '\000';
         }
    
        /*
         * Append "/statistics" to pathName in preparation for
         * reading packets received (rx_packets) and transmitted
         * (tx_packets) on the current interface.
         */
        strncat( pathName, "/", ( sizeof( pathName ) - strlen( pathName ) ) - 1 );
        strncat( pathName, "statistics", ( sizeof( pathName ) - strlen( pathName ) ) - 1 );
    
        /*
         * Open the statistics directory.
         */
        if( ( devAttrs = sysfs_open_class_device_path( pathName ) ) == NULL )
        {
            errHandler( "ERROR: sysfs_open_class_device_path(): could not open sysfs class device: %s\n",
                                                                                        strerror( errno ) );
            return (-1);
         }
    
        /*
         * Get received packet count for the current interface
         * (e.g., /sys/class/net/eth0/statistics/rx_packets).
         */
        rxPackets = "rx_packets";
    
        if( ( attr = sysfs_get_classdev_attr( devAttrs, rxPackets ) ) == NULL )
        {
            errHandler( "ERROR: sysfs_get_classdev_attr(): could not get sysfs class device attribute: %s\n",
                                                                                            strerror( errno ) );
            return (-1);
         }
    
        rxPackets = attr -> value;
    
        if( ( lfPtr = strchr( rxPackets, '\n' ) ) == NULL )
        {
            errHandler( "%s", "ERROR: strchr(): could not process interface metrics data: no newline terminator\n" );
            return (-1);
         }
    
        else
        {
            *lfPtr = '\000';
         }
    
        /*
         * Get transmitted packet count for the current interface
         * (e.g., /sys/class/net/eth0/statistics/tx_packets).
         */
        txPackets = "tx_packets";
    
        if( ( attr = sysfs_get_classdev_attr( devAttrs, txPackets ) ) == NULL )
        {
            errHandler( "ERROR: sysfs_get_classdev_attr(): could not get sysfs class device attribute: %s\n",
                                                                                            strerror( errno ) );
            return (-1);
         }
    
        txPackets = attr -> value;
    
        if( ( lfPtr = strchr( txPackets, '\n' ) ) == NULL )
        {
            errHandler( "%s", "ERROR: strchr(): could not process interface metrics data: no newline terminator\n" );
            return (-1);
         }
    
        else
        {
            *lfPtr = '\000';
         }
    
        memset( ifMetricBuf, '\000', sizeof( ifMetricBuf) );
    
        /*
         * Output one line of metrics data for the current interface.
         */
        if( snprintf( ifMetricBuf, sizeof( ifMetricBuf ), "%-14s %-7s %-15s %-13s", cur -> data,
                                                                ifIndex, rxPackets, txPackets ) < 1 )
        {
            errHandler( "ERROR: snprintf(): could not write to character buffer: %s\n",
                                                                      strerror( errno ) );
            return (-1);
         }
    
        printf( "%s\n", ifMetricBuf );
    
        if( ( writeLog( LOG_INFO, "%s\n", ifMetricBuf ) ) != 0 )
        {
            return (-1);
         }
    
        if( cur -> next != NULL )
        {
            cur = cur -> next;
         }
     }

    /*
     * Close the /sys/class/net sysfs class.
     */
    sysfs_close_class( class );

    return (0);
}
