summaryrefslogblamecommitdiffstats
path: root/src/util.c
blob: 729e6ba9deb1f6192f105f21d83bae39c356c283 (plain) (tree)




















                                
                  








                                                            
                      









                                                              
                       






















































                                                                                               
                                                                                     


                                                                                 









































































                                                                                                                                   
      




                                                        
      





                                   
#include "util.h"
#include "userlist.h"

#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <signal.h>
#include <pwd.h>
#include <poll.h>

int _testmode = 0;

// Base time for monotonic clock
static struct timespec basetime;

time_t now( void )
{
    struct timespec retval;
    if ( clock_gettime( CLOCK_MONOTONIC, &retval ) == -1 ) {
        perror( "Cannot clock_gettime CLOCK_MONOTONIC" );
        abort();
    }
    return (time_t)( retval.tv_sec - basetime.tv_sec );
}

void init_time( void )
{
    if ( clock_gettime( CLOCK_MONOTONIC, &basetime ) == -1 ) {
        perror( "cannot clock_gettime CLOCK_MONOTONIC" );
        basetime.tv_sec = 0; // Hope it doesn't overflow
    }
}

/**
 * Return false = parent, true = second child
 */
bool doublefork( void )
{
    const pid_t c1 = fork();
    if ( c1 == -1 ) {
        perror( "doublefork: First fork failed" );
        return false;
    }
    if ( c1 != 0 ) {
        // Parent (main program)
        int ws;
        while ( waitpid( c1, &ws, 0 ) == -1 && errno == EINTR ) {}
        return false;
    }
    // This is executed by first child
    signal( SIGCHLD, SIG_IGN );
    // Fork again
    const pid_t c2 = fork();
    if ( c2 == -1 ) {
        perror( "doublefork: Second fork failed" );
        abort();
    }
    if ( c2 != 0 ) {
        // Second parent (first child), exit immediately
        exit( 0 );
    }
    // Second child, this is what we want
    signal( SIGCHLD, SIG_DFL );
    return true;
}

bool waitRead( int fd, int ms )
{
    struct pollfd pd = { .fd = fd, .events = POLLIN | POLLHUP | POLLRDHUP };
    return poll( &pd, 1, ms ) > 0 && ( pd.revents & POLLIN ) != 0;
}

void copy( const char *src, char *dst, size_t len, const char *stop )
{
    const char *end = src + len - 1;
    while ( src < end && *src != '\0' && ( stop == NULL || strchr( stop, *src ) == NULL ) ) {
        *dst++ = *src++;
    }
    *dst = '\0';
}

void killSession( const struct user * user )
{
    if ( _testmode ) {
        printf( "Not really killing session %s of user %s.\n", user->sessionName, user->user );
        return;
    }
    if ( ! doublefork() )
        return;
    // Async do NOT use return from here on
    if ( user->sessionName[0] != '\0' ) {
        printf( "Terminating session %s of %s.\n", user->sessionName, user->user );
        run( true, "loginctl", "terminate-session", user->sessionName, (char*)NULL );
        sleep( 2 );
        if ( kill( user->sessionLeader, SIGTERM ) == -1
                || user->sessionHead == 0 || kill( user->sessionHead, 0 ) == -1 )
            exit( 0 );
    }
    if ( user->sessionHead != 0 && kill( user->sessionHead, SIGTERM ) == 0 ) {
        printf( "Session %s seems not entirely dead, TERMing loginpid %d...\n", user->sessionName, (int)user->sessionHead );
        sleep( 2 );
    }
    if ( ( killpg( user->sessionLeader, SIGTERM ) & kill( user->sessionLeader, SIGTERM ) ) == 0 ) {
        printf( "Session %s seems not entirely dead, TERMing process group %d...\n", user->sessionName, (int)user->sessionLeader );
        sleep( 3 );
    }
    // Maybe we need KILL now...
    if ( user->sessionHead != 0 && kill( user->sessionHead, SIGKILL ) == 0 ) {
        printf( "Session %s seems not entirely dead, KILLing %d...\n", user->sessionName, (int)user->sessionHead );
    }
    exit( 0 );
}

void redirect( int newIn, int newOut )
{
    if ( newIn > 2 ) {
        dup2( newIn, STDIN_FILENO );
        close( newIn );
    }
    if ( newOut > 2 ) {
        dup2( newOut, STDOUT_FILENO );
        close( newOut );
    }
}

void switchUserSafe( const char* user )
{
    errno = 0;
    struct passwd *u = getpwnam( user );
    if ( u == NULL ) {
        if ( errno != 0 ) {
            perror( "switchUserSafe: Cannot switch to user" );
        } else {
            fprintf( stderr, "switchUserSafe: Cannot switch to user %s: User not known.\n", user );
        }
        exit( 1 );
    }
    chdir( "/" );
    if ( setgid( u->pw_gid ) != 0 ) {
        perror( "switchUserSafe: setgid failed" );
        exit( 1 );
    }
    if ( setuid( u->pw_uid ) != 0 ) {
        perror( "switchUserSafe: setuid failed" );
        exit( 1 );
    }
    if ( u->pw_dir == NULL ) {
        unsetenv( "HOME" );
    } else {
        setenv( "HOME", u->pw_dir, 1 );
    }
    setenv( "USER", u->pw_name, 1 );
}

void run( bool detach, const char *file, ... )
{
    if ( detach && ! doublefork() )
        return;
    char *argv[100];
    argv[0] = strdup( file );
    argv[99] = NULL;
    va_list ap;
    va_start( ap, file );
    for ( int i = 1; i < 99; ++i ) {
        argv[i] = va_arg( ap, char* );
        if ( argv[i] == NULL )
            break;
        argv[i] = strdup( argv[i] );
    }
    va_end( ap );
    /*
    printf( "Running: '%s'", file );
    for ( int i = 0; i < 100 && argv[i] != NULL; ++i ) {
        printf( " '%s'", argv[i] );
    }
    printf( "\n" );
    */
    execvp( strdup( file ), argv );
    // Something went wrong...
    perror( "run execvp failed" );
    exit( 1 );
}