
/******************************************************************************
**
**  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:    scanner.c
**  Date:    March 15, 2002
**  
**  Author:  Brian Wotring
**  Purpose: host daemon scanning module.
**
******************************************************************************/

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

#ifdef WIN32
#include "accctrl.h"
#include "aclapi.h"
#else
#include <syslog.h>
#include <signal.h>
#include <sys/wait.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#endif

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


extern int process_outgoing_message( MESSAGE *message );

extern SSL *scan_data_ssl;
extern int scan_data_socket;

#ifdef WIN32
extern osi_bool GetTextualSid( PSID pSid, LPSTR szTextualSid,
                               LPDWORD dwBufferLen );

extern HANDLE stop_scan_event;
#endif



/******************************************************************************
**
**    Function: osi_scanner_new
**
**    Purpose:  allocate and zero out scanner structure.
**
******************************************************************************/

SCANNER * osi_scanner_new()
{
    SCANNER *new_scanner = (SCANNER *)osi_malloc( sizeof( struct SCANNER ) );
    memset( new_scanner, 0, sizeof( SCANNER ) );
    
    new_scanner->current_block = NULL;
    new_scanner->global_block  = NULL;
    new_scanner->sequence      = 0;
    
    return new_scanner;
}

/******************************************************************************
**
**    Function: osi_scanner_destroy
**
**    Purpose:  free allocated scanner structure.
**
*****************************************************************************/

void osi_scanner_destroy( SCANNER *scanner )
{
    if( scanner != NULL )
    {
        osi_free( scanner );
    }
}

/******************************************************************************
**
**    Function: osi_scanner_set_config
**
**    Purpose:  give config module to scanner, reset current and global block
**		        pointers.
**
******************************************************************************/

void osi_scanner_set_config( SCANNER *scanner, configuration *cfg )
{
    if( ( scanner != NULL ) && ( cfg != NULL ) )
    {
        scanner->config = cfg;
        
        scanner->global_block = osi_config_get_global_block( scanner->config );
        scanner->current_block = NULL;
    }
}

/******************************************************************************
**
**    Function: osi_scanner_run
**
**    Purpose:  send start scan data message, run through blocks.  the process
**		        block function will send the data messages.  then send the
**		        scan data end message.
**
******************************************************************************/

int osi_scanner_run( SCANNER *scanner )
{
    int result = SCANNER_ERROR;
    long current_time;
    
    if( ( scanner != NULL ) && ( scanner->config != NULL ) )
    {
        /* set our start time. */

        current_time = osi_get_time();
        scanner->stats.start_time  = current_time;

        scanner->stats.record_type = SCAN_RECORD_TYPE; 

        /* send a scan-data-first message back to daemon. */
        
        send_scan_data_first( scanner );
        
        /* get first block from the block list and reset stats. */
    
        scanner->current_block = osi_config_get_first_block( scanner->config );
        scanner->sequence      = 0;
        
        /* loop through entire block list processing each one. */
        
        while( scanner->current_block != NULL )
        {
            process_current_block( scanner );
            scanner->current_block = osi_config_get_next_block( scanner->config,                                                    scanner->current_block );
        }
        
        /* set our finish time. */
        
        current_time = osi_get_time();
        scanner->stats.stop_time = current_time;
        
        /* send final stan message. */
        
        send_scan_data_last( scanner );        
        result = SCANNER_OK;
    }
    
    return result;
}


/******************************************************************************
**
**    Function: process_block
**
**    Purpose:  not much.  right now it's basically empty but it
**              we need it because processing a directory could be
**		        recursive, we it's a separate function.
**
******************************************************************************/

void process_current_block( SCANNER *scanner )
{
    if( ( scanner != NULL ) && ( scanner->current_block != NULL ) )
    {
        process_block_root( scanner, scanner->current_block->directory );
        process_directory( scanner, scanner->current_block->directory );
    }
}

/******************************************************************************
**
**    Function: process_block_root
**
**    Purpose:  scan the file that is the root directory of this block. 
**              The root directory of a block is always processed.  if
**              other blocks include this directory, they will ignore it.
**
******************************************************************************/

