From f7cd45464fd7c037f6a60098ae77760998b3b4b6 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Fri, 10 May 2019 15:25:43 +0200 Subject: Initial commit --- src/userlist.c | 232 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 src/userlist.c (limited to 'src/userlist.c') diff --git a/src/userlist.c b/src/userlist.c new file mode 100644 index 0000000..6e1f3d2 --- /dev/null +++ b/src/userlist.c @@ -0,0 +1,232 @@ +#include "userlist.h" +#include "util.h" + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TRUE 1 +#define FALSE 0 +#define MIN(a,b) ((a) < (b) ? (a) : (b)) + +static void getSessionData( struct user * user ); + +static void fixString( char *buffer, int maxLen ) +{ + while ( *buffer != '\0' && maxLen-- > 1 ) { + if ( *buffer == ' ' || *buffer == '\t' || *buffer == '\r' || *buffer == '\n' ) { + *buffer = '_'; + } + buffer++; + } + *buffer = '\0'; +} + +static time_t lastActivity( char *line ) { + struct stat s; + char dev[40]; + snprintf( dev, sizeof(dev), "/dev/%s", line ); + if ( stat( dev, &s ) != 0 || s.st_atime == 0 ) + return -1; + return s.st_atime; +} + +int getUserList( struct user *outbuf, int size ) +{ + int fh = open( "/run/utmp", O_RDONLY ); + if ( fh == -1 ) + return 0; + // Mark + int deadzone = 0; + for ( int i = 0; i < size; ++i ) { + if ( outbuf[i].user[0] != '\0' ) { + deadzone = i + 1; + } + outbuf[i].mark = false; + } + struct utmp buffer[100]; + ssize_t len = read( fh, buffer, sizeof(buffer) ); + int num = len / sizeof(struct utmp); + for ( int i = 0; i < num; ++i ) { + struct utmp *u = buffer + i; + if ( u->ut_type != USER_PROCESS ) + continue; // Not user session + if ( u->ut_user[0] == '\0' ) + continue; // Empty username!? + // Check if alive + if ( kill( u->ut_pid, 0 ) != 0 ) + continue; + // Make sure these contain anything + if ( u->ut_line[0] == '\0' ) { + snprintf( u->ut_line, UT_LINESIZE, "-" ); + } + if ( u->ut_host[0] == '\0' ) { + snprintf( u->ut_host, UT_HOSTSIZE, "-" ); + } + // No spaces, null terminated + fixString( u->ut_user, UT_NAMESIZE ); + fixString( u->ut_line, UT_LINESIZE ); + fixString( u->ut_host, UT_HOSTSIZE ); + // Find slot in outbuf (matching one, first free one if not found) + int use = -1; + for ( int j = 0; j < deadzone; ++j ) { + if ( outbuf[j].user[0] == '\0' ) { + if ( use == -1 ) { + use = j; + } + } else if ( outbuf[j].sessionLeader == u->ut_pid + && strcmp( outbuf[j].user, u->ut_user ) == 0 + && strcmp( outbuf[j].device, u->ut_line ) == 0 ) { + use = j; + break; + } + } + if ( use == -1 ) { // Not found and no free slot, expand if possible + if ( deadzone >= size ) // No more slots :-/ + continue; + use = deadzone++; + } + if ( outbuf[use].user[0] == '\0' ) { + // New entry, init + memset( &outbuf[use], 0, sizeof(outbuf[use]) ); + snprintf( outbuf[use].user, STRLEN, "%.*s", + (int)MIN( UT_NAMESIZE, STRLEN-1 ), u->ut_user ); + snprintf( outbuf[use].device, STRLEN, "%.*s", + (int)MIN( UT_LINESIZE, STRLEN-1 ), u->ut_line ); + if ( u->ut_host[0] == ':' && isdigit( u->ut_host[1] ) ) { + snprintf( outbuf[use].display, STRLEN, "%.*s", + (int)MIN( UT_HOSTSIZE, STRLEN-1 ), u->ut_host ); + } + outbuf[use].sessionLeader = u->ut_pid; + getSessionData( &outbuf[use] ); + printf( "New Session: '%s' on '%s', Display '%s', logind session '%s', login pid '%d', utmp pid '%d'.\n", outbuf[use].user, outbuf[use].device, outbuf[use].display, outbuf[use].sessionName, (int)outbuf[use].sessionHead, (int)outbuf[use].sessionLeader ); + } + outbuf[use].mark = true; + // Reset offset if timestamp changed + // but ONLY if this isn't a known X session + if ( outbuf[use].display[0] == '\0' ) { + const time_t la = lastActivity( outbuf[use].device ); + if ( outbuf[use].lastActivity != la ) { + outbuf[use].lastActivity = la; + outbuf[use].lastActivityOffset = 0; + } + } + } + close( fh ); + // Compact + for ( int i = 0; i < deadzone; ++i ) { + if ( outbuf[i].mark ) + continue; // In use + do { + deadzone--; + if ( outbuf[deadzone].mark ) { + outbuf[i] = outbuf[deadzone]; + outbuf[deadzone].user[0] = '\0'; + break; + } + } while ( deadzone > i ); + } + return deadzone; +} + +static void getSessionData( struct user * user ) +{ + char buffer[500]; + snprintf( buffer, sizeof(buffer), "/proc/%d/cgroup", (int)user->sessionLeader ); + FILE *fh = fopen( buffer, "r" ); + if ( fh == NULL ) { + fprintf( stderr, "getSessionData: Cannot open %s to get logind session name\n", buffer ); + return; + } + // Read from cgroup file + user->sessionName[0] = '\0'; + char sessionName[40] = ""; + while ( fgets( buffer, sizeof(buffer), fh ) != NULL ) { + const char *p = strstr( buffer, ":name=systemd:" ); + if ( p == NULL ) + continue; + p = strstr( p + 14, "/session-" ); + if ( p == NULL ) + continue; + copy( p + 9, sessionName, sizeof(sessionName), ".\n" ); + break; + } + fclose( fh ); + // Extract session leader pid from loginctl - it's often different from what utmp says + int fds[2]; + if ( pipe( fds ) == -1 ) { + perror( "getSessionData: pipe for loginctl failed" ); + return; + } + if ( doublefork() ) { + close( fds[0] ); + redirect( -1, fds[1] ); + run( false, "loginctl", "-p", "Name", "-p", "Class", "-p", "Leader", "show-session", sessionName, (char*)NULL ); + } + close( fds[1] ); + char lcName[100] = "", lcClass[100] = "", lcLeader[100] = ""; + if ( ! waitRead( fds[0], 2000 ) ) { + fprintf( stderr, "getSessionData: loginctl timed out.\n" ); + } else { + char output[4000] = "\n"; + char *pos = output + 1, *end = output + sizeof(output) - 1; + do { + ssize_t ret = read( fds[0], pos, end - pos ); + if ( ret == -1 ) { + if ( errno == EINTR ) + continue; + perror( "getSessionData: read() from loginctl failed" ); + break; + } + if ( ret == 0 ) + break; + pos += ret; + } while ( waitRead( fds[0], 50 ) && pos < end ); + *pos = '\0'; + // Find strings + pos = strstr( output, "\nName=" ); + if ( pos != NULL ) { + copy( pos + 6, lcName, sizeof(lcName), "\n \t" ); + } + pos = strstr( output, "\nClass=" ); + if ( pos != NULL ) { + copy( pos + 7, lcClass, sizeof(lcClass), "\n \t" ); + } + pos = strstr( output, "\nLeader=" ); + if ( pos != NULL ) { + copy( pos + 8, lcLeader, sizeof(lcLeader), "\n \t" ); + } + } + close( fds[0] ); + // Eval what we got - only honor output if it makes sense + if ( *lcName == '\0' ) { + fprintf( stderr, "getSessionData: Could not get user name for session %s\n", sessionName ); + return; + } + if ( strcmp( lcName, user->user ) != 0 ) { + fprintf( stderr, "getSessionData: Sanity check failed. Session %s belongs to %s, but expected %s.\n", sessionName, lcName, user->user ); + return; + } + if ( *lcClass != '\0' && strcmp( lcClass, "user" ) != 0 ) { + fprintf( stderr, "killSession: Sanity check failed. Session %s has Class=%s, but expected user.\n", sessionName, lcClass ); + return; + } + // All good + snprintf( user->sessionName, sizeof(user->sessionName), "%s", sessionName ); + user->sessionHead = atol( lcLeader ); + if ( user->sessionHead < 10 ) { + fprintf( stderr, "getSessionData: Nonsensical Leader=%s reported by loginctl for session %s.\n", lcLeader, sessionName ); + user->sessionHead = 0; + } +} + -- cgit v1.2.3-55-g7522