this repo has no description
1/*
2This file is part of Darling.
3
4Copyright (C) 2017 Lubos Dolezel
5
6Darling is free software: you can redistribute it and/or modify
7it under the terms of the GNU General Public License as published by
8the Free Software Foundation, either version 3 of the License, or
9(at your option) any later version.
10
11Darling is distributed in the hope that it will be useful,
12but WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14GNU General Public License for more details.
15
16You should have received a copy of the GNU General Public License
17along with Darling. If not, see <http://www.gnu.org/licenses/>.
18*/
19
20#include <sys/socket.h>
21#include <sys/un.h>
22#include <sys/stat.h>
23#include <stdlib.h>
24#include <unistd.h>
25#include <fcntl.h>
26#include <stdbool.h>
27#include <string.h>
28#include <stdio.h>
29#include <errno.h>
30#include <sys/poll.h>
31#include <sys/types.h>
32#include <sys/wait.h>
33#include <sys/event.h>
34#include <sys/ioctl.h>
35#include <signal.h>
36#include "shellspawn.h"
37#include "duct_signals.h"
38
39#define DBG 0
40
41int g_serverSocket = -1;
42struct sigaction sigchld_oldaction;
43
44void setupSocket(void);
45void listenForConnections(void);
46void spawnShell(int fd);
47void setupSigchild(void);
48void restoreSigchild(void);
49void reapAll(void);
50
51int main(int argc, const char** argv)
52{
53 // shellspawn (daemon) --fork()--> shellspawn (child) --fork()--> exec /bin/bash
54 // in order to read the exit status of the shell process,
55 // we have to allow it to become a zombie, therefore we need to
56 // restore the sigaction of SIGCHLD of the child shellspawn
57 setupSigchild();
58 setupSocket();
59 listenForConnections();
60
61 if (g_serverSocket != -1)
62 close(g_serverSocket);
63 return 0;
64}
65
66void setupSocket(void)
67{
68 struct sockaddr_un addr = {
69 .sun_family = AF_UNIX,
70 .sun_path = SHELLSPAWN_SOCKPATH
71 };
72
73 g_serverSocket = socket(AF_UNIX, SOCK_STREAM, 0);
74 if (g_serverSocket == -1)
75 {
76 perror("Creating unix socket");
77 exit(EXIT_FAILURE);
78 }
79
80 fcntl(g_serverSocket, F_SETFD, FD_CLOEXEC);
81 unlink(SHELLSPAWN_SOCKPATH);
82
83 if (bind(g_serverSocket, (struct sockaddr*) &addr, sizeof(addr)) == -1)
84 {
85 perror("Binding the unix socket");
86 exit(EXIT_FAILURE);
87 }
88
89 chmod(addr.sun_path, 0600);
90
91 if (listen(g_serverSocket, 16384) == -1)
92 {
93 perror("Listening on unix socket");
94 exit(EXIT_FAILURE);
95 }
96}
97
98void listenForConnections(void)
99{
100 int sock;
101 struct sockaddr_un addr;
102 socklen_t len = sizeof(addr);
103
104 while (true)
105 {
106 sock = accept(g_serverSocket, (struct sockaddr*) &addr, &len);
107 if (sock == -1)
108 break;
109
110 if (fork() == 0)
111 {
112 restoreSigchild();
113 fcntl(sock, F_SETFD, FD_CLOEXEC);
114 spawnShell(sock);
115 exit(EXIT_SUCCESS);
116 }
117 else
118 {
119 close(sock);
120 }
121 }
122}
123
124void spawnShell(int fd)
125{
126 pid_t shell_pid = -1;
127 int shellfd[3] = { -1, -1, -1 };
128 int pipefd[2];
129 int rv;
130 struct pollfd pfd[2];
131 char** argv = NULL;
132 int argc = 2;
133 struct msghdr msg;
134 struct iovec iov;
135 char cmsgbuf[CMSG_SPACE(sizeof(int)) * 3];
136 int kq;
137
138 bool read_cmds = true;
139
140 argv = (char**) malloc(sizeof(char*) * 3);
141 argv[0] = "/bin/bash";
142 argv[1] = "--login";
143
144 char* alloc_exec = NULL;
145
146 // Read commands from client
147 while (read_cmds)
148 {
149 struct shellspawn_cmd cmd;
150 char* param = NULL;
151
152 memset(&msg, 0, sizeof(msg));
153 msg.msg_control = cmsgbuf;
154 msg.msg_controllen = sizeof(cmsgbuf);
155
156 iov.iov_base = &cmd;
157 iov.iov_len = sizeof(cmd);
158 msg.msg_iov = &iov;
159 msg.msg_iovlen = 1;
160
161 if (recvmsg(fd, &msg, 0) != sizeof(cmd))
162 {
163 if (DBG) puts("bad recvmsg");
164 goto err;
165 }
166
167 if (cmd.data_length != 0)
168 {
169 param = (char*) malloc(cmd.data_length + 1);
170 if (read(fd, param, cmd.data_length) != cmd.data_length)
171 goto err;
172 param[cmd.data_length] = '\0';
173 }
174
175 switch (cmd.cmd)
176 {
177 case SHELLSPAWN_ADDARG:
178 {
179 if (param != NULL)
180 {
181 argv = (char**) realloc(argv, sizeof(char*) * (argc + 1));
182 argv[argc] = param;
183 if (DBG) printf("add arg: %s\n", param);
184 argc++;
185 }
186 break;
187 }
188 case SHELLSPAWN_SETENV:
189 {
190 if (param != NULL)
191 {
192 if (DBG) printf("set env: %s\n", param);
193 putenv(param);
194 }
195 break;
196 }
197 case SHELLSPAWN_CHDIR:
198 {
199 if (param != NULL)
200 {
201 if (DBG) printf("chdir: %s\n", param);
202 chdir(param);
203 free(param);
204 }
205 break;
206 }
207 case SHELLSPAWN_GO:
208 {
209 struct cmsghdr *cmptr = CMSG_FIRSTHDR(&msg);
210
211 if (cmptr == NULL)
212 {
213 if (DBG) puts("bad cmptr");
214 goto err;
215 }
216 if (cmptr->cmsg_level != SOL_SOCKET
217 || cmptr->cmsg_type != SCM_RIGHTS)
218 {
219 if (DBG) puts("bad cmsg level/type");
220 goto err;
221 }
222 if (cmptr->cmsg_len != CMSG_LEN(sizeof(int) * 3))
223 {
224 if (DBG) printf("bad cmsg_len: %d\n", cmptr->cmsg_len);
225 goto err;
226 }
227
228 memcpy(shellfd, CMSG_DATA(cmptr), sizeof(int) * 3);
229
230 if (DBG) printf("go, fds={ %d, %d, %d }\n", shellfd[0], shellfd[1], shellfd[2]);
231 free(param);
232 read_cmds = false;
233 break;
234 }
235 case SHELLSPAWN_SETUIDGID:
236 {
237 int* ids = (int*) param;
238 if (cmd.data_length < 2*sizeof(int))
239 {
240 free(param);
241 break;
242 }
243
244 setuid(ids[0]);
245 setgid(ids[1]);
246 free(param);
247
248 break;
249 }
250 case SHELLSPAWN_SETEXEC:
251 {
252 argc = 0;
253 argv = realloc(argv, 0);
254 alloc_exec = param;
255 if (DBG) printf("setexec: %s\n", param);
256 break;
257 }
258 }
259 }
260
261 // Add terminating NULL
262 argv = (char**) realloc(argv, sizeof(char*) * (argc + 1));
263 argv[argc] = NULL;
264
265 if (pipe(pipefd) == -1)
266 goto err;
267
268 setsid();
269 setpgrp();
270
271 close(STDIN_FILENO);
272 close(STDOUT_FILENO);
273 close(STDERR_FILENO);
274
275 dup2(shellfd[0], STDIN_FILENO);
276 dup2(shellfd[1], STDOUT_FILENO);
277 dup2(shellfd[2], STDERR_FILENO);
278
279 ioctl(STDIN_FILENO, TIOCSCTTY, STDIN_FILENO);
280
281 shell_pid = fork();
282 if (shell_pid == 0)
283 {
284 close(fd);
285
286 fcntl(pipefd[1], F_SETFD, FD_CLOEXEC);
287
288 // In future, we may support spawning something else than Bash
289 // and check the provided shell against /etc/shells
290 execv(alloc_exec ? alloc_exec : "/bin/bash", argv);
291
292 rv = errno;
293 write(pipefd[1], &rv, sizeof(rv));
294 close(pipefd[1]);
295
296 exit(EXIT_FAILURE);
297 }
298
299 if (alloc_exec)
300 {
301 free(alloc_exec);
302 alloc_exec = NULL;
303 }
304
305 // Check that exec succeeded
306 close(pipefd[1]); // close the write end
307 if (read(pipefd[0], &rv, sizeof(rv)) == sizeof(rv))
308 {
309 errno = rv;
310 goto err;
311 }
312 close(pipefd[0]);
313
314 // Now we start passing signals
315 // and check for child process exit
316
317 kq = kqueue();
318
319 {
320 struct kevent changes[2];
321 EV_SET(&changes[0], fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, NULL);
322 EV_SET(&changes[1], shell_pid, EVFILT_PROC, EV_ADD | EV_ENABLE, NOTE_EXIT, 0, NULL);
323
324 if (kevent(kq, changes, 2, NULL, 0, NULL) == -1)
325 goto err;
326 }
327
328 while (true)
329 {
330 struct kevent ev;
331
332 if (kevent(kq, NULL, 0, &ev, 1, NULL) <= 0)
333 {
334 if (errno == EINTR) {
335 if (DBG) puts("kevent call interrupted; continuing...");
336 continue;
337 }
338 if (DBG) puts("kevent fail");
339 goto err;
340 }
341
342 if (ev.filter == EVFILT_PROC && (ev.fflags & NOTE_EXIT))
343 {
344 if (DBG) puts("subprocess exit");
345 break;
346 }
347 else if (ev.filter == EVFILT_READ)
348 {
349 struct shellspawn_cmd cmd;
350
351 if (read(fd, &cmd, sizeof(cmd)) != sizeof(cmd))
352 {
353 if (DBG) puts("Cannot read cmd");
354 break;
355 }
356
357 switch (cmd.cmd)
358 {
359 case SHELLSPAWN_SIGNAL:
360 {
361 int linux_signal, darwin_signal;
362
363 if (cmd.data_length != sizeof(int))
364 goto err;
365
366 if (read(fd, &linux_signal, sizeof(int)) != sizeof(int))
367 goto err;
368
369 // Convert Linux signal number to Darwin signal number
370 darwin_signal = signum_linux_to_bsd(linux_signal);
371 if (DBG) printf("rcvd signal %d -> %d\n", linux_signal, darwin_signal);
372
373 if (darwin_signal != 0)
374 {
375 int fg_pid = tcgetpgrp(shellfd[0]);
376 if (fg_pid != -1)
377 {
378 if (DBG) printf("fg_pid = %d\n", fg_pid);
379 kill(fg_pid, darwin_signal);
380 }
381 else
382 kill(-shell_pid, darwin_signal);
383 }
384
385 break;
386 }
387 default:
388 goto err;
389 }
390 }
391 }
392
393 // Kill the child process in case it's still running
394 kill(shell_pid, SIGKILL);
395
396 // Close shell fds
397 for (int i = 0; i < 3; i++)
398 {
399 if (shellfd[i] != -1)
400 close(shellfd[0]);
401 }
402
403 // Reap the child
404 int wstatus;
405 if (waitpid(shell_pid, &wstatus, 0) != shell_pid)
406 perror("waitpid");
407 wstatus = WEXITSTATUS(wstatus);
408
409 // Report exit code back to the client
410 write(fd, &wstatus, sizeof(int));
411
412 if (DBG) printf("Shell terminated with exit code %d\n", wstatus);
413 close(fd);
414
415 reapAll();
416 return;
417err:
418 if (DBG) fprintf(stderr, "Error spawning shell: %s\n", strerror(errno));
419
420 for (int i = 0; i < 3; i++)
421 {
422 if (shellfd[i] != -1)
423 close(shellfd[0]);
424 }
425
426 if (shell_pid != -1)
427 kill(shell_pid, SIGKILL);
428
429 close(fd);
430 reapAll();
431}
432
433void setupSigchild(void)
434{
435 struct sigaction sigchld_action = {
436 .sa_handler = SIG_DFL,
437 .sa_flags = SA_NOCLDWAIT
438 };
439 sigaction(SIGCHLD, &sigchld_action, &sigchld_oldaction);
440}
441
442void restoreSigchild(void)
443{
444 sigaction(SIGCHLD, &sigchld_oldaction, NULL);
445}
446
447void reapAll(void)
448{
449 while (waitpid((pid_t)(-1), 0, WNOHANG) > 0);
450}