void process_block_root( SCANNER *scanner, const char *directory_path )
{
    int result = 0;
    struct stat file_stats;
    scanner->stats.files_encountered++;

    memset( &file_stats, 0, sizeof( file_stats ) );

    /* windows doesn't have lstat. */
    
#ifdef WIN32

    if( stat( directory_path, &file_stats ) != 0 )
    {
        send_scan_error( OSI_ERROR_UNABLE_TO_STAT_FILE,
                         "error conducting stat on file \"%s\"",
                          directory_path );

        scanner->stats.directories_unreadable++;
        return;
    }
    
#else

    /* lstat the file so we know what we're dealing with.  */
    /* if access is denied, and we are using privsep, then */
    /* we try again as root.                               */
        
    result = lstat( directory_path, &file_stats );

    #ifdef USE_PRIVSEP
    if( ( result != 0 ) && ( errno == EACCES ) )
    {
        result = rootpriv_lstat( directory_path, &file_stats );
    }
    #endif

    if( result != 0 )
    {
        send_scan_error( OSI_ERROR_UNABLE_TO_STAT_FILE,
                         "error conducting lstat on file \"%s\"",
                         directory_path );

        scanner->stats.directories_unreadable++;
                        
        return;
    }

#endif    

    process_file( scanner, directory_path, &file_stats );
}


/******************************************************************************
**
**    Function: process_directory
**
**    Purpose:  recursive function that processes the file
**              system according to the current block.
**
******************************************************************************/

void process_directory( SCANNER *scanner, const char *directory_path )
{
    osi_bool open_result;
    OSI_DIRECTORY directory;

    char file_path[MAX_PATH_LENGTH] = "";
    char path_separator[2] = { PATH_SEPARATOR, '\0' };

    struct stat file_stats;

#ifndef WIN32
    int length;
    int result;
#endif
    
    block *current_block;

    memset( &file_stats, 0, sizeof( file_stats ) );
    
    if( ( scanner != NULL ) && ( scanner->current_block != NULL ) &&
        ( directory_path != NULL ) )
    {
        current_block = scanner->current_block;  
        memset( &directory, 0, sizeof( directory ) );
        
        open_result = osi_open_directory( directory_path, &directory );

        #ifdef USE_PRIVSEP
        if( ( open_result == FALSE ) && ( errno == EACCES ) )
        {
            open_result = rootpriv_opendir( directory_path, &directory );
        }
        #endif

        if( open_result == FALSE )
        {
            send_scan_error( OSI_ERROR_UNABLE_TO_OPEN_DIRECTORY,
                             "unable to open directory \"%s\"",
                              directory_path );

            scanner->stats.directories_unreadable++;
                    
            return;
        }
                            
        do
        {
            /* for threads, we check here to see if we need  */
            /* to stop scanning.  If so, we free our scanner */
            /* object, and kill ourselves.                   */

            /* NOTE: this is clunky, but it's the only hook  */
            /* into the scanning stream where we can easily  */
            /* quickly, and safely stop threads.             */

#ifdef HAVE_THREADS
            if( !WaitForSingleObject( stop_scan_event, 0 ) )
            {
                osi_scanner_destroy( scanner );

                osi_ssl_destroy( &scan_data_ssl );
                osi_close_socket( scan_data_socket );
                
                _endthreadex(0);
            }
#endif

            /* first, ignore the file if it is either the current directory */
            /* or the previous directory.                                   */
    
            if( ( strcmp( directory.filename, "." ) == 0 ) || 
                ( strcmp( directory.filename, ".." ) == 0 ) )
            {
                continue;
            }
            
            scanner->stats.files_encountered++;
    
            /* create the full path to the file. */
    
            file_path[0] = '\0';
    
            /* don't add an extra '/' if one is already there. */
    
            if( strcmp( directory_path, path_separator ) != 0 )
            {
                osi_strlcat( file_path, directory_path, sizeof( file_path ) );
            }
    
            osi_strlcat( file_path, path_separator, sizeof( file_path ) );
            osi_strlcat( file_path, directory.filename, sizeof( file_path ) );
    
            /* windows doesn't have lstat, too bad for windows. */
            
#ifdef WIN32

            if( stat( file_path, &file_stats ) != 0 )
            {
                send_scan_error( OSI_ERROR_UNABLE_TO_STAT_FILE,
                                 "error conducting stat on file \"%s\"",
                                  file_path );

                scanner->stats.files_unreadable++;
                continue;
            }
#else
            /* lstat the file so we know what we're dealing with. */
                
            result = lstat( file_path, &file_stats );
    
            #ifdef USE_PRIVSEP
            if( ( result != 0 ) && ( errno == EACCES ) )
            {
                result = rootpriv_lstat( file_path, &file_stats );
            }
            #endif

            if( result != 0 )
            {
                send_scan_error( OSI_ERROR_UNABLE_TO_STAT_FILE,
                                 "error conducting lstat on file \"%s\"",
                                 file_path );

                scanner->stats.files_unreadable++;
                continue;
            }
    
            /* check for symbolic links first. if it's a link */
            /* we check to see if we are following links and  */
            /* then we stat the file the link points to.      */

            if( ( result = S_ISLNK( file_stats.st_mode ) ) )
            {
                scanner->stats.symlinks_encountered++;
            }
        
            if( result && current_block->follow_links )
            {
                /* process the link, then traverse it. */

                process_file( scanner, file_path, &file_stats );
                result = stat( file_path, &file_stats );

                #ifdef USE_PRIVSEP
                if( ( result != 0 ) && (errno == EACCES ) )
                {
                    result = rootpriv_stat( file_path, &file_stats );
                }
                #endif

                if( result != 0 )
                {
                    continue;
                }
                    
                /* get the path the link points to. */
    
                length = readlink( file_path, file_path, sizeof( file_path ) );
    
                if( length > 0 )
                {
                    file_path[length] = '\0';
                    scanner->stats.symlinks_followed++;
                }
                
                else
                {
                    send_scan_error( OSI_ERROR_UNABLE_TO_READ_FILE,
                                     "unable to read link \"%s\"",
                                     file_path );

                    scanner->stats.symlinks_unreadable++;
                }
            }
#endif
            /* process the file, this includes directories. */
    
            process_file( scanner, file_path, &file_stats );
    
            /* if it's a directory, we only process it if: */
            /*                                             */
            /*  1) current block is recursive.             */
            /*  2) it's not on the no_enter list.          */
            /*  3) there is no block for it.               */
    
            if( osi_file_is_directory( file_path, &file_stats ) )
            {
                if( current_block->recursive )
                {
                    osi_bool case_sensitive = TRUE;
 
                    /* Mac and Windows case sensitive name list. */

#if defined(WIN32) || defined(SYSTEM_DARWIN)
                    case_sensitive = FALSE;
#endif
                    if( string_list_contains_item( current_block->no_entry_list,
                                                   file_path, case_sensitive ) )
                    {
                        continue;
                    }
    
                    if( string_list_contains_item( scanner->config->block_names,
                                                   file_path, case_sensitive ) )
                    {
                        continue;
                    }
    
                    process_directory( scanner, file_path );
                }
            }
    
        } while( osi_get_next_file( &directory ) );
    
        osi_close_directory( &directory );
    }
    
    else
    {
        log_error( LOG_HIGH, NULL,
                   "configuration block was null or invalid, skipping." );
    }
}

