#include <termios.h>
#include <unistd.h>
+/* Default PATH for new process.*/
+#ifndef PTYAS_DEFAULT_PATH
+/* Default user PATH from Debian's /etc/profile, change as needed. */
+# define PTYAS_DEFAULT_PATH "/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games"
+#endif
+
static void die(const char *s) {
perror(s);
exit(EXIT_FAILURE);
}
-static void die_msg(const char *fmt, ...) {
+static void die_fmt(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
/* The user must be able to write to the new TTY. Normally grantpt() would
* do this for us, but we don't trust the user and thus don't want to pass
* the pty_master to a process running under that uid. */
- // TODO: is this a problem?
if (chown(slave_path, uid, (gid_t)-1) != 0) {
die("chown slave tty");
}
die("setgroups");
}
if (getgroups(0, NULL) != 0) {
- die_msg("failed to drop all groups");
+ die_fmt("failed to drop all groups");
}
/* Dropping groups may require privileges, do that first. */
}
if ( uid != ruid || uid != euid || uid != suid
|| gid != rgid || gid != egid || gid != sgid) {
- die_msg("failed to drop privileges");
+ die_fmt("failed to drop privileges");
}
}
/* Just to be safe. */
if (setuid(0) != -1) {
- die_msg("failed to drop privileges (setuid)");
+ die_fmt("failed to drop privileges (setuid)");
}
}
while (*pid_to_wait_for != 0) {
/*
* If a signal happens here _and_ the child hasn't closed pty_slave,
- * we will hang in poll(); therefore ppoll() is requred.
+ * we would hang in poll(); therefore ppoll() is necessary.
*/
nfds_t nfds = sizeof(fds)/sizeof(*fds);
if (ppoll(fds, nfds, NULL /* no timeout */, &sigset_old) == -1) {
break;
}
- /* Handle errors first. */
+ /* Handle errors first. (Data available before the error occurred
+ * might be skipped, but shouldn't matter here.) */
if (fds[0].revents & (POLLERR | POLLNVAL)) {
fprintf(stderr, "poll: error on master: %d\n", fds[0].revents);
break;
break;
}
- /* Then read data if available. */
+ /* Read data if available. */
if (fds[0].revents & POLLIN) {
if (!read_from_write_to(pty_master, ctty)) {
perror("read from master write to ctty");
}
-/* Not sig_atomic_t but I don't know how to do that any other way. */
+/*
+ * Not sig_atomic_t (as required by POSIX) but I don't know how to do that any
+ * other way.
+ */
static volatile pid_t pid_to_wait_for;
static int pid_to_wait_for_status;
} else if (argc > 2) {
exec_argv = argv + 2;
} else {
- die_msg("%s <user> [<cmd>...]\n", argv[0]);
+ die_fmt("%s <user> [<cmd>...]\n", argv[0]);
}
const char *user = argv[1];
struct passwd *passwd = getpwnam(user);
if (!passwd) {
- die_msg("unknown user name '%s'\n", user);
+ die_fmt("unknown user name '%s'\n", user);
}
uid_t uid = passwd->pw_uid;
if (pid == -1) {
die("fork parent");
} else if (pid == 0) {
+ /* child, will become a session leader */
+
if (sigprocmask(SIG_SETMASK, &sigset_old, NULL) != 0) {
die("sigprocmask setmask child");
}
snprintf_or_assert(envp_term, sizeof(envp_term), "TERM=%s", term);
char *exec_envp[] = {
- "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
+ "PATH=" PTYAS_DEFAULT_PATH,
envp_user,
envp_home,
term_orig ? envp_term : NULL,
close_or_die(STDOUT_FILENO);
close_or_die(STDERR_FILENO);
- // TODO: EINTR?
+ /* TODO: EINTR? */
int status;
if (waitpid(pid, &status, 0) <= 0) {
die("waitpid child");
die("tcgetattr");
}
term = old_term;
- /* From man 3 cfmakeraw which is non-standard. */
+ /* From man 3 cfmakeraw; cfmakeraw is non-standard so set it manually. */
term.c_iflag &= ~(tcflag_t)(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
term.c_oflag &= ~(tcflag_t)(OPOST);
term.c_lflag &= ~(tcflag_t)(ECHO | ECHONL | ICANON | ISIG | IEXTEN);