lcpex rewrite - alpha

This commit is contained in:
phanes
2023-02-15 18:44:21 -05:00
parent bb85754dc0
commit 7ed6e13fa5
82 changed files with 4510 additions and 2592 deletions

View File

@@ -0,0 +1,282 @@
#include "libclpex_tty.h"
/**
* @brief Safely prints a message and exits the program
*
* @param msg The message to print
* @param ttyOrig A pointer to a struct termios representing the original terminal settings
*
* This function takes a message `msg` and a pointer to a struct termios `ttyOrig` as input.
* The function first prints the `msg` to `stderr`.
* Then, it calls `ttyResetExit()` with `ttyOrig` as its argument to reset the terminal settings.
* Finally, the function exits the program with exit code 1.
*/
void safe_perror( const char * msg, struct termios * ttyOrig )
{
std::cerr << msg << std::endl;
ttyResetExit( ttyOrig );
exit(1);
}
/**
* @brief Executes the child process
*
* @param fd_child_stderr_pipe A file descriptor array for the child process's stderr pipe
* @param processed_command An array of char pointers representing the command and its arguments to be executed
* @param ttyOrig A pointer to a struct termios representing the original terminal settings
* @param context_override A flag indicating whether to override the process's execution context
* @param context_user The username to use for the execution context if `context_override` is `true`
* @param context_group The group name to use for the execution context if `context_override` is `true`
*
* This function takes an array of file descriptors `fd_child_stderr_pipe` for the child process's stderr pipe,
* an array of char pointers `processed_command` representing the command and its arguments to be executed,
* a pointer to a struct termios `ttyOrig` representing the original terminal settings,
* a flag `context_override` indicating whether to override the process's execution context,
* a string `context_user` representing the username to use for the execution context if `context_override` is `true`,
* and a string `context_group` representing the group name to use for the execution context if `context_override` is `true`.
*
* The function first redirects the child process's stderr to the write end of the stderr pipe.
* If `context_override` is `true`, the function sets the process's execution context using `set_identity_context()`.
* If `context_override` is `false`, the function does nothing.
* Finally, the function executes the command specified in `processed_command` using `execvp()`.
* If the execution of `execvp()` fails, the function calls `safe_perror()` to print a message and exit the program.
*/
void run_child_process( int fd_child_stderr_pipe[2], char * processed_command[], struct termios * ttyOrig, bool context_override, std::string context_user, std::string context_group )
{
// redirect stderr to the write end of the stderr pipe
// close the file descriptor STDERR_FILENO if it was previously open, then (re)open it as a copy of
while ((dup2(fd_child_stderr_pipe[WRITE_END], STDERR_FILENO) == -1) && (errno == EINTR)) {}
// close the write end of the stderr pipe
close( fd_child_stderr_pipe[WRITE_END] );
// if the user has specified a context override, set the context
if ( context_override )
{
int context_result = set_identity_context(context_user, context_group);
switch (context_result) {
case IDENTITY_CONTEXT_ERRORS::ERROR_NONE:
break;
case IDENTITY_CONTEXT_ERRORS::ERROR_NO_SUCH_USER:
std::cerr << "lcpex: Aborting: context user not found: " << context_user << std::endl;
exit(1);
break;
case IDENTITY_CONTEXT_ERRORS::ERROR_NO_SUCH_GROUP:
std::cerr << "lcpex: Aborting: context group not found: " << context_group << std::endl;
exit(1);
break;
case IDENTITY_CONTEXT_ERRORS::ERROR_SETGID_FAILED:
std::cerr << "lcpex: Aborting: Setting GID failed: " << context_user << "/" << context_group << std::endl;
exit(1);
break;
case IDENTITY_CONTEXT_ERRORS::ERROR_SETUID_FAILED:
std::cerr << "lcpex: Aborting: Setting UID failed: " << context_user << "/" << context_group << std::endl;
exit(1);
break;
default:
std::cerr << "lcpex: Aborting: Unknown error while setting identity context." << std::endl;
exit(1);
break;
}
}
// execute the dang command, print to stdout, stderr (of parent), and dump to file for each!!!!
// (and capture exit code in parent)
int exit_code = execvp(processed_command[0], processed_command);
safe_perror("failed on execvp in child", ttyOrig );
exit(exit_code);
}
// this does three things:
// - execute a dang string as a subprocess command
// - capture child stdout/stderr to respective log files
// - TEE child stdout/stderr to parent stdout/stderr
int exec_pty(
std::string command,
std::string stdout_log_file,
std::string stderr_log_file,
bool context_override,
std::string context_user,
std::string context_group,
bool environment_supplied
) {
// initialize the terminal settings obj
struct termios ttyOrig;
// if the user chose to supply the environment, then we need to clear the environment
// before we fork the process, so that the child process will inherit the environment
// from the parent process
if ( environment_supplied ) {
clearenv();
}
// turn our command string into something execvp can consume
char ** processed_command = expand_env(command );
// open file handles to the two log files we need to create for each execution
FILE * stdout_log_fh = fopen( stdout_log_file.c_str(), "a+" );
FILE * stderr_log_fh = fopen( stderr_log_file.c_str(), "a+" );
// create the pipes for the child process to write and read from using its stderr
int fd_child_stderr_pipe[2];
if ( pipe( fd_child_stderr_pipe ) == -1 ) {
safe_perror( "child stderr pipe", &ttyOrig );
exit( 1 );
}
// using O_CLOEXEC to ensure that the child process closes the file descriptors
if ( fcntl( fd_child_stderr_pipe[READ_END], F_SETFD, FD_CLOEXEC ) == -1 ) { perror("fcntl"); exit(1); }
// // same for stderr write
if ( fcntl( fd_child_stderr_pipe[WRITE_END], F_SETFD, FD_CLOEXEC ) == -1 ) { perror("fcntl"); exit(1); }
// status result basket for the parent process to capture the child's exit status
int status = 616;
// start ptyfork integration
char slaveName[MAX_SNAME];
int masterFd;
struct winsize ws;
/* Retrieve the attributes of terminal on which we are started */
if (tcgetattr(STDIN_FILENO, &ttyOrig) == -1)
safe_perror("tcgetattr", &ttyOrig);
if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) < 0)
safe_perror("ioctl-TIOCGWINSZ", &ttyOrig );
pid_t pid = ptyFork( &masterFd, slaveName, MAX_SNAME, &ttyOrig, &ws );
switch( pid ) {
case -1:
{
// fork failed
safe_perror("ptyfork failure", &ttyOrig );
exit(1);
}
case 0:
{
// child process
run_child_process( fd_child_stderr_pipe, processed_command, &ttyOrig, context_override, context_user, context_group );
}
default:
{
// parent process
// start ptyfork integration
ttySetRaw(STDIN_FILENO, &ttyOrig);
// The parent process has no need to access the entrance to the pipe
close(fd_child_stderr_pipe[WRITE_END]);
// attempt to write to stdout,stderr from child as well as to write each to file
char buf[BUFFER_SIZE];
// contains the byte count of the last read from the pipe
ssize_t byte_count;
// watched_fds for poll() to wait on
struct pollfd watched_fds[3];
// populate the watched_fds array
// child in to parent
watched_fds[0].fd = STDIN_FILENO;
watched_fds[0].events = POLLIN;
// child pty to parent (stdout)
watched_fds[1].fd = masterFd;
watched_fds[1].events = POLLIN;
// child stderr to parent
watched_fds[2].fd = fd_child_stderr_pipe[READ_END];
watched_fds[2].events = POLLIN;
// number of files poll() reports as ready
int num_files_readable;
// loop flag
bool break_out = false;
// loop until we've read all the data from the child process
while ( ! break_out ) {
num_files_readable = poll(watched_fds, sizeof(watched_fds) / sizeof(watched_fds[0]), -1);
if (num_files_readable == -1) {
// error occurred in poll()
safe_perror("poll", &ttyOrig );
exit(1);
}
if (num_files_readable == 0) {
// poll reports no files readable
break_out = true;
break;
}
for (int this_fd = 0; this_fd < 3; this_fd++) {
if (watched_fds[this_fd].revents & POLLIN) {
// this pipe is readable
byte_count = read(watched_fds[this_fd].fd, buf, BUFFER_SIZE);
if (byte_count == -1) {
// error reading from pipe
safe_perror("read", &ttyOrig );
exit(EXIT_FAILURE);
} else if (byte_count == 0) {
// reached EOF on one of the streams but not a HUP
// we've read all we can this cycle, so go to the next fd in the for loop
continue;
} else {
// byte count was sane
// write to stdout,stderr
if (this_fd == 0) {
// parent stdin received, write to child pty (stdin)
write(masterFd, buf, byte_count);
} else if (this_fd == 1 ) {
// child pty sent some stuff, write to parent stdout and log
write(stdout_log_fh->_fileno, buf, byte_count);
write(STDOUT_FILENO, buf, byte_count);
} else if ( this_fd == 2 ){
//the child's stderr pipe sent some stuff, write to parent stderr and log
write(stderr_log_fh->_fileno, buf, byte_count);
write(STDERR_FILENO, buf, byte_count);
} else {
// this should never happen
perror("Logic error!");
exit(EXIT_FAILURE);
}
}
}
if (watched_fds[this_fd].revents & POLLERR) {
close(watched_fds[this_fd].fd);
//break_out = true;
continue;
}
if (watched_fds[this_fd].revents & POLLHUP) {
// this pipe has hung up
close(watched_fds[this_fd].fd);
break_out = true;
break;
}
}
}
// wait for child to exit, capture status
waitpid(pid, &status, 0);
// close the log file handles
fclose(stdout_log_fh);
fclose(stderr_log_fh);
ttyResetExit( &ttyOrig);
if WIFEXITED(status) {
return WEXITSTATUS(status);
} else {
return -617;
}
}
}
}