/******************************************************************************
**
**    Function: process_file
**
**    Purpose:  examine the file and loop through the actions for this
**              block until one catches this file.  if caught, we log
**              it and get a checksum, otherwise, we skip over it completely.
**
******************************************************************************/

void process_file( SCANNER *scanner, const char *file_path,
                   struct stat *file_stats )
{
    osi_list action_node;
    action *action;

    osi_bool filter_result;
    
    block *current_block;
    block *global_block;
    
    SCAN_RECORD_STRUCTURE scan_record;
    FILTER_CONTEXT context;

    if( ( scanner == NULL ) || ( file_path == NULL ) || ( file_stats == NULL ) )
    {        
        log_error( LOG_HIGH, NULL,
                   "scanner or file path was null or invalid, skipping." );
        return;
    }

    memset( &context, 0, sizeof( FILTER_CONTEXT ) );
    initialize_scan_record( (SCAN_RECORD *)&scan_record, SCAN_RECORD_TYPE );

    context.scan_record = (SCAN_RECORD *)&scan_record;
    context.file_stats  = file_stats;
    context.file_handle = NULL;
        
    current_block = scanner->current_block;
    global_block  = scanner->global_block;
        
    action_node = current_block->actions->next;
            
    /* if there are no actions in the list, then */
    /* we try to get the global action.          */
    
    if( action_node == NULL )
    {
        action_node = global_block->actions->next;
    }
    
