#include "rpc.h" #include "util.h" #include "main.h" #include "userlist.h" #include #include #include #include #include #include #include #include #include #include #define SENDSTRING(fd,x) write( fd, x, strlen(x) ) #define SOCKPATH "/run/idle-daemon" static union { struct __attribute__((packed)) { int read; int write; }; int array[2]; } rpcPipe = { .read = -1, .write = -1, }; _Static_assert( sizeof(rpcPipe) == ( sizeof(int) * 2 ), "Structsize mismatch" ); static void handleClient( int fd, struct ucred *user ); int rpc_open( void ) { int fd = socket( AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0 ); if ( fd == -1 ) { perror( "Cannot create local RPC socket" ); return -1; } struct sockaddr_un address = { .sun_family = AF_UNIX, .sun_path = SOCKPATH, }; unlink( SOCKPATH ); if ( bind( fd, (struct sockaddr *)&address, sizeof(address) ) == -1 ) { perror( "Could not bind RPC socket" ); close( fd ); return -1; } chmod( SOCKPATH, 0777 ); if ( listen( fd, 10 ) == -1 ) { perror( "Could not listen() on RPC socket" ); close( fd ); return -1; } if ( rpcPipe.write == -1 && rpcPipe.read == -1 ) { if ( pipe2( rpcPipe.array, O_DIRECT ) == -1 ) { perror( "Cannot create local RPC pipe" ); } else { // Read end nonblocking int flags = fcntl( rpcPipe.read, F_GETFL, 0 ); fcntl( rpcPipe.read, F_SETFL, flags | O_NONBLOCK ); } } return fd; } bool rpc_wait( int listenFd, int seconds ) { struct pollfd pd[2] = { { .fd = listenFd, .events = POLLIN | POLLHUP | POLLRDHUP }, { .fd = rpcPipe.read, .events = POLLIN | POLLHUP | POLLRDHUP }, }; int ret = poll( pd, 2, seconds * 1000 ); if ( ret == -1 ) { perror( "Error polling RPC sockets" ); } return ret > 0; } void rpc_handle( int listenFd ) { int fd; while ( ( fd = accept( listenFd, NULL, NULL ) ) != -1 ) { // Determine who connected socklen_t len; struct ucred ucred; len = sizeof(struct ucred); if ( getsockopt( fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len ) == -1 ) { perror( "Could not get credentials of connection" ); close( fd ); // TODO: Allow more for root (eg. cancel reboot/poweroff) continue; } if ( ! doublefork() ) { close( fd ); continue; // Parent continues } handleClient( fd, &ucred ); exit( 0 ); } if ( errno != EAGAIN && errno != EWOULDBLOCK ) { perror( "accept() on RPC socket failed" ); } // Check if any RPC client talks to us char buffer[200]; ssize_t len; while ( ( len = read( rpcPipe.read, buffer, sizeof(buffer) - 1 ) ) > 0 ) { buffer[len] = '\0'; const char *ptr = NULL; enum Shutdown action = SHUTDOWN_ENUM_END; if ( strncmp( buffer, "reboot ", 7 ) == 0 ) { ptr = buffer + 7; action = REBOOT; } else if ( strncmp( buffer, "poweroff ", 9 ) == 0 ) { ptr = buffer + 9; action = POWEROFF; } else if ( strncmp( buffer, "kexec ", 6 ) == 0 ) { ptr = buffer + 6; action = KEXEC; } else { fprintf( stderr, "Unknown RPC pipe callback: %s\n", buffer ); } if ( ptr != NULL ) { char *end; int seconds = strtol( ptr, &end, 10 ); if ( end != ptr && seconds >= 0 ) { main_queueAction( action, seconds ); } else { fprintf( stderr, "RPC: Ignoring reboot/poweroff with invalid timeout\n" ); } } } if ( len == -1 && errno != EAGAIN && errno != EWOULDBLOCK ) { perror( "Error reading vom RPC local pipe" ); } } static void handleClient( int fd, struct ucred *creds ) { //printf( "Credentials from SO_PEERCRED: pid=%ld, euid=%ld, egid=%ld\n", // (long) ucred.pid, (long) ucred.uid, (long) ucred.gid ); // Make socket blocking (should be default on linux after accept() but...) int flags = fcntl( fd, F_GETFL, 0 ); if ( flags != -1 ) { fcntl( fd, F_SETFL, flags & ~O_NONBLOCK ); } // But set timeouts struct timeval tv = { .tv_sec = 2, }; setsockopt( fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv) ); setsockopt( fd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof(tv) ); // Now read request char buffer[1000]; ssize_t len = read( fd, buffer, sizeof(buffer) - 1 ); if ( len <= 0 ) return; buffer[len] = '\0'; if ( strncmp( buffer, "get", 3 ) == 0 ) { // Get request FILE *s = fdopen( fd, "w" ); if ( s == NULL ) { perror( "Cannot wrap socket in stream" ); return; } // 1) Global state time_t deadline; const char *name = "none"; int userCount; main_getStatus( &name, &deadline, &userCount ); fprintf( s, "[General]\n" "nextAction=%s\n" "nextActionTime=%lld\n" "userCount=%d\n", name, (long long)deadline, userCount ); // 2) Requested sessions char *tok = strtok( buffer + 4, " \t\n\r" ); while ( tok != NULL ) { struct user *user = main_getUser( tok ); if ( user != NULL ) { const int idleTime = (int)( time( NULL ) - ( user->lastActivity + user->lastActivityOffset ) ); fprintf( s, "[%s]\n" "logoutTime=%lld\n" "locked=%d\n" "idleSeconds=%d\n", tok, (long long)user->logoutTime, (int)user->isLocked, idleTime ); } tok = strtok( NULL, " \t\n\r" ); } fflush( s ); // Don't fclose since we need to shutdown below. This is a leak, but since we're // running in a child that will exit soon, just close your eyes for a second... } else if ( strncmp( buffer, "reboot ", 7 ) == 0 || strncmp( buffer, "poweroff ", 9 ) == 0 || strncmp( buffer, "kexec ", 6 ) == 0 ) { if ( creds->uid != 0 ) { SENDSTRING( fd, "error only root can do this" ); } else { if ( write( rpcPipe.write, buffer, len ) == -1 ) { perror( "RPC Child: Cannot write to parent pipe" ); SENDSTRING( fd, "error cannot write to parent" ); } else { SENDSTRING( fd, "ok" ); } } } else if ( strncmp( buffer, "warn ", 5 ) == 0 ) { if ( creds->uid != 0 ) { SENDSTRING( fd, "error only root can do this" ); } else { main_warnAll( buffer + 5 ); SENDSTRING( fd, "ok" ); } } else { SENDSTRING( fd, "error unknown command" ); } shutdown( fd, SHUT_WR ); read( fd, buffer, sizeof(buffer) ); close( fd ); } bool rpc_send( const char *data ) { struct sockaddr_un addr = { .sun_family = AF_UNIX, .sun_path = SOCKPATH, }; int fd = socket( AF_UNIX, SOCK_STREAM, 0 ); if ( fd == -1 ) { perror( "RPC Client: socket() failed" ); return false; } if ( connect( fd, &addr, sizeof(addr) ) == -1 ) { perror( "RPC Client: connect() failed" ); close( fd ); return false; } if ( write( fd, data, strlen( data ) ) <= 0 ) { perror( "RPC Client: write() failed" ); close( fd ); return false; } shutdown( fd, SHUT_WR ); char buffer[200]; ssize_t len; if ( ( len = read( fd, buffer, sizeof(buffer) - 1 ) ) == -1 ) { perror( "RPC Client: read()ing reply failed" ); } else { buffer[len] = '\0'; printf( "%s\n", buffer ); } close( fd ); return true; }