You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

354 lines
8.8 KiB

  1. #include "slave.h"
  2. #include <stdlib.h>
  3. #include <stdio.h>
  4. #include <string.h>
  5. #include <ctype.h>
  6. #include <unistd.h>
  7. #include <errno.h>
  8. #include <assert.h>
  9. #include <signal.h>
  10. #include <termios.h>
  11. #include <sys/stat.h>
  12. #include <sys/ioctl.h>
  13. #include <fcntl.h>
  14. #define LOG_MODULE "slave"
  15. #define LOG_ENABLE_DBG 0
  16. #include "log.h"
  17. #include "terminal.h"
  18. #include "tokenize.h"
  19. #include "xmalloc.h"
  20. static bool
  21. is_valid_shell(const char *shell)
  22. {
  23. FILE *f = fopen("/etc/shells", "r");
  24. if (f == NULL)
  25. goto err;
  26. char *_line = NULL;
  27. size_t count = 0;
  28. while (true) {
  29. errno = 0;
  30. ssize_t ret = getline(&_line, &count, f);
  31. if (ret < 0) {
  32. free(_line);
  33. break;
  34. }
  35. char *line = _line;
  36. {
  37. while (isspace(*line))
  38. line++;
  39. if (line[0] != '\0') {
  40. char *end = line + strlen(line) - 1;
  41. while (isspace(*end))
  42. end--;
  43. *(end + 1) = '\0';
  44. }
  45. }
  46. if (line[0] == '#')
  47. continue;
  48. if (strcmp(line, shell) == 0) {
  49. fclose(f);
  50. return true;
  51. }
  52. }
  53. err:
  54. if (f != NULL)
  55. fclose(f);
  56. return false;
  57. }
  58. enum user_notification_ret_t {UN_OK, UN_NO_MORE, UN_FAIL};
  59. static enum user_notification_ret_t
  60. emit_one_notification(int fd, const struct user_notification *notif)
  61. {
  62. const char *prefix = NULL;
  63. const char *postfix = "\033[m\n";
  64. switch (notif->kind) {
  65. case USER_NOTIFICATION_DEPRECATED:
  66. prefix = "\033[33;1mdeprecated\033[39;21m: ";
  67. break;
  68. case USER_NOTIFICATION_WARNING:
  69. prefix = "\033[33;1mwarning\033[39;21m: ";
  70. break;
  71. case USER_NOTIFICATION_ERROR:
  72. prefix = "\033[31;1merror\033[39;21m: ";
  73. break;
  74. }
  75. assert(prefix != NULL);
  76. if (write(fd, prefix, strlen(prefix)) < 0 ||
  77. write(fd, notif->text, strlen(notif->text)) < 0 ||
  78. write(fd, postfix, strlen(postfix)) < 0)
  79. {
  80. /*
  81. * The main process is blocking and waiting for us to close
  82. * the error pipe. Thus, pts data will *not* be processed
  83. * until we've exec:d. This means we cannot write anymore once
  84. * the kernel buffer is full. Don't treat this as a fatal
  85. * error.
  86. */
  87. if (errno == EWOULDBLOCK || errno == EAGAIN)
  88. return UN_NO_MORE;
  89. else {
  90. LOG_ERRNO("failed to write user-notification");
  91. return UN_FAIL;
  92. }
  93. }
  94. return UN_OK;
  95. }
  96. static bool
  97. emit_notifications_of_kind(int fd, const user_notifications_t *notifications,
  98. enum user_notification_kind kind)
  99. {
  100. tll_foreach(*notifications, it) {
  101. if (it->item.kind == kind) {
  102. switch (emit_one_notification(fd, &it->item)) {
  103. case UN_OK:
  104. break;
  105. case UN_NO_MORE:
  106. return true;
  107. case UN_FAIL:
  108. return false;
  109. }
  110. }
  111. }
  112. return true;
  113. }
  114. static bool
  115. emit_notifications(int fd, const user_notifications_t *notifications)
  116. {
  117. return
  118. emit_notifications_of_kind(fd, notifications, USER_NOTIFICATION_ERROR) &&
  119. emit_notifications_of_kind(fd, notifications, USER_NOTIFICATION_WARNING) &&
  120. emit_notifications_of_kind(fd, notifications, USER_NOTIFICATION_DEPRECATED);
  121. }
  122. static void
  123. slave_exec(int ptmx, char *argv[], int err_fd, bool login_shell,
  124. const user_notifications_t *notifications)
  125. {
  126. int pts = -1;
  127. const char *pts_name = ptsname(ptmx);
  128. if (grantpt(ptmx) == -1) {
  129. LOG_ERRNO("failed to grantpt()");
  130. goto err;
  131. }
  132. if (unlockpt(ptmx) == -1) {
  133. LOG_ERRNO("failed to unlockpt()");
  134. goto err;
  135. }
  136. close(ptmx);
  137. ptmx = -1;
  138. if (setsid() == -1) {
  139. LOG_ERRNO("failed to setsid()");
  140. goto err;
  141. }
  142. pts = open(pts_name, O_RDWR);
  143. if (pts == -1) {
  144. LOG_ERRNO("failed to open pseudo terminal slave device");
  145. goto err;
  146. }
  147. if (ioctl(pts, TIOCSCTTY, 0) < 0) {
  148. LOG_ERRNO("failed to configure controlling terminal");
  149. goto err;
  150. }
  151. {
  152. struct termios flags;
  153. if (tcgetattr(pts, &flags) < 0) {
  154. LOG_ERRNO("failed to get terminal attributes");
  155. goto err;
  156. }
  157. flags.c_iflag |= IUTF8;
  158. if (tcsetattr(pts, TCSANOW, &flags) < 0) {
  159. LOG_ERRNO("failed to set IUTF8 terminal attribute");
  160. goto err;
  161. }
  162. }
  163. if (tll_length(*notifications) > 0) {
  164. int flags = fcntl(pts, F_GETFL);
  165. if (flags < 0)
  166. goto err;
  167. if (fcntl(pts, F_SETFL, flags | O_NONBLOCK) < 0)
  168. goto err;
  169. if (!emit_notifications(pts, notifications))
  170. goto err;
  171. fcntl(pts, F_SETFL, flags);
  172. }
  173. if (dup2(pts, STDIN_FILENO) == -1 ||
  174. dup2(pts, STDOUT_FILENO) == -1 ||
  175. dup2(pts, STDERR_FILENO) == -1)
  176. {
  177. LOG_ERRNO("failed to dup stdin/stdout/stderr");
  178. goto err;
  179. }
  180. close(pts);
  181. pts = -1;
  182. const char *file;
  183. if (login_shell) {
  184. file = xstrdup(argv[0]);
  185. char *arg0 = xmalloc(strlen(argv[0]) + 1 + 1);
  186. arg0[0] = '-';
  187. arg0[1] = '\0';
  188. strcat(arg0, argv[0]);
  189. argv[0] = arg0;
  190. } else
  191. file = argv[0];
  192. execvp(file, argv);
  193. err:
  194. (void)!write(err_fd, &errno, sizeof(errno));
  195. if (pts != -1)
  196. close(pts);
  197. if (ptmx != -1)
  198. close(ptmx);
  199. close(err_fd);
  200. _exit(errno);
  201. }
  202. pid_t
  203. slave_spawn(int ptmx, int argc, const char *cwd, char *const *argv,
  204. const char *term_env, const char *conf_shell, bool login_shell,
  205. const user_notifications_t *notifications)
  206. {
  207. int fork_pipe[2];
  208. if (pipe2(fork_pipe, O_CLOEXEC) < 0) {
  209. LOG_ERRNO("failed to create pipe");
  210. return -1;
  211. }
  212. pid_t pid = fork();
  213. switch (pid) {
  214. case -1:
  215. LOG_ERRNO("failed to fork");
  216. close(fork_pipe[0]);
  217. close(fork_pipe[1]);
  218. return -1;
  219. case 0:
  220. /* Child */
  221. close(fork_pipe[0]); /* Close read end */
  222. if (chdir(cwd) < 0) {
  223. const int _errno = errno;
  224. LOG_ERRNO("failed to change working directory");
  225. (void)!write(fork_pipe[1], &_errno, sizeof(_errno));
  226. _exit(_errno);
  227. }
  228. /* Restore signals */
  229. sigset_t mask;
  230. sigemptyset(&mask);
  231. const struct sigaction sa = {.sa_handler = SIG_DFL};
  232. if (sigaction(SIGINT, &sa, NULL) < 0 ||
  233. sigaction(SIGTERM, &sa, NULL) < 0 ||
  234. sigaction(SIGHUP, &sa, NULL) < 0 ||
  235. sigprocmask(SIG_SETMASK, &mask, NULL) < 0)
  236. {
  237. const int _errno = errno;
  238. LOG_ERRNO_P(errno, "failed to restore signals");
  239. (void)!write(fork_pipe[1], &_errno, sizeof(_errno));
  240. _exit(_errno);
  241. }
  242. setenv("TERM", term_env, 1);
  243. setenv("COLORTERM", "truecolor", 1);
  244. char **_shell_argv = NULL;
  245. char **shell_argv = NULL;
  246. if (argc == 0) {
  247. char *shell_copy = xstrdup(conf_shell);
  248. if (!tokenize_cmdline(shell_copy, &_shell_argv)) {
  249. free(shell_copy);
  250. (void)!write(fork_pipe[1], &errno, sizeof(errno));
  251. _exit(0);
  252. }
  253. shell_argv = _shell_argv;
  254. } else {
  255. size_t count = 0;
  256. for (; argv[count] != NULL; count++)
  257. ;
  258. shell_argv = xmalloc((count + 1) * sizeof(shell_argv[0]));
  259. for (size_t i = 0; i < count; i++)
  260. shell_argv[i] = argv[i];
  261. shell_argv[count] = NULL;
  262. }
  263. if (is_valid_shell(shell_argv[0]))
  264. setenv("SHELL", shell_argv[0], 1);
  265. slave_exec(ptmx, shell_argv, fork_pipe[1], login_shell, notifications);
  266. assert(false);
  267. break;
  268. default: {
  269. close(fork_pipe[1]); /* Close write end */
  270. LOG_DBG("slave has PID %d", pid);
  271. int _errno;
  272. static_assert(sizeof(errno) == sizeof(_errno), "errno size mismatch");
  273. ssize_t ret = read(fork_pipe[0], &_errno, sizeof(_errno));
  274. close(fork_pipe[0]);
  275. if (ret < 0) {
  276. LOG_ERRNO("failed to read from pipe");
  277. return -1;
  278. } else if (ret == sizeof(_errno)) {
  279. LOG_ERRNO_P(
  280. _errno, "%s: failed to execute",
  281. argc == 0 ? conf_shell : argv[0]);
  282. return -1;
  283. } else
  284. LOG_DBG("%s: successfully started", conf_shell);
  285. int fd_flags;
  286. if ((fd_flags = fcntl(ptmx, F_GETFD)) < 0 ||
  287. fcntl(ptmx, F_SETFD, fd_flags | FD_CLOEXEC) < 0)
  288. {
  289. LOG_ERRNO("failed to set FD_CLOEXEC on ptmx");
  290. return -1;
  291. }
  292. break;
  293. }
  294. }
  295. return pid;
  296. }