/************************************************************************
 * ifchk.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                                     *
 *                                                            *
 * ---------------------------------------------------------- *
 *                                                            *
 * ifchk.c          ifchk program front-end/framework.        *
 *                                                            *
 * Module                                                     *
 * Functions:     - Performs ifchk program initialization and *
 *                  shutdown proceedures.                     *
 *                - Provides a point of entry into an operat- *
 *                  ing system specific interface management  *
 *                  module.                                   *
 *                - Displays a program usage message.         *
 *                - Displays ifchk revision and runtime data. *
 **************************************************************/

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

#include "ifchk.h"


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

struct passwd *pw;			/* For getpwuid(). */
int iFlag = FALSE;			/* Interface metrics flag. */
int FFlag = FALSE;			/* Foreground process flag. */
int nullFlag = FALSE;			/* Dummy interface flag. */
int dFlag = FALSE;			/* Interface disable flag. */


/*******************************************************
 * main()      - Performs ifchk program initialization *
 *               and shutdown proceedures.             *
 *                                                     *
 * Parameters:     argc: command line argument count.  *
 *               **argv: command line argument vector. *
 *                                                     *
 * Returns:      0 on success.                         *
 *               -1 on failure.                        *
 *******************************************************/

int main( int argc, char **argv )
{
    int opt = 0;			/* For getopt() processing. */
    char *confFile = NULL;		/* Ptr to ifchk configuration file. */
    int index = 0;			/* For command line argument sanity check. */
    uid_t rUid;				/* Our real UID. */
    uid_t eUid;				/* Our effective UID. */
    struct rlimit rsrcLim;		/* For setrlimit(). */
    struct conf *curParam;		/* Pointer to current ifchk configuration parameter. */
    FILE *errfilefd = NULL;		/* Error log file pointer for fopen() */
    char paramBuf[STRBUFSZ];		/* Configuration parameter buffer. */
    char sigNumBuf[SNBUFSZ];		/* Signal log buffer for ifchk shutdown proceedure. */
    int ifStatusSecs = 0;		/* Interface status scan frequency. */
    int ifMetricSecs = 0;		/* Interface metrics dump frequency. */
    int disablePromisc = 0;		/* Enable/disable promiscuous interface shutdown. */
    char *errFile = NULL;		/* Error log file pathname. */
    struct sigevent evpTimer;		/* For POSIX timers. */
    timer_t timerOne = 0;		/* For POSIX timers. */
    timer_t timerTwo = 0;		/* For POSIX timers. */
    struct itimerspec spec;		/* For POSIX timers. */
    static sigset_t newMask;		/* For POSIX signal set. */
    static int sig = 0;			/* Signal number. */
    size_t pathLen = 0;			/* Pathname length. */
    struct fsObj fsObject;		/* For statFile(). */
    struct stat testAttrs;		/* Error log file attributes. */
    char argBuf[STRBUFSZ];		/* Command line option buffer. */
    char *wsPtr = NULL;			/* Ptr for trailing white space processing. */
    int i = 1;				/* For argBuf[]. */

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

    /*
     * Set our default file creation mask to 077 (mode 0600).
     */
    umask( 077 );

    /*
     * Process command line arguments.
     */
    while( ( opt = getopt( argc, argv, "Ff:hv" ) ) != -1 )
    {
        switch( opt )
        {
            /*
             * Start up as a foreground process - do not daemonize.
             */
            case 'F':
                FFlag = TRUE;
                break;

            /*
             * Specify the name of the ifchk configuration file.
             */
            case 'f':
                if( *optarg == '/' )
                {
                    confFile = optarg;
                    pathLen = strlen( confFile );

                    /*
                     * Does the configuration file pathname
                     * exceed PATH_MAX characters in length?
                     */
                    if( pathLen > PATH_MAX )
                    {
                        errHandler( "ERROR: %s pathname exceeds %d characters\n", confFile, PATH_MAX );
                        exit( EXIT_FAILURE );
                     }

                    /*
                     * Does the configuration file pathname contain set const-
		     * ruct wildcard, brace expansion, space or tab characters?
                     */
                    if( strpbrk( confFile, "[]{} \t" ) != NULL )
                    {
                        errHandler( "ERROR: %s contains invalid pathname component\n", confFile );
                        exit( EXIT_FAILURE );
                     }
                 }

                else
                {
                    errHandler( "ERROR: -f option requires absolute pathname as argument\n" );

                    ifchkHelp();
                    exit( EXIT_FAILURE );
                 }
                break;

            /*
             * Print program usage message.
             */
            case 'h':
                ifchkHelp();
                exit( EXIT_SUCCESS );

            /*
             * Print program revision information.
             */
            case 'v':
                if( ifchkRev() != 0 )
                {
                    exit( EXIT_FAILURE );
                 }
                exit( EXIT_SUCCESS );

            /*
             * Default: signals an error condition.
             */
            case '?':
                ifchkHelp();
                exit( EXIT_FAILURE );
         }
     }

    /*
     * No command line arguments specified.
     */
    if( argc == 1 )
    {
        errHandler( "ERROR: no command line arguments specified\n" );

        ifchkHelp();
        exit( EXIT_FAILURE );
     }

    /*
     * Sanity check for malformed command line input.
     */
    for( index = optind; index < argc; index++ )
    {
        errHandler( "ERROR: malformed argument list\n" );

        ifchkHelp();
        exit( EXIT_FAILURE );
     }

    /*
     * No configuration file specified for foreground process startup.
     */ 
    if( ( FFlag == TRUE ) && ( confFile == NULL ) )
    {
        errHandler( "ERROR: no configuration file specified\n" );

        ifchkHelp();
        exit( EXIT_FAILURE );
     }

    /*
     * Disable core dumps - set core file size to zero bytes.
     */
    rsrcLim.rlim_cur = 0;
    rsrcLim.rlim_max = 0;

    if( setrlimit( RLIMIT_CORE, &rsrcLim ) != 0 )
    {
        errHandler( "%s %s %c", "ERROR: setrlimit(): could not disable core dumps:",
                                                             strerror( errno ), '\n' );
        exit( EXIT_FAILURE );
     }

    /*
     * Check to see if /tmp/.ifchk.pid exists and is write
     * locked. If it is, ifchk is already running and we
     * prohibit the starting of another program instance
     * by quitting.
     */
    if( lockCtl( QUERYLOCK ) != 0 )
    {
        exit( EXIT_FAILURE );
     }

    /*
     * Report and log that we are start-
     * ing up as a foreground process.
     */
    memset( argBuf, '\000', sizeof( argBuf ) );

    while( i < argc )
    {
        strncat( argBuf, argv[i], sizeof( argBuf ) );
        strncat( argBuf, " ", sizeof( argBuf ) );

	i++;
     }

    if( ( wsPtr = strrchr( argBuf, ' ' ) ) )
    {
        *wsPtr = '\000';
     }

    if( FFlag == TRUE )
    {
        printf( "ifchk[%d]: starting ifchk %s %s\n", getpid(), VERS, argBuf );
     }

    if( ( writeLog( LOG_NOTICE, "starting ifchk %s %s\n", VERS, argBuf ) ) != 0 )
    {
        exit( EXIT_FAILURE );
     }

    /*
     * Get our real and effective UID.
     */
    rUid = getuid();
    eUid = geteuid();

    /*
     * We're running setuid to someone if our real and effec-
     * tive UIDs don't match. If this is the case, report it.
     */
    if( rUid != eUid )
    {
        if( ( pw = getpwuid( eUid ) ) == NULL )
        {
            errHandler( "ERROR: getpwuid(): could not get passwd entry for UID %d: %s\n",
                                                                  eUid, strerror( errno ) );
            exit( EXIT_FAILURE );
         }

        fprintf( stderr, "ifchk[%d]: WARNING: ifchk running setuid to user %s\n",
	                                                  getpid(), pw -> pw_name );

        if( ( writeLog( LOG_NOTICE, "WARNING: ifchk running setuid to user %s\n",
                                                           pw -> pw_name ) ) != 0 )
        {
            exit( EXIT_FAILURE );
         }
     }

    /*
     * Who are we running as?
     */
    if( ( pw = getpwuid( rUid ) ) == NULL )
    {
        errHandler( "ERROR: getpwuid(): could not get passwd entry for UID %d: %s\n",
                                                              eUid, strerror( errno ) );
        exit( EXIT_FAILURE );
     }

    /*
     * Report and log that we are about
     * to parse our configuration file.
     */
    if( FFlag == TRUE )
    {
        printf( "ifchk[%d]: loading configuration from %s\n", getpid(), confFile );
     }

    if( ( writeLog( LOG_NOTICE, "loading configuration from %s\n", confFile ) ) != 0 )
    {
        exit( EXIT_FAILURE );
     }

    /*
     * Parse the ifchk configuration file.
     */
    if( ( curParam = loadConfig( confFile ) ) == NULL )
    {
        exit( EXIT_FAILURE );
     }

    /*
     * With the file parsed, we can now load our configuration parameters.
     */
    while( curParam )
    {
        memset( paramBuf, '\000', sizeof( paramBuf ) );

        strncpy( paramBuf, curParam -> keyword, sizeof( paramBuf ) );

        paramBuf[STRBUFSZ - 1] = '\000';

        /*
         * errLog - the ifchk error log pathname.
         */
        if( ( strncmp( paramBuf, "errLog", sizeof( paramBuf ) ) ) == 0 )
        {
            errFile = curParam -> value;
            fsObject.regMode = TRUE;
         }

        /*
         * disableOnPromisc - promiscuous interface management policy.
	 */
        else if( ( strncmp( paramBuf, "disableOnPromisc", sizeof( paramBuf ) ) ) == 0 )
        {
            disablePromisc = atoi( curParam -> value );
         }

        /*
         * ifStatusDumpFreq - interface status dump frequency.
         */
        else if( ( strncmp( paramBuf, "ifStatusDumpFreq", sizeof( paramBuf ) ) ) == 0 )
        {
            ifStatusSecs = atoi( curParam -> value );
         }

        /*
	 * ifMetricDumpFreq - interface metric dump frequency.
	 */
        else if( ( strncmp( paramBuf, "ifMetricDumpFreq", sizeof( paramBuf ) ) ) == 0 )
        {
            ifMetricSecs = atoi( curParam -> value );
         }

        /*
         * Default: signals an error condition.
         */
        else
        {
            exit( EXIT_FAILURE ); 
         }

        curParam = curParam -> next;
     }

    /*
     * Daemon initialization.
     */
    if( FFlag == FALSE )
    {
        /*
         * Prepare to reopen STDERR on the ifchk error log file prior to
         * daemon initialization. This sequence ensures that we are not-
	 * ified in the event of an fopen() failure.
         */
        if( stat( errFile, &testAttrs ) == -1 )
        {
	    fprintf( stderr, "ifchk[%d]: NOTICE: can't stat file %s, creating\n", getpid(), errFile );

            if( ( writeLog( LOG_NOTICE, "NOTICE: can't stat file %s, creating\n", errFile ) ) != 0 )
            {
                exit( EXIT_FAILURE );
             }

            /*
             * Open the error log file create/append.
             */
            if( ( errfilefd = fopen( errFile, "ab" ) ) == NULL )
            {
                errHandler( "ERROR: fopen(): %s file creation failed: %s\n", errFile,
                                                                    strerror( errno ) );
                exit( EXIT_FAILURE );
             }

            /*
             * Close the newly created error log file to
             * prepare for reopening on standard error.
             */
            if( close( fileno( errfilefd ) ) != 0 )
            {
                errHandler( "ERROR: close(): could not close %s: %s\n", errFile,
                                                               strerror( errno ) );
                exit( EXIT_FAILURE );
             }
         }

        /*
         * Check error log file attributes.
         */
        if( ( errfilefd = statFile( errFile, fsObject ) ) == NULL )
        {
            exit( EXIT_FAILURE );
         }

        /*
         * Close standard error in preparation for reopening on the
         * ifchk error log file. If this fails, we log the failure
         * via syslogd(1M). errHandler() will log to /dev/console if
         * syslogd(1M) fails.
         */
        if( close( 2 ) != 0 )
        {
            errHandler( "ERROR: close(): could not close stderr: %s\n", strerror( errno ) );
            exit( EXIT_FAILURE );
         }

        /*
         * Reopen standard error on the ifchk error log file.
         */
        if( dup2( fileno( errfilefd ), STDERR_FILENO ) == -1 )
        {
            fprintf( stdout, "ifchk[%d]: ERROR: dup2(): could not open stderr on %s: %s\n",
                                                       getpid(), errFile, strerror( errno ) );

           if( ( writeLog( LOG_CRIT, "ERROR: dup2(): could not open stderr on %s: %s\n",
                                                     errFile, strerror( errno ) ) ) != 0 )
            {
                exit( EXIT_FAILURE );
	     }

            exit( EXIT_FAILURE );
         }

        /*
         * Initialize ifchk to run as a daemon process.
         */
        if( daemonInit() != 0 )
        {
            exit( EXIT_FAILURE );
         }
     }

    /*
     * Take out an exclusive write lock on the whole /tmp/.ifchk.pid
     * file - this limits ifchk to a single running instance. 
     */
    if( lockCtl( LOCK ) != 0 )
    {
        exit( EXIT_FAILURE );
     }

    /*
     * Call sigsetInit() to initialize our POSIX signal set,
     * newMask. In doing so, we add SIGINT, SIGTERM, SIGUSR1
     * and SIGUSR2.
     */
    newMask = sigsetInit();

    /*
     * Create a POSIX interval timer to send SIGUSR1 to ourselves.
     * This timer triggers network interface status scans.
     */
    evpTimer.sigev_notify = SIGEV_SIGNAL;
    evpTimer.sigev_signo = SIGUSR1;

    if( timer_create( CLOCK_REALTIME, &evpTimer, &timerOne ) != 0 )
    {
        errHandler( "ERROR: timer_create(): could not create interval timer: %s\n",
                                                                  strerror( errno ) );
        exit( EXIT_FAILURE );
     }

    /*
     * Specify that initially, the SIGUSR1 timer will fire one second after
     * being armed and then every ifStatusSecs (user defined) thereafter.
     */
    spec.it_value.tv_sec = 1;
    spec.it_value.tv_nsec = 0;
    spec.it_interval.tv_sec = ifStatusSecs;
    spec.it_interval.tv_nsec = 0;

    /*
     * Arm the SIGUSR1 timer.
     */
    if( timer_settime( timerOne, 0, &spec, NULL ) != 0 )
    {
        errHandler( "ERROR: timer_settime(): could not arm interval timer: %s\n",
                                                                strerror( errno ) );
        exit( EXIT_FAILURE );
     }

    /*
     * Create a POSIX interval timer to send SIGUSR2 to ourselves.
     * This timer triggers network interface metrics dumps.
     */
    evpTimer.sigev_notify = SIGEV_SIGNAL;
    evpTimer.sigev_signo = SIGUSR2;

    if( timer_create( CLOCK_REALTIME, &evpTimer, &timerTwo ) != 0 )
    {
        errHandler( "ERROR: timer_create(): could not create interval timer: %s\n",
                                                                  strerror( errno ) );
        exit( EXIT_FAILURE );
     }

    /*
     * Specify that initially, the SIGUSR2 timer will fire one second after
     * being armed and then every ifMetricSecs (user defined) thereafter.
     */
    spec.it_value.tv_sec = 1;
    spec.it_value.tv_nsec = 0;
    spec.it_interval.tv_sec = ifMetricSecs;
    spec.it_interval.tv_nsec = 0;

    /*
     * Arm the SIGUSR2 timer.
     */
    if( timer_settime( timerTwo, 0, &spec, NULL ) != 0 )
    {
        errHandler( "ERROR: timer_settime(): could not arm interval timer: %s\n",
                                                                strerror( errno ) );
        exit( EXIT_FAILURE );
     }

    /*
     * If we're not a daemon, we write a program initialization message to STDOUT.
     */
    printf( "ifchk[%d]: ifchk %s initialized\n", getpid(), VERS );

    if( ( writeLog( LOG_NOTICE, "ifchk %s initialized\n", VERS ) ) != 0 )
    {
        exit( EXIT_FAILURE );
     }

    /*
     * Program signal event loop.
     */
    for( ;; )
    {
        /*
         * We sigwait() here for signals. This loop determines our res-
         * ponse to the reception of SIGINT, SIGTERM, SIGUSR1 & SIGUSR2.
         */
        if( sigwait( &newMask, &sig ) != 0 )
        {
            errHandler( "ERROR: sigwait(): signal selection failed: %s\n", strerror( errno ) );
            exit( EXIT_FAILURE );
         }

        /*
         * We caught SIGINT or SIGTERM - initiate
         * program cleanup and subsequent shutdown.
         */
        if( ( sig == SIGINT ) || ( sig == SIGTERM ) )
        {
            /*
             * Remove the /tmp/.ifchk.pid write lock and then delete the file.
             */
            if( lockCtl( UNLOCK ) != 0 )
            {
                exit( EXIT_FAILURE );
             }

            /*
             * If we're running as a daemon, close() the ifchk.errlog file.
             */
            if( FFlag == FALSE )
            {
                if( close( fileno( errfilefd ) ) != 0 )
                {
                    errHandler( "ERROR: close(): could not close %s: %s\n", errFile,
                                                                   strerror( errno ) );
                    exit( EXIT_FAILURE );
                 }
             }

            /*
             * If we're running as a foreground process, we
             * write a program termination message to STDOUT.
             */
            printf( "ifchk[%d]: ifchk caught signal %d, exiting\n", getpid(), sig );

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

            if( snprintf( sigNumBuf, sizeof( sigNumBuf ), "%d", sig ) < 1 )
            {
                errHandler( "ERROR: snprintf(): could not write to character buffer: %s\n",
                                                                          strerror( errno ) );
                exit( EXIT_FAILURE );
             }

            sigNumBuf[SNBUFSZ - 1] = '\000';

            if( ( writeLog( LOG_NOTICE, "ifchk caught signal %s, exiting\n", sigNumBuf ) ) != 0 )
            {
                exit( EXIT_FAILURE );
             }

            exit( EXIT_SUCCESS );
         }

        /*
         * We caught SIGUSR1 - perform an interface
         * status scan when we call ifStat(), below.
         */
        if( sig == SIGUSR1 )
        {
            iFlag = FALSE;

            /*
             * If disablePromisc is set, report and shutdown promiscuous interfaces.
             */
            if( disablePromisc == 1 )
            {
                dFlag = TRUE;
             }

            /*
             * If disablePromisc is not set, report promiscuous interfaces only.
             */
            else
            {
                nullFlag = TRUE;
             }
         }

        /*
         * We caught SIGUSR2 - perform an interface
         * metrics dump when we call ifStat(), below.
         */
        if( sig == SIGUSR2 )
        {
            dFlag = FALSE;
            nullFlag = FALSE;
            iFlag = TRUE;
         }

        /*
         * Call ifStat().
         */
        if( ifStat() != 0 )
        {
            exit( EXIT_FAILURE );
         }
     }

    return (0);
}


