summaryrefslogtreecommitdiffstats
path: root/src/util.c
blob: 729e6ba9deb1f6192f105f21d83bae39c356c283 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#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 );
}