/************************************************************************
 * filestat.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                                    *
 *                                                           *
 * --------------------------------------------------------- *
 *                                                           *
 * filestat.c       ifchk file attribute check subsystem.    *
 *                                                           *
 * Functions:     - Sanity checks the attributes of files    *
 *                  that ifchk accesses via the use of a     *
 *                  predefined policy.                       *
 *************************************************************/
 
/**********************
 * include directives *
 **********************/

#include "filestat.h"


/*********************************************************
 * statFile()  - Screens files that ifchk accesses in    *
 *               order to enforce a secure file access   *
 *               policy.                                 *
 *               Implemented by examining multiple file  *
 *               attributes (e.g., owner, length, link   *
 *               count, etc).                            *
 *                                                       *
 * Parameters:   *path:      Pathname pointer of a file  *
 *                           to check.                   *
 *               fsObjAttrs: struct of type fsObj who's  *
 *                           members define what file    *
 *                           attributes should be check- *
 *                           ed (beyond those that are   *
 *                           always checked by default). *
 *                                                       *
 * Returns:      File pointer that points to a sanity    *
 *               checked file.                           *
 *               NULL on failure.                        *
 *********************************************************/

FILE *statFile( char *path, struct fsObj fsObjAttrs )
{
    struct stat fileAttrs;	/* File attributes. */
    struct stat linkAttrs;	/* Link attributes. */
    char *filePath = NULL;	/* File to test. */
    FILE *file = NULL;		/* FILE ptr for file to test. */
    mode_t mode = 0;		/* File mode for file to test. */
    struct fsObj objAttrs;	/* File attribute checking policy. */

    filePath = path;
    objAttrs = fsObjAttrs;

    /*
     * Retrieve the attributes of the file
     * to test, prior to its being opened.
     */
    if( ( lstat( filePath, &linkAttrs ) ) != 0 )
    {
        errHandler( "ERROR: lstat(): can't stat file %s: %s\n", filePath, strerror( errno ) );
        return (NULL);
     }

    /*
     * Use the S_ISLNK POSIX macro to check if the
     * file is a symbolic link. ifchk will reject
     * all symbolic links, as a matter of policy.
     */
    if( S_ISLNK( linkAttrs.st_mode ) )
    {
        errHandler( "ERROR: %s is a symbolic link\n", filePath );
        return (NULL);
     }

    /*
     * Open the file read-only,
     * if instructed to do so.
     */
    if( objAttrs.openRead == TRUE )
    {
        if( ( file = fopen( filePath, "r" ) ) == NULL )
        {
            errHandler( "ERROR: fopen(): could not open file %s: %s\n", filePath, strerror( errno ) );
            return (NULL);
         }
     }

    /*
     * Otherwise, open the file create/append.
     */
    else
    {
        if( ( file = fopen( filePath, "ab" ) ) == NULL )
        {
            errHandler( "ERROR: fopen(): could not open file %s: %s\n", filePath, strerror( errno ) );
            return (NULL);
         }
     }

    /*
     * Retrieve the attributes of the file
     * to test, after it has been opened.
     */
    if( ( fstat( fileno( file ), &fileAttrs ) ) != 0 )
    {
        errHandler( "ERROR: fstat(): could not stat file %s: %s\n", filePath, strerror( errno ) );
        return (NULL);
     }

    /*
     * Compare file attributes retrieved before and
     * after the file to test was opened. Any diff-
     * erence between these two sets of attributes
     * signifies the presence of a file based race
     * condition.
     */
    if( ( ( linkAttrs.st_ino ) != ( fileAttrs.st_ino ) ) ||
        ( ( linkAttrs.st_dev ) != ( fileAttrs.st_dev ) ) ||
        ( ( linkAttrs.st_nlink ) != ( fileAttrs.st_nlink ) ) ||
        ( ( linkAttrs.st_mode ) != ( fileAttrs.st_mode ) ) )
    {
        errHandler( "ERROR: %s file attribute inconsistency\n", filePath );
        return (NULL);
     }

    /*
     * Use the S_ISREG POSIX macro to check if the
     * file is a regular file. ifchk will not work
     * with any object that is not a regular file.
     */
    if( ! S_ISREG( fileAttrs.st_mode ) )
    {
        errHandler( "ERROR: %s is not a regular file\n", filePath );
        return (NULL);
     }    

    /*
     * Check that the file to test has a link count
     * of one. This disallows scenarios such as the
     * indirect modification of the file to test by
     * editing some other file that is hard linked
     * to it.
     */
    if( fileAttrs.st_nlink != 1 )
    {
        errHandler( "ERROR: %s has link count of %d\n", filePath, fileAttrs.st_nlink );
        return (NULL);
     }

    /*
     * This is a Linux /proc file specific check.
     * These files should be zero bytes in size.
     */
    if( objAttrs.procLen == TRUE )
    {
        if( ( ( fileAttrs.st_size ) || ( fileAttrs.st_blocks ) ) != 0 )
        {
            errHandler( "%s", "ERROR: /proc/net/dev file size is greater than 0 bytes\n" );
            return (NULL);
         }
     }

    /*
     * This is a Linux /proc file specific check.
     * These files should be chowned root:root.
     */
    if( objAttrs.procOwner == TRUE )
    {
        if( ( ( fileAttrs.st_uid ) || ( fileAttrs.st_gid ) ) != 0 )
        {
            errHandler( "%s", "ERROR: /proc/net/dev is not owned by UID 0, GID 0\n" ); 
            return (NULL);
         }
     }

    /*
     * This is a Linux /proc file specific check.
     * These files should be mode 0444.
     */
    if( objAttrs.procMode == TRUE )
    {
        if( ( mode = fileAttrs.st_mode & ALLPERMS ) != PROCMASK )
        {
            errHandler( "%s", "ERROR: /proc/net/dev permissions are not mode 0444\n" );
            return (NULL);
         }
     }

    /*
     * Check that the file to test is not empty.
     */
    if( objAttrs.regLen == TRUE )
    {
        if( ( ( fileAttrs.st_size ) || ( fileAttrs.st_blocks ) ) == 0 )
        {
            errHandler( "ERROR: %s is empty\n", filePath );
            return (NULL);
         }
     }

    /*
     * Check that the file to test is mode 0600. Issue
     * a warning of it is not, but do not exit ifchk.
     */
    if( objAttrs.regMode == TRUE )
    {
        if( ( ( ( fileAttrs.st_mode ) & ( W_GRP_OTH) ) && ALLPERMS ) )
        {
	        fprintf( stderr, "ifchk[%d]: WARNING: %s: writable by group and/or other\n",
	                                                                  getpid(), filePath );

            if( ( writeLog( LOG_WARNING, "WARNING: %s: writable by group and/or other\n",
                                                                        filePath ) ) != 0 )
            {
                exit( EXIT_FAILURE );
             }
         }
     }

    return (file);
}
