/* * libwebsockets - small server side websockets and web server implementation * * Copyright (C) 2010 - 2020 Andy Green * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #include "private-lib-core.h" #include #include #include void lws_spawn_timeout(struct lws_sorted_usec_list *sul) { struct lws_spawn_piped *lsp = lws_container_of(sul, struct lws_spawn_piped, sul); lwsl_warn("%s: spawn exceeded timeout, killing\n", __func__); lws_spawn_piped_kill_child_process(lsp); } void lws_spawn_sul_reap(struct lws_sorted_usec_list *sul) { struct lws_spawn_piped *lsp = lws_container_of(sul, struct lws_spawn_piped, sul_reap); lwsl_notice("%s: reaping spawn after last stdpipe, tries left %d\n", __func__, lsp->reap_retry_budget); if (!lws_spawn_reap(lsp) && !lsp->pipes_alive) { if (--lsp->reap_retry_budget) { lws_sul_schedule(lsp->info.vh->context, lsp->info.tsi, &lsp->sul_reap, lws_spawn_sul_reap, 250 * LWS_US_PER_MS); } else { lwsl_err("%s: Unable to reap lsp %p, killing\n", __func__, lsp); lsp->reap_retry_budget = 20; lws_spawn_piped_kill_child_process(lsp); } } } static struct lws * lws_create_basic_wsi(struct lws_context *context, int tsi, const struct lws_role_ops *ops) { struct lws_context_per_thread *pt = &context->pt[tsi]; struct lws *new_wsi; if (!context->vhost_list) return NULL; if ((unsigned int)context->pt[tsi].fds_count == context->fd_limit_per_thread - 1) { lwsl_err("no space for new conn\n"); return NULL; } lws_context_lock(context, __func__); new_wsi = __lws_wsi_create_with_role(context, tsi, ops, NULL); lws_context_unlock(context); if (new_wsi == NULL) { lwsl_err("Out of memory for new connection\n"); return NULL; } new_wsi->rxflow_change_to = LWS_RXFLOW_ALLOW; /* initialize the instance struct */ lws_role_transition(new_wsi, 0, LRS_ESTABLISHED, ops); new_wsi->hdr_parsing_completed = 0; new_wsi->position_in_fds_table = LWS_NO_FDS_POS; /* * these can only be set once the protocol is known * we set an unestablished connection's protocol pointer * to the start of the defauly vhost supported list, so it can look * for matching ones during the handshake */ new_wsi->user_space = NULL; new_wsi->desc.sockfd = LWS_SOCK_INVALID; return new_wsi; } void lws_spawn_piped_destroy(struct lws_spawn_piped **_lsp) { struct lws_spawn_piped *lsp = *_lsp; struct lws *wsi; int n; if (!lsp) return; for (n = 0; n < 3; n++) { if (lsp->pipe_fds[n][!!(n == 0)]) { CloseHandle(lsp->pipe_fds[n][n == 0]); lsp->pipe_fds[n][n == 0] = NULL; } for (n = 0; n < 3; n++) { if (lsp->stdwsi[n]) { lwsl_notice("%s: closing stdwsi %d\n", __func__, n); wsi = lsp->stdwsi[n]; lsp->stdwsi[n]->desc.filefd = NULL; lsp->stdwsi[n] = NULL; lws_set_timeout(wsi, 1, LWS_TO_KILL_SYNC); } } } lws_dll2_remove(&lsp->dll); lws_sul_cancel(&lsp->sul); lws_sul_cancel(&lsp->sul_reap); lws_sul_cancel(&lsp->sul_poll); lwsl_warn("%s: deleting lsp\n", __func__); lws_free_set_NULL((*_lsp)); } int lws_spawn_reap(struct lws_spawn_piped *lsp) { void *opaque = lsp->info.opaque; lsp_cb_t cb = lsp->info.reap_cb; struct _lws_siginfo_t lsi; lws_usec_t acct[4]; DWORD ex; if (!lsp->child_pid) return 0; if (!GetExitCodeProcess(lsp->child_pid, &ex)) { lwsl_notice("%s: GetExitCodeProcess failed\n", __func__); return 0; } /* nonzero = success */ if (ex == STILL_ACTIVE) { lwsl_notice("%s: still active\n", __func__); return 0; } /* mark the earliest time we knew he had gone */ if (!lsp->reaped) { lsp->reaped = lws_now_usecs(); /* * Switch the timeout to restrict the amount of grace time * to drain stdwsi */ lws_sul_schedule(lsp->info.vh->context, lsp->info.tsi, &lsp->sul, lws_spawn_timeout, 5 * LWS_US_PER_SEC); } /* * Stage finalizing our reaction to the process going down until the * stdwsi flushed whatever is in flight and all noticed they were * closed. For that reason, each stdwsi close must call lws_spawn_reap * to check if that was the last one and we can proceed with the reap. */ if (!lsp->ungraceful && lsp->pipes_alive) { lwsl_notice("%s: stdwsi alive, not reaping\n", __func__); return 0; } /* we reached the reap point, no need for timeout wait */ lws_sul_cancel(&lsp->sul); /* * All the stdwsi went down, nothing more is coming... it's over * Collect the final information and then reap the dead process */ lsi.retcode = 0x10000 | (int)ex; lwsl_notice("%s: process exit 0x%x\n", __func__, lsi.retcode); lsp->child_pid = NULL; /* destroy the lsp itself first (it's freed and plsp set NULL */ if (lsp->info.plsp) lws_spawn_piped_destroy(lsp->info.plsp); /* then do the parent callback informing it's destroyed */ memset(acct, 0, sizeof(acct)); if (cb) cb(opaque, acct, &lsi, 0); lwsl_notice("%s: completed reap\n", __func__); return 1; /* was reaped */ } int lws_spawn_piped_kill_child_process(struct lws_spawn_piped *lsp) { if (!lsp->child_pid) return 1; lsp->ungraceful = 1; /* don't wait for flushing, just kill it */ if (lws_spawn_reap(lsp)) /* that may have invalidated lsp */ return 0; lwsl_warn("%s: calling TerminateProcess on child pid\n", __func__); TerminateProcess(lsp->child_pid, 252); lws_spawn_reap(lsp); /* that may have invalidated lsp */ return 0; } static void windows_pipe_poll_hack(lws_sorted_usec_list_t *sul) { struct lws_spawn_piped *lsp = lws_container_of(sul, struct lws_spawn_piped, sul_poll); struct lws *wsi, *wsi1; DWORD br; char c; /* * Do it first, we know lsp exists and if it's destroyed inbetweentimes, * it will already have cancelled this */ lws_sul_schedule(lsp->context, 0, &lsp->sul_poll, windows_pipe_poll_hack, 50 * LWS_US_PER_MS); wsi = lsp->stdwsi[LWS_STDOUT]; wsi1 = lsp->stdwsi[LWS_STDERR]; if (wsi && lsp->pipe_fds[LWS_STDOUT][0] != NULL) { if (!PeekNamedPipe(lsp->pipe_fds[LWS_STDOUT][0], &c, 1, &br, NULL, NULL)) { lwsl_notice("%s: stdout pipe errored\n", __func__); CloseHandle(lsp->stdwsi[LWS_STDOUT]->desc.filefd); lsp->pipe_fds[LWS_STDOUT][0] = NULL; lsp->stdwsi[LWS_STDOUT]->desc.filefd = NULL; lsp->stdwsi[LWS_STDOUT] = NULL; lws_set_timeout(wsi, 1, LWS_TO_KILL_SYNC); if (lsp->stdwsi[LWS_STDIN]) { lwsl_notice("%s: closing stdin from stdout close\n", __func__); CloseHandle(lsp->stdwsi[LWS_STDIN]->desc.filefd); wsi = lsp->stdwsi[LWS_STDIN]; lsp->stdwsi[LWS_STDIN]->desc.filefd = NULL; lsp->stdwsi[LWS_STDIN] = NULL; lsp->pipe_fds[LWS_STDIN][1] = NULL; lws_set_timeout(wsi, 1, LWS_TO_KILL_SYNC); } /* * lsp may be destroyed by here... if we wanted to * handle a still-extant stderr we'll get it next time */ return; } else if (br) wsi->a.protocol->callback(wsi, LWS_CALLBACK_RAW_RX_FILE, NULL, NULL, 0); } /* * lsp may have been destroyed above */ if (wsi1 && lsp->pipe_fds[LWS_STDERR][0]) { if (!PeekNamedPipe(lsp->pipe_fds[LWS_STDERR][0], &c, 1, &br, NULL, NULL)) { lwsl_notice("%s: stderr pipe errored\n", __func__); CloseHandle(wsi1->desc.filefd); /* * Assume is stderr still extant on entry, lsp can't * have been destroyed by stdout/stdin processing */ lsp->stdwsi[LWS_STDERR]->desc.filefd = NULL; lsp->stdwsi[LWS_STDERR] = NULL; lsp->pipe_fds[LWS_STDERR][0] = NULL; lws_set_timeout(wsi1, 1, LWS_TO_KILL_SYNC); /* * lsp may have been destroyed above */ } else if (br) wsi1->a.protocol->callback(wsi1, LWS_CALLBACK_RAW_RX_FILE, NULL, NULL, 0); } } /* * Deals with spawning a subprocess and executing it securely with stdin/out/err * diverted into pipes */ struct lws_spawn_piped * lws_spawn_piped(const struct lws_spawn_piped_info *i) { const struct lws_protocols *pcol = i->vh->context->vhost_list->protocols; struct lws_context *context = i->vh->context; struct lws_spawn_piped *lsp; PROCESS_INFORMATION pi; SECURITY_ATTRIBUTES sa; char cli[300], *p; STARTUPINFO si; int n; if (i->protocol_name) pcol = lws_vhost_name_to_protocol(i->vh, i->protocol_name); if (!pcol) { lwsl_err("%s: unknown protocol %s\n", __func__, i->protocol_name ? i->protocol_name : "default"); return NULL; } lsp = lws_zalloc(sizeof(*lsp), __func__); if (!lsp) { lwsl_err("%s: OOM\n", __func__); return NULL; } /* wholesale take a copy of info */ lsp->info = *i; lsp->context = context; lsp->reap_retry_budget = 20; /* * Prepare the stdin / out / err pipes */ for (n = 0; n < 3; n++) { lsp->pipe_fds[n][0] = NULL; lsp->pipe_fds[n][1] = NULL; } /* create pipes for [stdin|stdout] and [stderr] */ memset(&sa, 0, sizeof(sa)); sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.bInheritHandle = TRUE; /* inherit the pipes */ sa.lpSecurityDescriptor = NULL; for (n = 0; n < 3; n++) { DWORD waitmode = PIPE_NOWAIT; if (!CreatePipe(&lsp->pipe_fds[n][0], &lsp->pipe_fds[n][1], &sa, 0)) { lwsl_err("%s: CreatePipe() failed\n", __func__); goto bail1; } SetNamedPipeHandleState(lsp->pipe_fds[1][0], &waitmode, NULL, NULL); SetNamedPipeHandleState(lsp->pipe_fds[2][0], &waitmode, NULL, NULL); /* don't inherit the pipe side that belongs to the parent */ if (!SetHandleInformation(&lsp->pipe_fds[n][!n], HANDLE_FLAG_INHERIT, 0)) { lwsl_err("%s: SetHandleInformation() failed\n", __func__); //goto bail1; } } /* create wsis for each stdin/out/err fd */ for (n = 0; n < 3; n++) { lsp->stdwsi[n] = lws_create_basic_wsi(i->vh->context, i->tsi, i->ops ? i->ops : &role_ops_raw_file); if (!lsp->stdwsi[n]) { lwsl_err("%s: unable to create lsp stdwsi\n", __func__); goto bail2; } __lws_lc_tag(i->vh->context, &i->vh->context->lcg[LWSLCG_WSI], &lsp->stdwsi[n]->lc, "nspawn-stdwsi-%d", n); lsp->stdwsi[n]->lsp_channel = n; lws_vhost_bind_wsi(i->vh, lsp->stdwsi[n]); lsp->stdwsi[n]->a.protocol = pcol; lsp->stdwsi[n]->a.opaque_user_data = i->opaque; lsp->stdwsi[n]->desc.filefd = lsp->pipe_fds[n][!n]; lsp->stdwsi[n]->file_desc = 1; lwsl_debug("%s: lsp stdwsi %p: pipe idx %d -> fd %d / %d\n", __func__, lsp->stdwsi[n], n, lsp->pipe_fds[n][!!(n == 0)], lsp->pipe_fds[n][!(n == 0)]); #if 0 /* read side is 0, stdin we want the write side, others read */ lsp->stdwsi[n]->desc.filefd = lsp->pipe_fds[n][!!(n == 0)]; if (fcntl(lsp->pipe_fds[n][!!(n == 0)], F_SETFL, O_NONBLOCK) < 0) { lwsl_err("%s: setting NONBLOCK failed\n", __func__); goto bail2; } #endif } for (n = 0; n < 3; n++) if (i->opt_parent) { lsp->stdwsi[n]->parent = i->opt_parent; lsp->stdwsi[n]->sibling_list = i->opt_parent->child_list; i->opt_parent->child_list = lsp->stdwsi[n]; } lwsl_notice("%s: pipe handles in %p, out %p, err %p\n", __func__, lsp->stdwsi[LWS_STDIN]->desc.sockfd, lsp->stdwsi[LWS_STDOUT]->desc.sockfd, lsp->stdwsi[LWS_STDERR]->desc.sockfd); /* * Windows nonblocking pipe handling is a mess that is unable * to interoperate with WSA-based wait as far as I can tell. * * Let's set up a sul to poll the pipes and synthesize the * protocol callbacks if anything coming. */ lws_sul_schedule(context, 0, &lsp->sul_poll, windows_pipe_poll_hack, 50 * LWS_US_PER_MS); /* * Windows wants a single string commandline */ p = cli; n = 0; while (i->exec_array[n]) { lws_strncpy(p, i->exec_array[n], sizeof(cli) - lws_ptr_diff(p, cli)); if (sizeof(cli) - lws_ptr_diff(p, cli) < 4) break; p += strlen(p); *p++ = ' '; *p = '\0'; n++; } puts(cli); memset(&pi, 0, sizeof(pi)); memset(&si, 0, sizeof(si)); si.cb = sizeof(STARTUPINFO); si.hStdInput = lsp->pipe_fds[LWS_STDIN][0]; si.hStdOutput = lsp->pipe_fds[LWS_STDOUT][1]; si.hStdError = lsp->pipe_fds[LWS_STDERR][1]; si.dwFlags = STARTF_USESTDHANDLES | CREATE_NO_WINDOW; si.wShowWindow = TRUE; if (!CreateProcess(NULL, cli, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) { lwsl_err("%s: CreateProcess failed 0x%x\n", __func__, (unsigned long)GetLastError()); goto bail3; } lsp->child_pid = pi.hProcess; lwsl_notice("%s: lsp %p spawned PID %d\n", __func__, lsp, lsp->child_pid); lws_sul_schedule(context, i->tsi, &lsp->sul, lws_spawn_timeout, i->timeout_us ? i->timeout_us : 300 * LWS_US_PER_SEC); /* * close: stdin:r, stdout:w, stderr:w */ for (n = 0; n < 3; n++) CloseHandle(lsp->pipe_fds[n][n != 0]); lsp->pipes_alive = 3; lsp->created = lws_now_usecs(); if (i->owner) lws_dll2_add_head(&lsp->dll, i->owner); if (i->timeout_us) lws_sul_schedule(context, i->tsi, &lsp->sul, lws_spawn_timeout, i->timeout_us); return lsp; bail3: lws_sul_cancel(&lsp->sul_poll); while (--n >= 0) __remove_wsi_socket_from_fds(lsp->stdwsi[n]); bail2: for (n = 0; n < 3; n++) if (lsp->stdwsi[n]) __lws_free_wsi(lsp->stdwsi[n]); bail1: for (n = 0; n < 3; n++) { if (lsp->pipe_fds[n][0] >= 0) CloseHandle(lsp->pipe_fds[n][0]); if (lsp->pipe_fds[n][1] >= 0) CloseHandle(lsp->pipe_fds[n][1]); } lws_free(lsp); lwsl_err("%s: failed\n", __func__); return NULL; } void lws_spawn_stdwsi_closed(struct lws_spawn_piped *lsp, struct lws *wsi) { int n; assert(lsp); lsp->pipes_alive--; lwsl_debug("%s: pipes alive %d\n", __func__, lsp->pipes_alive); if (!lsp->pipes_alive) lws_sul_schedule(lsp->info.vh->context, lsp->info.tsi, &lsp->sul_reap, lws_spawn_sul_reap, 1); for (n = 0; n < 3; n++) if (lsp->stdwsi[n] == wsi) lsp->stdwsi[n] = NULL; } int lws_spawn_get_stdfd(struct lws *wsi) { return wsi->lsp_channel; }