View File

@@ -0,0 +1,50 @@
//
// Created by phanes on 2/6/23.
//
#ifndef LCPEX_LIBCLPEX_TTY_H
#define LCPEX_LIBCLPEX_TTY_H
#include <cstdio>
#include <cstdlib>
#include <poll.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>
#include <iostream>
#include "../string_expansion/string_expansion.h"
#include "../helpers.h"
#include "../Contexts.h"
#include "../vpty/pty_fork_mod/tty_functions.h"
#include "../vpty/pty_fork_mod/pty_fork.h"
#include <sys/ioctl.h>
#include <string>
/**
* @brief Execute a string as a subprocess command, capture its stdout/stderr to log files, and TEE its output to the parent process's stdout/stderr.
* This function performs the following three tasks:
* - Execute a string as a subprocess command.
* - Capture the child process's stdout and stderr to respective log files.
* - TEE the child process's stdout and stderr to the parent process's stdout and stderr.
*
* @param command The command to be executed as a subprocess.
* @param stdout_log_file The file path where the child process's stdout will be logged.
* @param stderr_log_file The file path where the child process's stderr will be logged.
* @param context_override Specify whether to override the process's execution context.
* @param context_user The user context to run the process as, if context_override is true.
* @param context_group The group context to run the process as, if context_override is true.
* @param environment_supplied Specify whether the environment is supplied.
* @return The exit status of the child process. If the child process terminated due to a signal, returns -617.
*/
int exec_pty(
std::string command,
std::string stdout_log_file,
std::string stderr_log_file,
bool context_override,
std::string context_user,
std::string context_group,
bool environment_supplied
);
#endif //LCPEX_LIBCLPEX_TTY_H