    while( action_node != NULL )
    {
        /* get an action, store action attributes  */
        /* and the filter type.                    */
    
        action = action_node->data;
    
        /* there are 4 possibliities for actions:  */
        /*                                         */
        /* 1) Include filter                       */
        /* 2) Exclude filter                       */
        /* 3) IncludeAll                           */
        /* 4) ExcludeAll                           */
    
        switch( action->type )
        {
            case ACTION_TYPE_INCLUDE:

                context.filter_argument = action->action_filter.argument;

                filter_result = action->action_filter.perform( file_path,
                                                               &context );
                    
                if( filter_result )
                {
                    osi_strlcpy( scan_record.path, file_path,
                                 MAX_PATH_LENGTH );

                    scan_record.filter = action->action_filter.type;
                      
                    populate_scan_record( scanner, (SCAN_RECORD *)&scan_record,
                                          file_stats, context.file_handle );

                    send_scan_data( scanner, (SCAN_RECORD *)&scan_record );

                    #ifdef SYSTEM_DARWIN
                    if( !S_ISDIR( file_stats->st_mode ) )
                    {
                        process_file_rfork( scanner,
                                           (SCAN_RECORD *)&scan_record,
                                            context.file_handle );
                    }
                    #endif

                    goto exit_gracefully;
                }
    
                break;
    
            case ACTION_TYPE_EXCLUDE:
  
                context.filter_argument = action->action_filter.argument;
                filter_result = action->action_filter.perform( file_path,
                                                               &context );
    
                if( filter_result )
                {
                    goto exit_gracefully;
                }
    
                break;
    
            case ACTION_TYPE_INCLUDE_ALL:
                    
                osi_strlcpy( scan_record.path, file_path, MAX_PATH_LENGTH );
                scan_record.filter = action->action_filter.type;

                populate_scan_record( scanner, (SCAN_RECORD *)&scan_record,
                                      file_stats, context.file_handle );

                send_scan_data( scanner, (SCAN_RECORD *)&scan_record );

                #ifdef SYSTEM_DARWIN
                if( !S_ISDIR( file_stats->st_mode ) )
                {
                    process_file_rfork( scanner,
                                        (SCAN_RECORD *)&scan_record,
                                        context.file_handle );
                }
                #endif
                
                goto exit_gracefully;    
                break;
    
            case ACTION_TYPE_EXCLUDE_ALL:
       
                goto exit_gracefully; 
                break;
    
            case ACTION_TYPE_UNKNOWN:
                default:
    
                log_error( LOG_HIGH, NULL,"unrecognized action in block!" );
                break;
       }
    
        /* move to the next action. */
    
        action_node = action_node->next;
    }

exit_gracefully:

    if( context.file_handle != NULL )
    {
        fclose( context.file_handle );
    }
}

/******************************************************************************
**
**    Function: process_file_rfork
**
**    Purpose:  same as process file but for resource forks HFS+ on Mac OS X.
**              and Darwin systems.  Assume the scan record has been 
**              populated with the file in question.
**
******************************************************************************/

#if defined(SYSTEM_DARWIN)
void process_file_rfork( SCANNER *scanner, SCAN_RECORD *scan_record, FILE *f )
{
    SCAN_RECORD_UNIX1 *r = (SCAN_RECORD_UNIX1 *)scan_record;
    struct stat file_stats;
    
    if( ( scanner == NULL || scan_record == NULL ) )
    {
        return;
    }

    /* first, stat the resource fork of the given file */
    /* the file size must be greater than zero, or we  */
    /* don't care.                                     */

    osi_strlcat( r->path, "/rsrc", sizeof( r->path ) );

    if( stat( r->path, &file_stats ) != 0 )
    {
        return;
    }

    if( file_stats.st_size == 0 )
    {
        return;
    }

    /* we have a resource fork.  Now we populate the record and */
    /* we send the record back to the mhost.                    */

    unwrap_scan_record( scan_record );
    populate_scan_record( scanner, scan_record, &file_stats, f );

    send_scan_data( scanner, scan_record );
}
#endif

/******************************************************************************
**
**    Function: populate_scan_record
**
**    Purpose:  based on the scan record type, we populate the scan_record
**		        fields appropriately.  If some of the string attributes are
**              set, don't go through the process of getting the values again.
**
******************************************************************************/

void populate_scan_record( SCANNER *scanner, SCAN_RECORD *record,
                           struct stat *file_stats, FILE *file )
{
    osi_uint16 type;
    FILE *file_handle = file;

    if( ( scanner == NULL ) || ( record == NULL ) || ( file_stats == NULL ) )
    {
        return;
    }

    type = record->type;

#ifdef WIN32

    if( type == SCAN_RECORD_TYPE_WINNT )
    {
        SCAN_RECORD_WINNT *scan_record = (SCAN_RECORD_WINNT *)record;
        
        /* set the standard stat fields. */
        
        scan_record->device          = (osi_uint64)file_stats->st_dev;
        scan_record->mtime 	         = (osi_uint64)file_stats->st_mtime;
        scan_record->atime 	         = (osi_uint64)file_stats->st_atime;
        scan_record->ctime 	         = (osi_uint64)file_stats->st_ctime;
        scan_record->bytes           = (osi_uint64)file_stats->st_size;

        /* get file attributes, convert to our flags.*/

        scan_record->attributes = get_win32_file_attributes( scan_record->path);
       
        /* if not set, get values for user,group and permissions. */
                                
        if( scan_record->owner[0] == 0 )
        {
            get_owner_information( (SCAN_RECORD *)scan_record );
        }

        if( scan_record->group[0] == 0 )
        {
            get_group_information( (SCAN_RECORD *)scan_record ); 
        }
        
        /* get file checksum, if not already done, and set algorithm  */
        /* type if it is a directory, or a zero length file, we don't */
        /* hash and  set the algorithm to 'none'                      */
        
        scan_record->checksum_algorithm =
                        (osi_uint16)scanner->current_block->hash;
        
        if( scan_record->checksum[0] == 0 )
        {
            if( ( file_stats->st_size > 0 ) &&
              !( osi_file_is_directory( scan_record->path, file_stats ) ) )
            {
                switch( (int)scanner->current_block->hash )
                {
                    case HASH_MD5:
        
                        hash_file_md5( scan_record->path, file_handle,
                                       scan_record->checksum,
                                       MAX_CHECKSUM_LENGTH );
                        break;
        
                    case HASH_SHA:

                        hash_file_sha( scan_record->path, file_handle,
                                       scan_record->checksum,
                                       MAX_CHECKSUM_LENGTH );
                        break;
        
                    case HASH_RIPEMD:
        
                        hash_file_ripemd( scan_record->path, file_handle,
                                          scan_record->checksum,
                                          MAX_CHECKSUM_LENGTH );
                        break;
        
                    default:

                        hash_file_md5( scan_record->path, file_handle,
                                       scan_record->checksum,
                                       MAX_CHECKSUM_LENGTH );
                        break;
                }
            }
            
            else
            {
                scan_record->checksum_algorithm = (osi_uint16)HASH_NULL;
            }
        }
    }