/*****************************************
 * ifchkHelp() - Prints program options. *
 *                                       *
 * Parameters:   None.                   *
 *                                       *
 * Returns:      Nothing.                *
 *****************************************/

void ifchkHelp()
{
    printf( "usage: ifchk [-Fhv] [-f config_file]\n" );
}


/****************************************************
 * ifchkRev()  - Prints program revision, execution *
 *               environment info & build date/time *
 *               stamp.                             *
 *                                                  *
 * Parameters:   None.                              *
 *                                                  *
 * Returns:      0 on success.                      *
 *               -1 on failure.                     *
 ****************************************************/

int ifchkRev( void )
{
    struct utsname nodeId;				/* uname structure. */
    const char buildDate[] = ""__DATE__" "__TIME__"";	/* ifchk build date and time. */

    /*
     * Get information identifying the current system.
     */
    if( ( uname( &nodeId ) ) == -1 )
    {
        errHandler( "ERROR: uname(): could not identify current system: %s\n", strerror( errno ) );

        return (-1);
     }

    /*
     * Print the above along with ifchk build date/time.
     */
    printf( "ifchk %s on %s, %s %s %s\n", VERS, nodeId.nodename, nodeId.sysname,
                                                  nodeId.release, nodeId.machine );
    printf( "build date: %s\n", buildDate );

    return (0);
}