View File

@@ -0,0 +1,79 @@
#include "pty_fork.h"
pid_t ptyFork(int *masterFd, char *slaveName, size_t snLen,
const struct termios *slaveTermios, const struct winsize *slaveWS)
{
int mfd, slaveFd, savedErrno;
pid_t childPid;
char slname[MAX_SNAME];
mfd = ptyMasterOpen(slname, MAX_SNAME);
if (mfd == -1)
return -1;
if (slaveName != NULL) { /* Return slave name to caller */
if (strlen(slname) < snLen) {
strncpy(slaveName, slname, snLen);
} else { /* 'slaveName' was too small */
close(mfd);
errno = EOVERFLOW;
return -1;
}
}
childPid = fork();
/* fork() failed */
if (childPid == -1) {
/* close() might change 'errno' */
savedErrno = errno;
/* Don't leak file descriptors */
close(mfd);
errno = savedErrno;
return -1;
}
if (childPid != 0) { /* Parent */
*masterFd = mfd; /* Only parent gets master fd */
return childPid; /* Like parent of fork() */
}
/* Child falls through to here */
if (setsid() == -1) /* Start a new session */
perror("ptyFork:setsid");
close(mfd); /* Not needed in child */
slaveFd = open(slname, O_RDWR); /* Becomes controlling tty */
if (slaveFd == -1)
perror("ptyFork:open-slave");
#ifdef TIOCSCTTY /* Acquire controlling tty on BSD */
if (ioctl(slaveFd, TIOCSCTTY, 0) == -1)
perror("ptyFork:ioctl-TIOCSCTTY");
#endif
if (slaveTermios != NULL) /* Set slave tty attributes */
if (tcsetattr(slaveFd, TCSANOW, slaveTermios) == -1)
perror("ptyFork:tcsetattr");
if (slaveWS != NULL) /* Set slave tty window size */
if (ioctl(slaveFd, TIOCSWINSZ, slaveWS) == -1)
perror("ptyFork:ioctl-TIOCSWINSZ");
/* Duplicate pty slave to be child's stdin, stdout, and stderr */
if (dup2(slaveFd, STDIN_FILENO) != STDIN_FILENO)
perror("ptyFork:dup2-STDIN_FILENO");
if (dup2(slaveFd, STDOUT_FILENO) != STDOUT_FILENO)
perror("ptyFork:dup2-STDOUT_FILENO");
if (dup2(slaveFd, STDERR_FILENO) != STDERR_FILENO)
perror("ptyFork:dup2-STDERR_FILENO");
if (slaveFd > STDERR_FILENO) /* Safety check */
close(slaveFd); /* No longer need this fd */
return 0; /* Like child of fork() */
}

