From 6053a358dd187fd56bdd1753fbc6b8285c35cd98 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Mon, 13 May 2019 14:43:35 +0200 Subject: Add reboot/kexec/poweroff via RPC --- src/rpc.c | 304 ++++++++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 206 insertions(+), 98 deletions(-) (limited to 'src/rpc.c') diff --git a/src/rpc.c b/src/rpc.c index e49547e..bfcc2c3 100644 --- a/src/rpc.c +++ b/src/rpc.c @@ -4,7 +4,7 @@ #include "userlist.h" #include -#include +#include #include #include #include @@ -14,119 +14,227 @@ #include #include +#define SENDSTRING(fd,x) write( fd, x, strlen(x) ) + #define SOCKPATH "/run/idle-daemon" +#pragma pack(1) +static struct { + int read; + int write; +} rpcPipe = { + .read = -1, + .write = -1, +}; +#pragma pack(0) + +_Static_assert( sizeof(rpcPipe) == ( sizeof(int) * 2 ), "Structsize mismatch" ); + static void handleClient( int fd, struct ucred *user ); int rpcOpen() { - 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; - } - return fd; + 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( (int*)&rpcPipe, 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; } void rpcWait( int listenFd, int seconds ) { - waitRead( listenFd, seconds * 1000 ); + struct pollfd pd[2] = { + { .fd = listenFd, .events = POLLIN | POLLHUP | POLLRDHUP }, + { .fd = rpcPipe.read, .events = POLLIN | POLLHUP | POLLRDHUP }, + }; + if ( poll( pd, 2, seconds * 1000 ) == -1 ) { + perror( "Error polling RPC sockets" ); + } } void rpcHandle( 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() ) - continue; // Parent continues - handleClient( fd, &ucred ); - exit( 0 ); - } - if ( errno != EAGAIN && errno != EWOULDBLOCK ) { - perror( "accept() on RPC socket failed" ); - } + 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() ) + 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"; + main_getStatus( &name, &deadline ); + fprintf( s, "[General]\n" + "nextAction=%s\n" + "nextActionTime=%lld\n", + name, (long long)deadline ); + // 2) Requested sessions + char *tok = strtok( buffer + 4, " \t\n\r" ); + while ( tok != NULL ) { + struct user *user = main_getUser( tok ); + if ( user != NULL ) { + fprintf( s, "[%s]\n" + "logoutTime=%lld\n" + "locked=%d\n", + tok, (long long)user->logoutTime, (int)user->isLocked ); + } + 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 { + SENDSTRING( fd, "error unknown command" ); + } + shutdown( fd, SHUT_WR ); + read( fd, buffer, sizeof(buffer) ); + close( fd ); } -static void handleClient( int fd, struct ucred *user ) +bool rpc_send( const char *data ) { - //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...) - (void)user; - 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 ) { - write( fd, "error", 5 ); - } else { - // 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"; - main_getStatus( &name, &deadline ); - fprintf( s, "[General]\n" - "nextAction=%s\n" - "nextActionTime=%lld\n", - name, (long long)deadline ); - // 2) Requested sessions - char *tok = strtok( buffer + 4, " \t\n\r" ); - while ( tok != NULL ) { - struct user *user = main_getUser( tok ); - if ( user != NULL ) { - fprintf( s, "[%s]\n" - "logoutTime=%lld\n" - "locked=%d\n", - tok, (long long)user->logoutTime, (int)user->isLocked ); - } - tok = strtok( NULL, " \t\n\r" ); - } - fflush( s ); - } - shutdown( fd, SHUT_WR ); - read( fd, buffer, sizeof(buffer) ); - close( fd ); + 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; } -- cgit v1.2.3-55-g7522