
/******************************************************************************
**
**  This program is free software; you can redistribute it and/or
**  modify it, however, you cannot sell it.
**
**  This program is distributed in the hope that it will be useful,
**  but WITHOUT ANY WARRANTY; without even the implied warranty of
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
**
**  You should have received a copy of the license attached to the
**  use of this software.  If not, visit www.shmoo.com/osiris for
**  details.
**
******************************************************************************/

/*****************************************************************************
**
**  File:    rootpriv.c
**  Date:    February 17, 2002
**
**  Author:  Brian Wotring
**  Purpose: scanning daemon main implementation
**
******************************************************************************/

#include "libosiris.h"
#include "libfileapi.h"
#include "common.h"

#include "logging.h"
#include "rootpriv.h"

#ifdef USE_PRIVSEP

#include <sys/uio.h>
#include <sys/param.h>
#include <sys/wait.h>

#include <signal.h>


static int received_sigchld = 0;
static int received_sigterm = 0;
static int received_sigint = 0;

extern pid_t child_pid;
extern int rootpriv_pipe[2];      /* socketpair used for rootpriv comm.  */

#define INCOMING_ROOT_REQUEST() ( FD_ISSET( rootpriv_pipe[1], &root_read_set ) )


/* THE FOLLOWING FUNCTIONS RUN AS ROOT!! */

void handle_root_requests()
{
    setup_signals();
    setup_pidfile();

    for(;;)
    {
        handle_signals();
        wait_for_root_request();

        if( INCOMING_ROOT_REQUEST() )
        {
            process_root_request();
        }
    }
}

void wait_for_root_request()
{
    int result;

wait_start:

    FD_ZERO( &root_read_set );

    /* always add the server socket to the set. */

    FD_SET( rootpriv_pipe[1], &root_read_set );

    //log_info( LOG_HIGH, NULL, "waiting for child requets." );
    result = select( ( rootpriv_pipe[1] + 1 ), &root_read_set,NULL,NULL,NULL );

    if( result < 0 )
    {
        if( errno == EINTR )
        {
            handle_signals();
        }

        goto wait_start;
    }
}

void process_root_request()
{
    char result;

    char op; 
    char path[MAX_PATH_LENGTH] = "";

    /* read op command. */

    if( ( osi_read( rootpriv_pipe[1], &op, sizeof( op ) ) ) <= 0 )
    {
        //log_error( LOG_MEDIUM, NULL, "error reading rootpriv operation." );
        return;
    }

    /* now process the request. */

    if( op == ROOT_OP_STAT )
    {
        struct stat stats;

        /* read path. */

        if( ( osi_read( rootpriv_pipe[1], path, sizeof( path ) ) ) <= 0 )
        {
            //log_error( LOG_MEDIUM, NULL, "error reading rootpriv message." );
            return;
        }
        
        if( stat( path, &stats ) == 0 )
        {
            result = ROOT_OP_SUCCESS;
            osi_write( rootpriv_pipe[1], &result, sizeof( result ) );
            osi_write( rootpriv_pipe[1], (char *)&stats, sizeof( stats ) );   
        }

        else
        {
            result = ROOT_OP_FAIL;
            osi_write( rootpriv_pipe[1], &result, sizeof( result ) );
        }
    }

    else if( op == ROOT_OP_LSTAT )
    {
        struct stat stats;

        /* read path. */

        if( ( osi_read( rootpriv_pipe[1], path, sizeof( path ) ) ) <= 0 )
        {
            //log_error( LOG_MEDIUM, NULL, "error reading rootpriv message." );
            return;
        }

#ifdef WIN32
        result = ROOT_OP_FAIL;
        osi_write( rootpriv_pipe[1], &result, sizeof( result ) );
#else
        if( lstat( path, &stats ) == 0 )
        {
            result = ROOT_OP_SUCCESS;
            osi_write( rootpriv_pipe[1], &result, sizeof( result ) );
            osi_write( rootpriv_pipe[1], (char *)&stats, sizeof( stats ) );
        }

        else
        {
            result = ROOT_OP_FAIL;
            osi_write( rootpriv_pipe[1], &result, sizeof( result ) );
        }
#endif
    }

    else if( op == ROOT_OP_OPENDIR )
    {
        DIR *directory;

        /* read path. */

        if( ( osi_read( rootpriv_pipe[1], path, sizeof( path ) ) ) <= 0 )
        {
            //log_error( LOG_MEDIUM, NULL, "error reading rootpriv message." );
            return;
        }

        if( ( directory = opendir( path ) ) == NULL )
        {
            result = ROOT_OP_FAIL;  
            osi_write( rootpriv_pipe[1], &result, sizeof( result ) );
        }

        else
        {
            result = ROOT_OP_SUCCESS;

            osi_write( rootpriv_pipe[1], &result, sizeof( result ) );
            send_fd( rootpriv_pipe[1], dirfd( directory ) );

            closedir(directory);
        }
    }

    else if( op == ROOT_OP_FOPEN )
    {
        int fd;
        FILE *file;

        /* read path. */

        if( ( osi_read( rootpriv_pipe[1], path, sizeof( path ) ) ) <= 0 )
        {
            //log_error( LOG_MEDIUM, NULL, "error reading rootpriv message." );
            return;
        }
        
        file = osi_fopen( path, "r", 0 );

        if( ( file == NULL ) || ( ( fd = fileno( file ) ) <= 0 ) )
        {
            result = ROOT_OP_FAIL;
            osi_write( rootpriv_pipe[1], &result, sizeof( result ) );
        }

        /* send this file descriptor to the child. */

        else
        {
            result = ROOT_OP_SUCCESS;
            osi_write( rootpriv_pipe[1], &result, sizeof( result ) );
            send_fd( rootpriv_pipe[1], fd );
            fclose( file );
        }
    }

    else
    {
        //log_error( LOG_MEDIUM, NULL, "unrecognized rootpriv request." );
    }
}