View File

@@ -0,0 +1,39 @@
#ifndef LCPEX_PTY_FORK_H
#define LCPEX_PTY_FORK_H
#include <sys/types.h>
#include "./pty_master_open.h"
#include <cstdio>
#include "./tty_functions.h"
#include <sys/ioctl.h>
/* Maximum size for pty slave name */
#define MAX_SNAME 1000
/**
* @brief Fork a child process with a pseudo-terminal
*
* This function forks a child process and returns the child's process ID. The
* child process is attached to a pseudo-terminal and its standard input,
* output, and error streams are redirected to the slave side of the pseudo-terminal.
*
* @param[out] masterFd Pointer to store the file descriptor of the master pty
* @param[out] slaveName Buffer to store the name of the slave pty
* @param[in] snLen Length of the 'slaveName' buffer
* @param[in] slaveTermios Terminal attributes for the slave pty (optional)
* @param[in] slaveWS Window size for the slave pty (optional)
*
* @return Process ID of the child process on success, or -1 on error
*
* On success, the file descriptor of the master pty is stored in the location
* pointed to by 'masterFd', and the name of the slave pty is stored in the buffer
* pointed to by 'slaveName'. If the buffer is too small to hold the name of the
* slave, an error of type 'EOVERFLOW' is returned. If 'slaveTermios' is non-NULL,
* the terminal attributes specified by it will be set for the slave pty. If
* 'slaveWS' is non-NULL, the window size specified by it will be set for the slave
* pty.
*/
pid_t ptyFork(int *masterFd, char *slaveName, size_t snLen, const struct termios *slaveTermios, const struct winsize *slaveWS);
#endif //LCPEX_PTY_FORK_H