    if( type == SCAN_RECORD_TYPE_WIN32 )
    {

    }

#else
    if( type == SCAN_RECORD_TYPE_UNIX1 )
    {
        SCAN_RECORD_UNIX1 *scan_record = (SCAN_RECORD_UNIX1 *)record;
        
        /* set the standard stat fields. */
        
        scan_record->device          = (osi_uint64)file_stats->st_dev;
        scan_record->inode           = (osi_uint64)file_stats->st_ino;
        scan_record->permissions     = (osi_uint64)file_stats->st_mode;
        scan_record->links           = (osi_uint64)file_stats->st_nlink;
        scan_record->uid             = (osi_uint64)file_stats->st_uid;
        scan_record->gid             = (osi_uint64)file_stats->st_gid;

        if( ( ( file_stats->st_mode & S_IFMT ) == S_IFCHR ) ||
            ( ( file_stats->st_mode & S_IFMT) == S_IFBLK ) )
        {

            scan_record->device_type     = (osi_uint64)file_stats->st_rdev;
        }

        else
        {
            scan_record->device_type     = 0; 
        }
        
        scan_record->mtime 	         = (osi_uint64)file_stats->st_mtime;
        scan_record->atime 	         = (osi_uint64)file_stats->st_atime;
        scan_record->ctime 	         = (osi_uint64)file_stats->st_ctime;

        scan_record->bytes           = (osi_uint64)file_stats->st_size;
        scan_record->blocks	         = (osi_uint64)file_stats->st_blocks;
        scan_record->block_size	 = (osi_uint64)file_stats->st_blksize;
        
        /* if not set, get values for user,group and permissions. */
                                
        if( scan_record->user[0] == 0 )
        {
            struct passwd *file_owner = getpwuid( file_stats->st_uid );
            
            if( file_owner != NULL )
            {
                osi_strlcpy( scan_record->user, file_owner->pw_name,
                             USERNAME_LENGTH );
            }
        }

        if( scan_record->group[0] == 0 )
        {
            struct group *file_group = getgrgid( file_stats->st_gid );
            
            if( file_group != NULL )
            {
                osi_strlcpy( scan_record->group, file_group->gr_name,
                             GROUPNAME_LENGTH );
            }
        }

        if( scan_record->permissions_string[0] == 0 )
        { 
            int length = PERMISSIONS_STRING_LENGTH;
            get_file_attribute_string( scan_record->permissions_string,
                                       length, file_stats->st_mode );
        }
        
        /* get file checksum, if not already done, and set algorithm */
        /* type if it is a zero length file, we don't hash and set   */
        /* algorithm to none.                                        */
        
        scan_record->checksum_algorithm =
                        (osi_uint16)scanner->current_block->hash;
        
        if( scan_record->checksum[0] == 0 )
        {

            if( ( file_stats->st_size > 0 ) &&
              !( osi_file_is_directory( scan_record->path, file_stats ) ) )
            {
                /* open file first in case we need to use privsep. */

                if( file_handle == NULL )
                {
                    file_handle = osi_fopen( scan_record->path, "r", 0 );
       
                    #ifdef USE_PRIVSEP
                    if( ( file_handle == NULL ) && ( errno == EACCES ) )
                    {
                        file_handle = rootpriv_fopen( scan_record->path );
                    }
                    #endif 
                }

                if( file_handle == NULL )
                {
                    return;
                }
                
                switch( (int)scanner->current_block->hash )
                {
                    case HASH_MD5:
        
                        hash_file_md5( scan_record->path, file_handle,
                                       scan_record->checksum,
                                       MAX_CHECKSUM_LENGTH );
                        break;
        
                    case HASH_SHA:

                        hash_file_sha( scan_record->path, file_handle,
                                       scan_record->checksum,
                                       MAX_CHECKSUM_LENGTH );
                        break;
        
                    case HASH_RIPEMD:
        
                        hash_file_ripemd( scan_record->path, file_handle,
                                          scan_record->checksum,
                                          MAX_CHECKSUM_LENGTH );
                        break;
        
                    default:

                        hash_file_md5( scan_record->path, file_handle,
                                       scan_record->checksum,
                                       MAX_CHECKSUM_LENGTH );
                        break;
                }
            }
            
            else
            {
                scan_record->checksum_algorithm = (osi_uint16)HASH_NULL;
            }
        }
    }
    