void setup_signals()
{
    signal( SIGCHLD, root_handle_sigchld );
    signal( SIGTERM, root_handle_sigterm );
    signal( SIGINT, root_handle_sigint );
}

void setup_pidfile()
{
    FILE *pidfile = fopen( PIDFILE, "wb" );

    if( pidfile != NULL )
    {
        fprintf( pidfile, "%ld\n", (long)getpid() );
        fclose( pidfile );
    }
}

void handle_signals()
{
    pid_t pid;
    int status;

    if( received_sigchld )
    {
       while( (pid = waitpid( -1, &status, WNOHANG ) ) > 0 ||
              ( ( pid < 0 ) && ( errno == EINTR ) ) )
        ;

        /* child process has died, we die. */
    
        exit(0);
    }

    if( received_sigterm || received_sigint )
    {
        unlink( PIDFILE );

        /* kill child daemon. */

        kill( child_pid, SIGTERM );
    }
}

void root_handle_sigchld( int signal )
{
    received_sigchld = 1;
}

void root_handle_sigterm( int signal )
{
    received_sigterm = 1;
}

void root_handle_sigint( int signal )
{
    received_sigint = 1;
}


/* THE FOLLOWING FUNCTIONS DO >NOT< RUN AS ROOT. */

FILE * rootpriv_fopen( const char *path )
{
    int result;

    char op = ROOT_OP_FOPEN;
    char response;

    int fd;
    FILE *file;

    if( path == NULL )
    {
        return NULL;
    }

    /* send root operation to root process. */

    if( osi_write( rootpriv_pipe[0], &op, sizeof( op ) ) <= 0 )
    {
        return NULL;
    }

    if( osi_write( rootpriv_pipe[0], path, strlen( path ) ) <= 0 )
    {
        return NULL;
    }

    /* read response. */

    result = osi_read( rootpriv_pipe[0], &response, sizeof( response ) );

    if( ( result <= 0 ) || ( response != ROOT_OP_SUCCESS ) )
    {
        return NULL;
    }

    /* receive the file descriptor. */

    fd = receive_fd( rootpriv_pipe[0] );    

    if( fd <= 0 )
    {
        return NULL;
    }

    return fdopen( fd, "r" );
}

int rootpriv_stat( const char *path, struct stat *file_stats )
{
    int result;

    char op = ROOT_OP_STAT;
    char response;

    if( ( path == NULL ) || ( file_stats == NULL ) )
    {
        return -1;
    }

    /* send root operation to root process. */
 
    if( osi_write( rootpriv_pipe[0], &op, sizeof( op ) ) <= 0 )
    {
        return -1;
    }

    if( osi_write( rootpriv_pipe[0], path, strlen( path ) ) <= 0 )
    {
        return -1;
    }

    /* read response. */

    result = osi_read( rootpriv_pipe[0], &response, sizeof( response ) );

    if( ( result <= 0 ) || ( response != ROOT_OP_SUCCESS ) )
    {
        return -1;
    }

    result = osi_read( rootpriv_pipe[0], (char *)file_stats,
                       sizeof( struct stat ) );

    if( result <= 0 )
    {
        return -1;
    }

    return 0;
}

int rootpriv_lstat( const char *path, struct stat *file_stats )
{
    int result;

    char op = ROOT_OP_LSTAT;
    char response;

    if( ( path == NULL ) || ( file_stats == NULL ) )
    {
        return -1;
    }

    /* send root operation to root process. */

    if( osi_write( rootpriv_pipe[0], &op, sizeof( op ) ) <= 0 )
    {
        return -1;
    }

    if( osi_write( rootpriv_pipe[0], path, strlen( path ) ) <= 0 )
    {
        return -1;
    }

    /* read response. */

    result = osi_read( rootpriv_pipe[0], &response, sizeof( response ) );

    if( ( result <= 0 ) || ( response != ROOT_OP_SUCCESS ) )
    {
        return -1;
    }

    result = osi_read( rootpriv_pipe[0], (char *)file_stats,
                       sizeof( struct stat ) );

    if( result <= 0 )
    {
        return -1;
    }

    return 0;
}