View File

@@ -0,0 +1,76 @@
/* pty_master_open.cPP
Implement our ptyMasterOpen() function, based on UNIX 98 pseudoterminals.
See comments below.
See also pty_master_open_bsd.c.
*/
#if ! defined(__sun)
/* Prevents ptsname() declaration being visible on Solaris 8 */
#if ! defined(_XOPEN_SOURCE) || _XOPEN_SOURCE < 600
#define _XOPEN_SOURCE 600
#endif
#endif
#include "pty_master_open.h" /* Declares ptyMasterOpen() */
/* Some implementations don't have posix_openpt() */
#if defined(__sun) /* Not on Solaris 8 */
#define NO_POSIX_OPENPT
#endif
#ifdef NO_POSIX_OPENPT
static int
posix_openpt(int flags)
{
return open("/dev/ptmx", flags);
}
#endif
/* Open a pty master, returning file descriptor, or -1 on error.
On successful completion, the name of the corresponding pty
slave is returned in 'slaveName'. 'snLen' should be set to
indicate the size of the buffer pointed to by 'slaveName'. */
int ptyMasterOpen(char *slaveName, size_t snLen)
{
int masterFd, savedErrno;
char *p;
masterFd = posix_openpt(O_RDWR | O_NOCTTY); /* Open pty master */
if (masterFd == -1)
return -1;
if (grantpt(masterFd) == -1) { /* Grant access to slave pty */
savedErrno = errno;
close(masterFd); /* Might change 'errno' */
errno = savedErrno;
return -1;
}
if (unlockpt(masterFd) == -1) { /* Unlock slave pty */
savedErrno = errno;
close(masterFd); /* Might change 'errno' */
errno = savedErrno;
return -1;
}
p = ptsname(masterFd); /* Get slave pty name */
if (p == nullptr) {
savedErrno = errno;
close(masterFd); /* Might change 'errno' */
errno = savedErrno;
return -1;
}
if (strlen(p) < snLen) {
strncpy(slaveName, p, snLen);
} else { /* Return an error if buffer too small */
close(masterFd);
errno = EOVERFLOW;
return -1;
}
return masterFd;
}

View File

@@ -0,0 +1,32 @@
#ifndef LCPEX_PTY_MASTER_OPEN_H
#define LCPEX_PTY_MASTER_OPEN_H
#include <sys/types.h>
#include <errno.h>
#include <cstdlib>
#include <fcntl.h>
#include <csignal>
#include <cstring>
#include <stdlib.h>
#include <fcntl.h>
/**
* @brief Open a pseudo-terminal master
*
* This function opens a pseudo-terminal master and returns the file descriptor
* for the master. The name of the corresponding pseudo-terminal slave is also
* returned in the buffer pointed to by 'slaveName'.
*
* @param[out] slaveName Buffer to store the name of the slave pty
* @param[in] snLen Length of the 'slaveName' buffer
*
* @return File descriptor of the master pty on success, or -1 on error
*
* On success, the name of the corresponding pseudo-terminal slave is stored in
* the buffer pointed to by 'slaveName'. If the buffer is too small to hold the
* name of the slave, an error of type 'EOVERFLOW' is returned.
*/
int ptyMasterOpen(char *slaveName, size_t snLen);
#endif //LCPEX_PTY_MASTER_OPEN_H

View File