    if( type == SCAN_RECORD_TYPE_UNIX2 )
    {
        /* not implemented yet. */
    }
#endif /* WIN32 */

    /* if we opened it in this function, we close it. */

    if( ( file_handle != NULL ) && ( file == NULL ) )
    {
        fclose( file_handle );
    }

}

osi_uint64 get_win32_file_attributes( const char *path )
{
    osi_uint64 attrs = 0;

#ifdef WIN32

    DWORD f_attrs;

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

    f_attrs = GetFileAttributes( path );

    if( f_attrs >= 0 )
    {
        if( f_attrs & FILE_ATTRIBUTE_ARCHIVE )
        {
            attrs |= WIN32_FILE_ATTR_ARCHIVE;
        }

        if( f_attrs & FILE_ATTRIBUTE_COMPRESSED )
        {
            attrs |= WIN32_FILE_ATTR_COMPRESSED;
        }

        if( f_attrs & FILE_ATTRIBUTE_DIRECTORY )
        {
            attrs |= WIN32_FILE_ATTR_DIRECTORY;
        }

        if( f_attrs & FILE_ATTRIBUTE_ENCRYPTED )
        {
            attrs |= WIN32_FILE_ATTR_ENCRYPTED;
        }

        if( f_attrs & FILE_ATTRIBUTE_HIDDEN )
        {
            attrs |= WIN32_FILE_ATTR_HIDDEN;
        }

        if( f_attrs & FILE_ATTRIBUTE_NORMAL )
        {
            attrs |= WIN32_FILE_ATTR_NORMAL;
        }

        if( f_attrs & FILE_ATTRIBUTE_NOT_CONTENT_INDEXED )
        {
            attrs |= WIN32_FILE_ATTR_NOT_INDEXED;
        }

        if( f_attrs & FILE_ATTRIBUTE_OFFLINE )
        {
            attrs |= WIN32_FILE_ATTR_OFFLINE;
        }

        if( f_attrs & FILE_ATTRIBUTE_READONLY )
        {
            attrs |= WIN32_FILE_ATTR_READONLY;
        }

        if( f_attrs & FILE_ATTRIBUTE_REPARSE_POINT )
        {
            attrs |= WIN32_FILE_ATTR_REPARSE_POINT;
        }

        if( f_attrs & FILE_ATTRIBUTE_SPARSE_FILE )
        {
            attrs |= WIN32_FILE_ATTR_SPARSE_FILE;
        }

        if( f_attrs & FILE_ATTRIBUTE_SYSTEM )
        {
            attrs |= WIN32_FILE_ATTR_SYSTEM;
        }

        if( f_attrs & FILE_ATTRIBUTE_TEMPORARY )
        {
            attrs |= WIN32_FILE_ATTR_TEMPORARY;    
        }
    }
    
#endif

    return attrs;
    
}

/******************************************************************************
**
**    Function: send_scan_data_first
**
**    Purpose:  send a scan-data-first message back up to the parent deamon
**              process.
**
******************************************************************************/

int send_scan_data_first( SCANNER *scanner )
{
    int result = SCANNER_ERROR;
    MESSAGE message;
    
    if( scanner != NULL )
    {
        osi_uint64 type = SCAN_RECORD_TYPE;
        initialize_message( &message, MESSAGE_TYPE_SCAN_DATA_FIRST );
        
        type = HTONLL( type );
        message_set_payload( &message, &type, sizeof( type ), 0 );
        
        result = process_outgoing_message( &message );
        
        if( result == MESSAGE_OK )
        {
            result = SCANNER_OK;
        }
        
        else
        {
            log_error( LOG_MEDIUM, NULL, "unable to send first data message." );
        }
    }
    
    return result;
}

/******************************************************************************
**
**    Function: send_scan_data
**
**    Purpose:  send a scan-data message back up to the parent deamon process. 
**		        set the sequence appropriately.
**
******************************************************************************/

