/************************************************************************
 * linux.c                                                              *
 *                                                                      *
 * Copyright (C) 2002 - 2016 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.0.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"


/*************************************************************
 * 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. */
    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. */

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

    /*
     * 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 );

        /*
	 * 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 ) )
        {
            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); 
             }

            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 )
        {
            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
        {
            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 );
     }

    /*
     * 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 termination\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 termination\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 termination\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 ), "%-7s %-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);
}