@@ -0,0 +1,77 @@
#include "tty_functions.h"
/* Reset terminal mode on program exit */
void ttyResetExit( struct termios * ttyOrig )
{
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, ttyOrig) == -1)
perror("tcsetattr");
}
/* Place terminal referred to by 'fd' in cbreak mode (noncanonical mode
with echoing turned off). This function assumes that the terminal is
currently in cooked mode (i.e., we shouldn't call it if the terminal
is currently in raw mode, since it does not undo all of the changes
made by the ttySetRaw() function below). Return 0 on success, or -1
on error. If 'prevTermios' is non-NULL, then use the buffer to which
it points to return the previous terminal settings. */
int ttySetCbreak(int fd, struct termios *prevTermios)
{
struct termios t;
if (tcgetattr(fd, &t) == -1)
return -1;
if (prevTermios != nullptr)
*prevTermios = t;
t.c_lflag &= ~(ICANON | ECHO);
t.c_lflag |= ISIG;
t.c_iflag &= ~ICRNL;
t.c_cc[VMIN] = 1; /* Character-at-a-time input */
t.c_cc[VTIME] = 0; /* with blocking */
if (tcsetattr(fd, TCSAFLUSH, &t) == -1)
return -1;
return 0;
}
/* Place terminal referred to by 'fd' in raw mode (noncanonical mode
with all input and output processing disabled). Return 0 on success,
or -1 on error. If 'prevTermios' is non-NULL, then use the buffer to
which it points to return the previous terminal settings. */
int ttySetRaw(int fd, struct termios *prevTermios)
{
struct termios t;
if (tcgetattr(fd, &t) == -1)
return -1;
if (prevTermios != nullptr)
*prevTermios = t;
t.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO);
/* Noncanonical mode, disable signals, extended
input processing, and echoing */
t.c_iflag &= ~(BRKINT | ICRNL | IGNBRK | IGNCR | INLCR |
INPCK | ISTRIP | IXON | PARMRK);
/* Disable special handling of CR, NL, and BREAK.
No 8th-bit stripping or parity error handling.
Disable START/STOP output flow control. */
// this breaks cursor position change tracking in parent processes
//t.c_oflag &= ~OPOST; /* Disable all output processing */
t.c_cc[VMIN] = 1; /* Character-at-a-time input */
t.c_cc[VTIME] = 0; /* with blocking */
if (tcsetattr(fd, TCSAFLUSH, &t) == -1)
return -1;
return 0;
}

View File

@@ -0,0 +1,59 @@
#ifndef LCPEX_TTY_FUNCTIONS_H
#define LCPEX_TTY_FUNCTIONS_H
#include <termios.h>
#include <cstdio>
#include <iostream>
#include <unistd.h>
/**
* @brief Place terminal referred to by 'fd' in cbreak mode
*
* This function places the terminal referred to by the file descriptor 'fd'
* in cbreak mode (noncanonical mode with echoing turned off). It assumes that
* the terminal is currently in cooked mode (i.e., it should not be called
* if the terminal is currently in raw mode, since it does not undo all of
* the changes made by the ttySetRaw() function).
*
* @param fd File descriptor of the terminal
* @param prevTermios Buffer to store the previous terminal settings
*
* @return 0 on success, or -1 on error
*
* If 'prevTermios' is non-NULL, then the buffer pointed to by it will be used
* to return the previous terminal settings.
*/
int ttySetCbreak(int fd, struct termios * prevTermios);
/**
* @brief Place terminal referred to by 'fd' in raw mode
*
* This function places the terminal referred to by the file descriptor 'fd'
* in raw mode.
*
* @param fd File descriptor of the terminal
* @param prevTermios Buffer to store the previous terminal settings
*
* @return 0 on success, or -1 on error
*
* If 'prevTermios' is non-NULL, then the buffer pointed to by it will be used
* to return the previous terminal settings.
*/
int ttySetRaw(int fd, struct termios * prevTermios);
/**
* @brief Reset terminal mode on program exit
*
* @param ttyOrig Original terminal mode to be restored
*
* This function resets the terminal mode when the program exits by using the
* tcsetattr function. If the tcsetattr function returns an error, the perror
* function is called to print an error message.
*/
void ttyResetExit( struct termios * ttyOrig );
#endif //LCPEX_TTY_FUNCTIONS_H