/ src / shellspawn / shellspawn.c
shellspawn.c
  1  /*
  2  This file is part of Darling.
  3  
  4  Copyright (C) 2017 Lubos Dolezel
  5  
  6  Darling is free software: you can redistribute it and/or modify
  7  it under the terms of the GNU General Public License as published by
  8  the Free Software Foundation, either version 3 of the License, or
  9  (at your option) any later version.
 10  
 11  Darling is distributed in the hope that it will be useful,
 12  but WITHOUT ANY WARRANTY; without even the implied warranty of
 13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 14  GNU General Public License for more details.
 15  
 16  You should have received a copy of the GNU General Public License
 17  along 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  
 41  int g_serverSocket = -1;
 42  struct sigaction sigchld_oldaction;
 43  
 44  void setupSocket(void);
 45  void listenForConnections(void);
 46  void spawnShell(int fd);
 47  void setupSigchild(void);
 48  void restoreSigchild(void);
 49  void reapAll(void);
 50  
 51  int 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  
 66  void 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  
 98  void 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  
124  void 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;
417  err:
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  
433  void 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  
442  void restoreSigchild(void)
443  {
444  	sigaction(SIGCHLD, &sigchld_oldaction, NULL);
445  }
446  
447  void reapAll(void)
448  {
449      while (waitpid((pid_t)(-1), 0, WNOHANG) > 0);
450  }