int send_scan_data( SCANNER *scanner, SCAN_RECORD *record )
{
    int result = SCANNER_ERROR;
    osi_uint16 type;

    MESSAGE message;
    unsigned char buffer[MAX_SCAN_RECORD_LENGTH];
    
    if( scanner != NULL )
    {
        int record_size;

        initialize_message( &message, MESSAGE_TYPE_SCAN_DATA );
        type = record->type;
        
        /* we need to wrap the record contents before pushing over */
        /* the network, then we need to pack the structure.        */

        wrap_scan_record( record );
        record_size = pack_scan_record( record, buffer, sizeof( buffer ) );

        message_set_payload( &message, (void *)buffer, record_size,
                             scanner->sequence );

        result = process_outgoing_message( &message );
        
        if( result == MESSAGE_OK )
        {
            result = SCANNER_OK;
            
            scanner->sequence += 1;
            scanner->stats.files_scanned++;
        }
        
        else
        {
            log_error( LOG_MEDIUM, NULL, "unable to send scan data message." );
        }
    }

    return result;
}

/******************************************************************************
**
**    Function: send_scan_data_last
**
**    Purpose:  send a scan-data-last message back up to the parent
**              deamon process. this signals the end of the scan data
**              transmission.
**
******************************************************************************/

int send_scan_data_last( SCANNER *scanner )
{
    int result = SCANNER_ERROR;
    MESSAGE message;
    
    if( scanner != NULL )
    {
        initialize_message( &message, MESSAGE_TYPE_SCAN_DATA_LAST );
        wrap_scan_results( &( scanner->stats ) );

        message_set_payload( &message, &( scanner->stats ),
                             sizeof( scanner->stats ), scanner->sequence );
        
        result = process_outgoing_message( &message );
        
        if( result == MESSAGE_OK )
        {
            result = SCANNER_OK;
        }
        
        else
        {
            log_error( LOG_MEDIUM, NULL,
                       "unable to send last scan data message." );
        }
    }
    
    return result;
}

/******************************************************************************
**
**    Function: send_scan_error
**
**    Purpose:  send a error message back up to the parent deamon process.
**		        this should contain a payload with an error string.
**
******************************************************************************/

int send_scan_error( int type, char *error_message, ... )
{
    int result = SCANNER_ERROR;  
    OSI_ERROR error;
    
    char buffer[MAX_MESSAGE_DATA_SIZE] = "";
    MESSAGE message;
    
    if( error_message != NULL )
    {        
        va_list ap;
        va_start( ap, error_message );
        osi_vsnprintf( buffer, MAX_MESSAGE_DATA_SIZE, error_message, ap );
        va_end( ap );
        
        /* setup error. */
        
        memset( &error, 0, sizeof( error ) );
        error.type = (osi_uint64)type;
        osi_strlcpy( error.message, buffer, sizeof( error.message ) );
                
        /* put into a message. */
        
        initialize_message( &message, MESSAGE_TYPE_ERROR );
        wrap_error( &error );
        message_set_payload( &message, &error, sizeof( error ), 0 );
        
        result = process_outgoing_message( &message );
        
        if( result == MESSAGE_OK )
        {
            result = SCANNER_OK;
        }
        
        else
        {
            log_error( LOG_MEDIUM, NULL,
                      "unable to send error message (%d:%s).",
                       result, buffer );
        }
    }
    
    return result;
}


#ifdef WIN32