osi_bool rootpriv_opendir( const char *path, OSI_DIRECTORY *dir )
{
    int result;
    int fd;

    char op = ROOT_OP_OPENDIR;
    char response;

    if( path == NULL || dir == NULL )
    {
        return FALSE;
    }

    /* send root operation to root process. */

    if( osi_write( rootpriv_pipe[0], &op, sizeof( op ) ) <= 0 )
    {
        return FALSE;
    }

    if( osi_write( rootpriv_pipe[0], path, strlen( path ) ) <= 0 )
    {
        return FALSE;
    }

    /* read response. */

    result = osi_read( rootpriv_pipe[0], &response, sizeof( response ) );

    if( ( result <= 0 ) || ( response != ROOT_OP_SUCCESS ) )
    {
        return FALSE;
    }

    fd = receive_fd( rootpriv_pipe[0] );

    if( fd <= 0 )
    {
        return FALSE;
    }

    osi_directory_set_fd( dir, fd );

    /* for compatibility with the file api, we read in the first */
    /* filename, if there is one.                                */

    return osi_get_next_file( dir );
}


/*
 * Copyright 2001 Niels Provos <provos@citi.umich.edu>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 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.
 */

void send_fd( int socket, int fd )
{
#if defined(HAVE_SENDMSG) && (defined(HAVE_ACCRIGHTS_IN_MSGHDR) || defined(HAVE_CONTROL_IN_MSGHDR))
    struct msghdr msg;
    struct iovec vec;
    char ch = '\0';
    ssize_t n;
#ifndef HAVE_ACCRIGHTS_IN_MSGHDR
    char tmp[CMSG_SPACE(sizeof(int))];
    struct cmsghdr *cmsg;
#endif

    memset(&msg, 0, sizeof(msg));
#ifdef HAVE_ACCRIGHTS_IN_MSGHDR
    msg.msg_accrights = (caddr_t)&fd;
    msg.msg_accrightslen = sizeof(fd);
#else
    msg.msg_control = (caddr_t)tmp;
    msg.msg_controllen = CMSG_LEN(sizeof(int));
    cmsg = CMSG_FIRSTHDR(&msg);
    cmsg->cmsg_len = CMSG_LEN(sizeof(int));
    cmsg->cmsg_level = SOL_SOCKET;
    cmsg->cmsg_type = SCM_RIGHTS;
    *(int *)CMSG_DATA(cmsg) = fd;
#endif

    vec.iov_base = &ch;
    vec.iov_len = 1;
    msg.msg_iov = &vec;
    msg.msg_iovlen = 1;

    if ((n = sendmsg(socket, &msg, 0)) == -1)
        fprintf(stderr,"unable to send message! (%d)\n",errno );
    if (n != 1)
        fprintf( stderr,"expected different byte count!" );
#else
    fprintf(stderr,"privsep not supported!\n" );
#endif
}


int receive_fd( int socket )
{
#if defined(HAVE_RECVMSG) && (defined(HAVE_ACCRIGHTS_IN_MSGHDR) || defined(HAVE_CONTROL_IN_MSGHDR))
    struct msghdr msg;
    struct iovec vec;
    ssize_t n;
    char ch;
    int fd;
#ifndef HAVE_ACCRIGHTS_IN_MSGHDR
    char tmp[CMSG_SPACE(sizeof(int))];
    struct cmsghdr *cmsg;
#endif

    memset(&msg, 0, sizeof(msg));
    vec.iov_base = &ch;    vec.iov_len = 1;
    msg.msg_iov = &vec;
    msg.msg_iovlen = 1;
#ifdef HAVE_ACCRIGHTS_IN_MSGHDR
    msg.msg_accrights = (caddr_t)&fd;
    msg.msg_accrightslen = sizeof(fd);
#else
    msg.msg_control = tmp;
    msg.msg_controllen = sizeof(tmp);
#endif
    if ((n = recvmsg(socket, &msg, 0)) == -1)
    fprintf(stderr,"error receiving message!!\n" );
    if (n != 1)
    fprintf(stderr," expected different byte receive.\n" );

#ifdef HAVE_ACCRIGHTS_IN_MSGHDR
    if (msg.msg_accrightslen != sizeof(fd))
    fprintf(stderr,"NO FD!\n" );
#else
    cmsg = CMSG_FIRSTHDR(&msg);
    if (cmsg->cmsg_type != SCM_RIGHTS)
        fprintf(stderr,"got wrong receive type!\n" );
    fd = (*(int *)CMSG_DATA(cmsg));
#endif
    return fd;
#else
    fprintf(stderr,"privsep not supported!!!!!\n" );
#endif
}


#endif /* USE_PRIVSEP */

