/* Start a subprocess with run_command_redirected and register it with the termination handler. Takes care of o.shellexec. Returns the PID of the subprocess or -1 on error. */ static int start_subprocess(char *cmdexec, struct subprocess_info *info) { char *cmdbuf; int pid; if (o.shellexec) { /* Run with cmd.exe. */ const char *shell; size_t cmdlen; shell = get_shell(); cmdlen = strlen(shell) + strlen(cmdexec) + 32; cmdbuf = (char *) safe_malloc(cmdlen); Snprintf(cmdbuf, cmdlen, "%s /C %s", shell, cmdexec); } else { cmdbuf = cmdexec; } if (o.debug) logdebug("Executing: %s\n", cmdbuf); pid = run_command_redirected(cmdbuf, info); if (cmdbuf != cmdexec) free(cmdbuf); if (pid == -1) return -1; if (register_subprocess(info->proc) == -1) { if (o.verbose) logdebug("Couldn't register subprocess with termination handler; not executing.\n"); TerminateProcess(info->proc, 2); subprocess_info_close(info); return -1; } return pid; }
/* Relay data between a socket and a process until the process dies or stops sending or receiving data. The socket descriptor and process pipe handles are in the data argument, which must be a pointer to struct subprocess_info. This function is a workaround for the fact that we can't just run a process after redirecting its input handles to a socket. If the process, for example, redirects its own stdin, it somehow confuses the socket and stdout stops working. This is exactly what ncat does (as part of the Windows stdin workaround), so it can't be ignored. This function can be invoked through CreateThread to simulate fork+exec, or called directly to simulate exec. It frees the subprocess_info struct and closes the socket and pipe handles before returning. Returns the exit code of the subprocess. */ static DWORD WINAPI subprocess_thread_func(void *data) { struct subprocess_info *info; char pipe_buffer[BUFSIZ]; OVERLAPPED overlap = { 0 }; HANDLE events[3]; DWORD ret, rc; int crlf_state = 0; info = (struct subprocess_info *) data; /* Three events we watch for: socket read, pipe read, and process end. */ events[0] = (HANDLE) WSACreateEvent(); WSAEventSelect(info->fdn.fd, events[0], FD_READ | FD_CLOSE); events[1] = info->child_out_r; events[2] = info->proc; /* To avoid blocking or polling, we use asynchronous I/O, or what Microsoft calls "overlapped" I/O, on the process pipe. WaitForMultipleObjects reports when the read operation is complete. */ ReadFile(info->child_out_r, pipe_buffer, sizeof(pipe_buffer), NULL, &overlap); /* Loop until EOF or error. */ for (;;) { DWORD n, nwritten; int i; i = WaitForMultipleObjects(3, events, FALSE, INFINITE); if (i == WAIT_OBJECT_0) { /* Read from socket, write to process. */ char buffer[BUFSIZ]; int pending; ResetEvent(events[0]); do { n = ncat_recv(&info->fdn, buffer, sizeof(buffer), &pending); if (n <= 0) goto loop_end; if (WriteFile(info->child_in_w, buffer, n, &nwritten, NULL) == 0) break; if (nwritten != n) goto loop_end; } while (pending); } else if (i == WAIT_OBJECT_0 + 1) { char *crlf = NULL, *wbuf; /* Read from process, write to socket. */ if (GetOverlappedResult(info->child_out_r, &overlap, &n, FALSE)) { int n_r; wbuf = pipe_buffer; n_r = n; if (o.crlf) { if (fix_line_endings((char *) pipe_buffer, &n_r, &crlf, &crlf_state)) wbuf = crlf; } /* The above call to WSAEventSelect puts the socket in non-blocking mode, but we want this send to block, not potentially return WSAEWOULDBLOCK. We call block_socket, but first we must clear out the select event. */ WSAEventSelect(info->fdn.fd, events[0], 0); block_socket(info->fdn.fd); nwritten = ncat_send(&info->fdn, wbuf, n_r); if (crlf != NULL) free(crlf); if (nwritten != n_r) break; /* Restore the select event (and non-block the socket again.) */ WSAEventSelect(info->fdn.fd, events[0], FD_READ | FD_CLOSE); /* Queue another ansychronous read. */ ReadFile(info->child_out_r, pipe_buffer, sizeof(pipe_buffer), NULL, &overlap); } else { if (GetLastError() != ERROR_IO_PENDING) /* Error or end of file. */ break; } } else if (i == WAIT_OBJECT_0 + 2) { /* The child died. There are no more writes left in the pipe because WaitForMultipleObjects guarantees events with lower indexes are handled first. */ break; } else { break; } } loop_end: WSACloseEvent(events[0]); rc = unregister_subprocess(info->proc); ncat_assert(rc != -1); GetExitCodeProcess(info->proc, &ret); if (ret == STILL_ACTIVE) { if (o.debug > 1) logdebug("Subprocess still running, terminating it.\n"); rc = TerminateProcess(info->proc, 0); if (rc == 0) { if (o.debug > 1) logdebug("TerminateProcess failed with code %d.\n", rc); } } GetExitCodeProcess(info->proc, &ret); if (o.debug > 1) logdebug("Subprocess ended with exit code %d.\n", ret); shutdown(info->fdn.fd, 2); subprocess_info_close(info); free(info); rc = WaitForSingleObject(pseudo_sigchld_mutex, INFINITE); ncat_assert(rc == WAIT_OBJECT_0); if (pseudo_sigchld_handler != NULL) pseudo_sigchld_handler(); rc = ReleaseMutex(pseudo_sigchld_mutex); ncat_assert(rc != 0); return ret; }
/* Start a subprocess with run_command_redirected and register it with the termination handler. Takes care of o.shellexec. Returns the PID of the subprocess or -1 on error. */ static int start_subprocess(char *cmdexec, struct subprocess_info *info) { char *cmdbuf; int pid; if (o.execmode == EXEC_SHELL) { /* Run with cmd.exe. */ const char *shell; size_t cmdlen; shell = get_shell(); cmdlen = strlen(shell) + strlen(cmdexec) + 32; cmdbuf = (char *) safe_malloc(cmdlen); Snprintf(cmdbuf, cmdlen, "%s /C %s", shell, cmdexec); #ifdef HAVE_LUA } else if (o.execmode == EXEC_LUA) { char exepath[8192]; char *cmdexec_escaped, *exepath_escaped; int n; n = GetModuleFileName(GetModuleHandle(0), exepath, sizeof(exepath)); if (n == 0 || n == sizeof(exepath)) return -1; cmdexec_escaped = escape_windows_command_arg(cmdexec); if (cmdexec_escaped == NULL) return -1; exepath_escaped = escape_windows_command_arg(exepath); if (exepath_escaped == NULL) { free(cmdexec_escaped); return -1; } n = asprintf(&cmdbuf, "%s --lua-exec-internal %s", exepath_escaped, cmdexec_escaped); free(cmdexec_escaped); free(exepath_escaped); if (n < 0) return -1; #endif } else { cmdbuf = cmdexec; } if (o.debug) logdebug("Executing: %s\n", cmdbuf); pid = run_command_redirected(cmdbuf, info); if (cmdbuf != cmdexec) free(cmdbuf); if (pid == -1) return -1; if (register_subprocess(info->proc) == -1) { if (o.verbose) logdebug("Couldn't register subprocess with termination handler; not executing.\n"); TerminateProcess(info->proc, 2); subprocess_info_close(info); return -1; } return pid; }