osi_bool get_owner_information( SCAN_RECORD *scan_record )
{
    DWORD dwRtnCode = 0;
    PSID pSidOwner;
    BOOL result        = FALSE;
    LPTSTR AcctName    = NULL;
    DWORD dwAcctName   = 1;
    DWORD dwDomainName = 1;
    SID_NAME_USE eUse  = SidTypeUnknown;

    PSECURITY_DESCRIPTOR pSD;
    SCAN_RECORD_WINNT *record;

    int length;

    unsigned char psd[sizeof(PSECURITY_DESCRIPTOR)];
    unsigned char psid[sizeof(PSID)];

    if( scan_record != NULL )
    {
        record = (SCAN_RECORD_WINNT *)scan_record;

        // Allocate memory for the SID structure.
        pSidOwner = (PSID)psid;
    
        // Allocate memory for the security descriptor structure.
        pSD = (PSECURITY_DESCRIPTOR)psd;
    
        // Get the owner SID of the file.
        dwRtnCode = GetNamedSecurityInfo(
                    (LPTSTR)record->path,
                    SE_FILE_OBJECT,
                    OWNER_SECURITY_INFORMATION,
                    &pSidOwner,
                    NULL,
                    NULL,
                    NULL,
                    &pSD);

        // Check GetLastError for GetSecurityInfo error condition.
        if( dwRtnCode != ERROR_SUCCESS ) 
        {
            result = FALSE;
            goto exit_error;
        }

        // get sid string for owner.

        length = sizeof( record->owner_sid );
        GetTextualSid( pSidOwner, (LPSTR)record->owner_sid, (LPDWORD)&length );
        
        // First call to LookupAccountSid to get the buffer sizes.
        result = LookupAccountSid(
                        NULL,           // local computer
                        pSidOwner,
                        AcctName,
                        (LPDWORD)&dwAcctName,
                        NULL,
                        (LPDWORD)&dwDomainName,
                        &eUse);
        
        // Reallocate memory for the buffers.
        AcctName = (char *)GlobalAlloc( GMEM_FIXED, dwAcctName );
        
        // Check GetLastError for GlobalAlloc error condition.
        if( AcctName == NULL ) 
        {
            result = FALSE;
            goto exit_error;
        }
        
        // Second call to LookupAccountSid to get the account name.
        result = LookupAccountSid(
                NULL,                        // name of local or remote computer
                pSidOwner,                   // security identifier
                AcctName,                    // account name buffer
                (LPDWORD)&dwAcctName,        // size of account name buffer
                NULL,			             // domain name
                (LPDWORD)&dwDomainName,      // size of domain name buffer
                &eUse);                      // SID type
        
        if( result )
        {
            osi_strlcpy( record->owner, AcctName, sizeof( record->owner ) );
            result = TRUE;

            goto exit_gracefully;
        }
    }

exit_gracefully:

    GlobalFree( (HGLOBAL)AcctName );

exit_error:

    GlobalFree( (HGLOBAL)pSD );
    GlobalFree( (HGLOBAL)pSidOwner );

    return result;
}

osi_bool get_group_information( SCAN_RECORD *scan_record )
{
    DWORD dwRtnCode    = 0;
    PSID pSidGroup;
    BOOL result        = FALSE;
    LPTSTR GroupName   = NULL;
    DWORD dwGroupName  = 1;
    DWORD dwDomainName = 1;
    SID_NAME_USE eUse  = SidTypeUnknown;

    PSECURITY_DESCRIPTOR pSD;
    SCAN_RECORD_WINNT *record;

    int length;

    unsigned char psd[sizeof(PSECURITY_DESCRIPTOR)];
    unsigned char psid[sizeof(PSID)];

    if( scan_record != NULL )
    {
        record = (SCAN_RECORD_WINNT *)scan_record;

        // Allocate memory for the SID structure.
        pSidGroup = (PSID)psid;
    
        // Allocate memory for the security descriptor structure.
        pSD = (PSECURITY_DESCRIPTOR)psd;
    
        // Get the owner SID of the file.
        dwRtnCode = GetNamedSecurityInfo(
                    (LPTSTR)record->path,
                    SE_FILE_OBJECT,
                    GROUP_SECURITY_INFORMATION,
					NULL,
                    &pSidGroup,
                    NULL,
                    NULL,
                    &pSD);

        // Check GetLastError for GetSecurityInfo error condition.
        if( dwRtnCode != ERROR_SUCCESS ) 
        {
            result = FALSE;
            goto exit_error;
        }

        // get group sid string.

        length = sizeof( record->group_sid );
        GetTextualSid( pSidGroup, record->group_sid, (LPDWORD)&length );
        
        // First call to LookupAccountSid to get the buffer sizes.
        result = LookupAccountSid(
                        NULL,           // local computer
                        pSidGroup,
                        GroupName,
                        (LPDWORD)&dwGroupName,
                        NULL,
                        (LPDWORD)&dwDomainName,
                        &eUse);
        
        // Reallocate memory for the buffers.
        GroupName = (char *)GlobalAlloc( GMEM_FIXED, dwGroupName );
        
        // Check GetLastError for GlobalAlloc error condition.
        if( GroupName == NULL ) 
        {
            result = FALSE;
            goto exit_error;
        }
        
        // Second call to LookupAccountSid to get the account name.
        result = LookupAccountSid(
                NULL,                      // name of local or remote computer
                pSidGroup,                 // security identifier
                GroupName,                 // account name buffer
                (LPDWORD)&dwGroupName,     // size of account name buffer
                NULL,			           // domain name
                (LPDWORD)&dwDomainName,    // size of domain name buffer
                &eUse);                    // SID type
        
        if( result )
        {
            osi_strlcpy( record->group, GroupName, sizeof( record->group ) );
          
            result = TRUE;
            goto exit_gracefully;
        }
    }

exit_gracefully:

    GlobalFree( (HGLOBAL)GroupName );

exit_error:

    GlobalFree( (HGLOBAL)pSD );
    GlobalFree( (HGLOBAL)pSidGroup );

    return result;
}

#endif



