#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 );
sleep( 5 );
if ( kill( user->sessionLeader, 0 ) == -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 );
}