2019-06-13 15:19:10 +02:00
|
|
|
#include "slave.h"
|
2019-11-01 21:01:15 +01:00
|
|
|
|
2019-06-13 15:19:10 +02:00
|
|
|
#include <stdlib.h>
|
2020-03-02 18:46:35 +01:00
|
|
|
#include <stdio.h>
|
2019-10-30 18:05:03 +01:00
|
|
|
#include <string.h>
|
2020-03-02 18:46:35 +01:00
|
|
|
#include <ctype.h>
|
2019-06-13 15:19:10 +02:00
|
|
|
#include <unistd.h>
|
|
|
|
|
#include <errno.h>
|
2019-10-30 18:05:03 +01:00
|
|
|
#include <assert.h>
|
2019-11-03 13:24:15 +01:00
|
|
|
#include <signal.h>
|
2020-05-26 18:18:11 +02:00
|
|
|
#include <termios.h>
|
2019-06-13 15:19:10 +02:00
|
|
|
|
|
|
|
|
#include <sys/stat.h>
|
2020-05-13 13:04:52 +02:00
|
|
|
#include <sys/ioctl.h>
|
2019-06-13 15:19:10 +02:00
|
|
|
#include <fcntl.h>
|
|
|
|
|
|
|
|
|
|
#define LOG_MODULE "slave"
|
2019-07-03 20:21:03 +02:00
|
|
|
#define LOG_ENABLE_DBG 0
|
2019-06-13 15:19:10 +02:00
|
|
|
#include "log.h"
|
2019-07-17 09:30:39 +02:00
|
|
|
|
2020-07-29 19:42:12 +02:00
|
|
|
#include "terminal.h"
|
2019-10-30 18:05:03 +01:00
|
|
|
#include "tokenize.h"
|
|
|
|
|
|
2020-03-02 18:46:35 +01:00
|
|
|
static bool
|
|
|
|
|
is_valid_shell(const char *shell)
|
|
|
|
|
{
|
|
|
|
|
FILE *f = fopen("/etc/shells", "r");
|
|
|
|
|
if (f == NULL)
|
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
|
|
char *_line = NULL;
|
|
|
|
|
size_t count = 0;
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
errno = 0;
|
|
|
|
|
ssize_t ret = getline(&_line, &count, f);
|
|
|
|
|
|
|
|
|
|
if (ret < 0) {
|
|
|
|
|
free(_line);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
char *line = _line;
|
|
|
|
|
{
|
|
|
|
|
while (isspace(*line))
|
|
|
|
|
line++;
|
|
|
|
|
if (line[0] != '\0') {
|
|
|
|
|
char *end = line + strlen(line) - 1;
|
|
|
|
|
while (isspace(*end))
|
|
|
|
|
end--;
|
|
|
|
|
*(end + 1) = '\0';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (line[0] == '#')
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (strcmp(line, shell) == 0) {
|
|
|
|
|
fclose(f);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err:
|
|
|
|
|
if (f != NULL)
|
|
|
|
|
fclose(f);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-30 18:57:21 +02:00
|
|
|
static bool
|
|
|
|
|
emit_one_notification(int fd, const struct user_notification *notif)
|
|
|
|
|
{
|
|
|
|
|
const char *prefix = NULL;
|
|
|
|
|
const char *postfix = "\e[m\n";
|
|
|
|
|
|
|
|
|
|
switch (notif->kind) {
|
|
|
|
|
case USER_NOTIFICATION_DEPRECATED:
|
|
|
|
|
prefix = "\e[33;1mdeprecated\e[39;21m: ";
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case USER_NOTIFICATION_WARNING:
|
|
|
|
|
prefix = "\e[33;1mwarning\e[39;21m: ";
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case USER_NOTIFICATION_ERROR:
|
|
|
|
|
prefix = "\e[31;1merror\e[39;21m: ";
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
assert(prefix != NULL);
|
|
|
|
|
|
|
|
|
|
if (write(fd, prefix, strlen(prefix)) < 0 ||
|
|
|
|
|
write(fd, notif->text, strlen(notif->text)) < 0 ||
|
|
|
|
|
write(fd, postfix, strlen(postfix)) < 0)
|
|
|
|
|
{
|
2020-07-31 17:15:51 +02:00
|
|
|
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
|
|
|
|
/*
|
|
|
|
|
* The main process is blocking and waiting for us to
|
|
|
|
|
* close the error pipe. Thus, pts data will *not* be
|
|
|
|
|
* processed until we've exec:d. This means we cannot
|
|
|
|
|
* write anymore once the kernel buffer is full. Don't
|
|
|
|
|
* treat this as a fatal error.
|
|
|
|
|
*/
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2020-07-30 18:57:21 +02:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
|
emit_notifications(int fd, const user_notifications_t *notifications)
|
|
|
|
|
{
|
2020-07-31 17:10:39 +02:00
|
|
|
/* Errors first */
|
2020-07-30 18:57:21 +02:00
|
|
|
tll_foreach(*notifications, it) {
|
2020-07-31 17:10:39 +02:00
|
|
|
if (it->item.kind == USER_NOTIFICATION_ERROR) {
|
|
|
|
|
if (!emit_one_notification(fd, &it->item))
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Then warnings */
|
|
|
|
|
tll_foreach(*notifications, it) {
|
|
|
|
|
if (it->item.kind == USER_NOTIFICATION_WARNING) {
|
|
|
|
|
if (!emit_one_notification(fd, &it->item))
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Finally deprecation messages */
|
|
|
|
|
tll_foreach(*notifications, it) {
|
|
|
|
|
if (it->item.kind == USER_NOTIFICATION_DEPRECATED) {
|
|
|
|
|
if (!emit_one_notification(fd, &it->item))
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2020-07-30 18:57:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-30 18:05:03 +01:00
|
|
|
static void
|
2020-07-29 19:42:12 +02:00
|
|
|
slave_exec(int ptmx, char *argv[], int err_fd, bool login_shell,
|
2020-07-30 18:57:21 +02:00
|
|
|
const user_notifications_t *notifications)
|
2019-06-13 15:19:10 +02:00
|
|
|
{
|
|
|
|
|
int pts = -1;
|
|
|
|
|
const char *pts_name = ptsname(ptmx);
|
|
|
|
|
|
|
|
|
|
if (grantpt(ptmx) == -1) {
|
|
|
|
|
LOG_ERRNO("failed to grantpt()");
|
|
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
if (unlockpt(ptmx) == -1) {
|
|
|
|
|
LOG_ERRNO("failed to unlockpt()");
|
|
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
close(ptmx);
|
|
|
|
|
ptmx = -1;
|
|
|
|
|
|
|
|
|
|
if (setsid() == -1) {
|
|
|
|
|
LOG_ERRNO("failed to setsid()");
|
|
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pts = open(pts_name, O_RDWR);
|
|
|
|
|
if (pts == -1) {
|
|
|
|
|
LOG_ERRNO("failed to open pseudo terminal slave device");
|
|
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-13 13:04:52 +02:00
|
|
|
if (ioctl(pts, TIOCSCTTY, 0) < 0) {
|
|
|
|
|
LOG_ERRNO("failed to configure controlling terminal");
|
|
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-26 18:18:11 +02:00
|
|
|
{
|
|
|
|
|
struct termios flags;
|
|
|
|
|
if (tcgetattr(pts, &flags) < 0) {
|
|
|
|
|
LOG_ERRNO("failed to get terminal attributes");
|
|
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
flags.c_iflag |= IUTF8;
|
|
|
|
|
if (tcsetattr(pts, TCSANOW, &flags) < 0) {
|
|
|
|
|
LOG_ERRNO("failed to set IUTF8 terminal attribute");
|
|
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-05-13 13:04:52 +02:00
|
|
|
|
2019-06-13 15:19:10 +02:00
|
|
|
if (dup2(pts, STDIN_FILENO) == -1 ||
|
|
|
|
|
dup2(pts, STDOUT_FILENO) == -1 ||
|
|
|
|
|
dup2(pts, STDERR_FILENO) == -1)
|
|
|
|
|
{
|
|
|
|
|
LOG_ERRNO("failed to dup stdin/stdout/stderr");
|
|
|
|
|
goto err;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-31 17:15:51 +02:00
|
|
|
{
|
|
|
|
|
int flags = fcntl(pts, F_GETFL);
|
|
|
|
|
if (flags < 0)
|
|
|
|
|
goto err;
|
|
|
|
|
if (fcntl(pts, F_SETFL, flags | O_NONBLOCK) < 0)
|
|
|
|
|
goto err;
|
|
|
|
|
|
2020-07-31 18:13:00 +02:00
|
|
|
if (!emit_notifications(pts, notifications))
|
|
|
|
|
goto err;
|
|
|
|
|
|
|
|
|
|
fcntl(pts, F_SETFL, flags);
|
|
|
|
|
}
|
2020-07-29 19:42:12 +02:00
|
|
|
|
2019-06-13 15:19:10 +02:00
|
|
|
close(pts);
|
|
|
|
|
pts = -1;
|
|
|
|
|
|
2020-02-20 18:36:09 +01:00
|
|
|
const char *file;
|
|
|
|
|
if (login_shell) {
|
|
|
|
|
file = strdup(argv[0]);
|
|
|
|
|
|
|
|
|
|
char *arg0 = malloc(strlen(argv[0]) + 1 + 1);
|
|
|
|
|
arg0[0] = '-';
|
|
|
|
|
arg0[1] = '\0';
|
|
|
|
|
strcat(arg0, argv[0]);
|
|
|
|
|
|
|
|
|
|
argv[0] = arg0;
|
|
|
|
|
} else
|
|
|
|
|
file = argv[0];
|
|
|
|
|
|
|
|
|
|
execvp(file, argv);
|
2019-06-15 22:22:44 +02:00
|
|
|
|
2019-06-13 15:19:10 +02:00
|
|
|
err:
|
2019-07-17 09:39:12 +02:00
|
|
|
(void)!write(err_fd, &errno, sizeof(errno));
|
2019-06-13 15:19:10 +02:00
|
|
|
if (pts != -1)
|
|
|
|
|
close(pts);
|
|
|
|
|
if (ptmx != -1)
|
|
|
|
|
close(ptmx);
|
2019-12-21 15:27:17 +01:00
|
|
|
close(err_fd);
|
2019-06-13 15:19:10 +02:00
|
|
|
_exit(errno);
|
|
|
|
|
}
|
2019-10-30 18:05:03 +01:00
|
|
|
|
|
|
|
|
pid_t
|
2019-12-21 19:57:28 +01:00
|
|
|
slave_spawn(int ptmx, int argc, const char *cwd, char *const *argv,
|
2020-07-29 19:42:12 +02:00
|
|
|
const char *term_env, const char *conf_shell, bool login_shell,
|
2020-07-30 18:57:21 +02:00
|
|
|
const user_notifications_t *notifications)
|
2019-10-30 18:05:03 +01:00
|
|
|
{
|
|
|
|
|
int fork_pipe[2];
|
|
|
|
|
if (pipe2(fork_pipe, O_CLOEXEC) < 0) {
|
|
|
|
|
LOG_ERRNO("failed to create pipe");
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pid_t pid = fork();
|
|
|
|
|
switch (pid) {
|
|
|
|
|
case -1:
|
|
|
|
|
LOG_ERRNO("failed to fork");
|
|
|
|
|
close(fork_pipe[0]);
|
|
|
|
|
close(fork_pipe[1]);
|
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
|
|
case 0:
|
|
|
|
|
/* Child */
|
|
|
|
|
close(fork_pipe[0]); /* Close read end */
|
|
|
|
|
|
2020-02-20 18:46:35 +01:00
|
|
|
if (chdir(cwd) < 0) {
|
|
|
|
|
const int _errno = errno;
|
|
|
|
|
LOG_ERRNO("failed to change working directory");
|
|
|
|
|
(void)!write(fork_pipe[1], &_errno, sizeof(_errno));
|
|
|
|
|
_exit(_errno);
|
|
|
|
|
}
|
2019-12-21 19:57:28 +01:00
|
|
|
|
2019-11-03 13:24:15 +01:00
|
|
|
/* Restore signals */
|
2020-05-21 20:22:24 +02:00
|
|
|
sigset_t mask;
|
|
|
|
|
sigemptyset(&mask);
|
2019-11-03 13:24:15 +01:00
|
|
|
const struct sigaction sa = {.sa_handler = SIG_DFL};
|
|
|
|
|
if (sigaction(SIGINT, &sa, NULL) < 0 ||
|
|
|
|
|
sigaction(SIGTERM, &sa, NULL) < 0 ||
|
2020-05-21 20:22:24 +02:00
|
|
|
sigaction(SIGHUP, &sa, NULL) < 0 ||
|
|
|
|
|
sigprocmask(SIG_SETMASK, &mask, NULL) < 0)
|
2019-11-03 13:24:15 +01:00
|
|
|
{
|
|
|
|
|
const int _errno = errno;
|
|
|
|
|
LOG_ERRNO_P("failed to restore signals", errno);
|
|
|
|
|
(void)!write(fork_pipe[1], &_errno, sizeof(_errno));
|
|
|
|
|
_exit(_errno);
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-01 21:01:15 +01:00
|
|
|
setenv("TERM", term_env, 1);
|
|
|
|
|
|
2019-10-30 18:05:03 +01:00
|
|
|
char **_shell_argv = NULL;
|
2020-02-20 18:36:09 +01:00
|
|
|
char **shell_argv = NULL;
|
2019-10-30 18:05:03 +01:00
|
|
|
|
|
|
|
|
if (argc == 0) {
|
|
|
|
|
char *shell_copy = strdup(conf_shell);
|
|
|
|
|
if (!tokenize_cmdline(shell_copy, &_shell_argv)) {
|
|
|
|
|
free(shell_copy);
|
|
|
|
|
(void)!write(fork_pipe[1], &errno, sizeof(errno));
|
|
|
|
|
_exit(0);
|
|
|
|
|
}
|
|
|
|
|
shell_argv = _shell_argv;
|
2020-02-20 18:36:09 +01:00
|
|
|
} else {
|
|
|
|
|
size_t count = 0;
|
|
|
|
|
for (; argv[count] != NULL; count++)
|
|
|
|
|
;
|
|
|
|
|
shell_argv = malloc((count + 1) * sizeof(shell_argv[0]));
|
|
|
|
|
for (size_t i = 0; i < count; i++)
|
|
|
|
|
shell_argv[i] = argv[i];
|
|
|
|
|
shell_argv[count] = NULL;
|
2019-10-30 18:05:03 +01:00
|
|
|
}
|
|
|
|
|
|
2020-03-02 18:46:35 +01:00
|
|
|
if (is_valid_shell(shell_argv[0]))
|
|
|
|
|
setenv("SHELL", shell_argv[0], 1);
|
|
|
|
|
|
2020-07-30 18:57:21 +02:00
|
|
|
slave_exec(ptmx, shell_argv, fork_pipe[1], login_shell, notifications);
|
2019-10-30 18:05:03 +01:00
|
|
|
assert(false);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default: {
|
|
|
|
|
close(fork_pipe[1]); /* Close write end */
|
2019-10-30 20:20:56 +01:00
|
|
|
LOG_DBG("slave has PID %d", pid);
|
2019-10-30 18:05:03 +01:00
|
|
|
|
|
|
|
|
int _errno;
|
|
|
|
|
static_assert(sizeof(errno) == sizeof(_errno), "errno size mismatch");
|
|
|
|
|
|
|
|
|
|
ssize_t ret = read(fork_pipe[0], &_errno, sizeof(_errno));
|
|
|
|
|
close(fork_pipe[0]);
|
|
|
|
|
|
|
|
|
|
if (ret < 0) {
|
|
|
|
|
LOG_ERRNO("failed to read from pipe");
|
|
|
|
|
return -1;
|
|
|
|
|
} else if (ret == sizeof(_errno)) {
|
2019-10-30 20:21:19 +01:00
|
|
|
LOG_ERRNO_P(
|
|
|
|
|
"%s: failed to execute", _errno, argc == 0 ? conf_shell : argv[0]);
|
2019-10-30 18:05:03 +01:00
|
|
|
return -1;
|
|
|
|
|
} else
|
2019-10-30 20:20:56 +01:00
|
|
|
LOG_DBG("%s: successfully started", conf_shell);
|
2019-11-03 01:14:02 +01:00
|
|
|
|
|
|
|
|
int fd_flags;
|
|
|
|
|
if ((fd_flags = fcntl(ptmx, F_GETFD)) < 0 ||
|
|
|
|
|
fcntl(ptmx, F_SETFD, fd_flags | FD_CLOEXEC) < 0)
|
|
|
|
|
{
|
|
|
|
|
LOG_ERRNO("failed to set FD_CLOEXEC on ptmx");
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
2019-10-30 18:05:03 +01:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return pid;
|
|
|
|
|
}
|