this repo has no description
at master 450 lines 9.0 kB view raw
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}