LWS_VISIBLE int lws_server_socket_service_ssl(struct lws *wsi, lws_sockfd_type accept_fd) { struct lws_context *context = wsi->context; struct lws_vhost *vh; struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; int n; char buf[256]; (void)buf; if (!LWS_SSL_ENABLED(wsi->vhost)) return 0; switch (lwsi_state(wsi)) { case LRS_SSL_INIT: if (wsi->tls.ssl) lwsl_err("%s: leaking ssl\n", __func__); if (accept_fd == LWS_SOCK_INVALID) assert(0); if (context->simultaneous_ssl_restriction && context->simultaneous_ssl >= context->simultaneous_ssl_restriction) { lwsl_notice("unable to deal with SSL connection\n"); return 1; } if (lws_tls_server_new_nonblocking(wsi, accept_fd)) { if (accept_fd != LWS_SOCK_INVALID) compatible_close(accept_fd); goto fail; } if (context->simultaneous_ssl_restriction && ++context->simultaneous_ssl == context->simultaneous_ssl_restriction) /* that was the last allowed SSL connection */ lws_gate_accepts(context, 0); #if defined(LWS_WITH_STATS) context->updated = 1; #endif /* * we are not accepted yet, but we need to enter ourselves * as a live connection. That way we can retry when more * pieces come if we're not sorted yet */ lwsi_set_state(wsi, LRS_SSL_ACK_PENDING); lws_pt_lock(pt, __func__); if (__insert_wsi_socket_into_fds(context, wsi)) { lwsl_err("%s: failed to insert into fds\n", __func__); goto fail; } lws_pt_unlock(pt); lws_set_timeout(wsi, PENDING_TIMEOUT_SSL_ACCEPT, context->timeout_secs); lwsl_debug("inserted SSL accept into fds, trying SSL_accept\n"); /* fallthru */ case LRS_SSL_ACK_PENDING: if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { lwsl_err("%s: lws_change_pollfd failed\n", __func__); goto fail; } lws_latency_pre(context, wsi); if (wsi->vhost->tls.allow_non_ssl_on_ssl_port) { n = recv(wsi->desc.sockfd, (char *)pt->serv_buf, context->pt_serv_buf_size, MSG_PEEK); /* * optionally allow non-SSL connect on SSL listening socket * This is disabled by default, if enabled it goes around any * SSL-level access control (eg, client-side certs) so leave * it disabled unless you know it's not a problem for you */ if (n >= 1 && pt->serv_buf[0] >= ' ') { /* * TLS content-type for Handshake is 0x16, and * for ChangeCipherSpec Record, it's 0x14 * * A non-ssl session will start with the HTTP * method in ASCII. If we see it's not a legit * SSL handshake kill the SSL for this * connection and try to handle as a HTTP * connection upgrade directly. */ wsi->tls.use_ssl = 0; lws_tls_server_abort_connection(wsi); /* * care... this creates wsi with no ssl * when ssl is enabled and normally * mandatory */ wsi->tls.ssl = NULL; if (lws_check_opt(context->options, LWS_SERVER_OPTION_REDIRECT_HTTP_TO_HTTPS)) wsi->tls.redirect_to_https = 1; lwsl_debug("accepted as non-ssl\n"); goto accepted; } if (!n) { /* * connection is gone, fail out */ lwsl_debug("PEEKed 0\n"); goto fail; } if (n < 0 && (LWS_ERRNO == LWS_EAGAIN || LWS_ERRNO == LWS_EWOULDBLOCK)) { /* * well, we get no way to know ssl or not * so go around again waiting for something * to come and give us a hint, or timeout the * connection. */ if (lws_change_pollfd(wsi, 0, LWS_POLLIN)) { lwsl_info("%s: change_pollfd failed\n", __func__); return -1; } lwsl_info("SSL_ERROR_WANT_READ\n"); return 0; } } /* normal SSL connection processing path */ #if defined(LWS_WITH_STATS) /* only set this the first time around */ if (!wsi->accept_start_us) wsi->accept_start_us = lws_time_in_microseconds(); #endif errno = 0; lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_SSL_CONNECTIONS_ACCEPT_SPIN, 1); n = lws_tls_server_accept(wsi); lws_latency(context, wsi, "SSL_accept LRS_SSL_ACK_PENDING\n", n, n == 1); lwsl_info("SSL_accept says %d\n", n); switch (n) { case LWS_SSL_CAPABLE_DONE: break; case LWS_SSL_CAPABLE_ERROR: lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_SSL_CONNECTIONS_FAILED, 1); lwsl_info("SSL_accept failed socket %u: %d\n", wsi->desc.sockfd, n); wsi->socket_is_permanently_unusable = 1; goto fail; default: /* MORE_SERVICE */ return 0; } lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED, 1); #if defined(LWS_WITH_STATS) if (wsi->accept_start_us) lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_MS_SSL_CONNECTIONS_ACCEPTED_DELAY, lws_time_in_microseconds() - wsi->accept_start_us); wsi->accept_start_us = lws_time_in_microseconds(); #endif accepted: /* adapt our vhost to match the SNI SSL_CTX that was chosen */ vh = context->vhost_list; while (vh) { if (!vh->being_destroyed && wsi->tls.ssl && vh->tls.ssl_ctx == lws_tls_ctx_from_wsi(wsi)) { lwsl_info("setting wsi to vh %s\n", vh->name); lws_vhost_bind_wsi(vh, wsi); break; } vh = vh->vhost_next; } /* OK, we are accepted... give him some time to negotiate */ lws_set_timeout(wsi, PENDING_TIMEOUT_ESTABLISH_WITH_SERVER, context->timeout_secs); lwsi_set_state(wsi, LRS_ESTABLISHED); if (lws_tls_server_conn_alpn(wsi)) goto fail; lwsl_debug("accepted new SSL conn\n"); break; default: break; } return 0; fail: return 1; }
LWS_VISIBLE LWS_EXTERN int lws_cgi(struct lws *wsi, const char * const *exec_array, int script_uri_path_len, int timeout_secs, const struct lws_protocol_vhost_options *mp_cgienv) { struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; char *env_array[30], cgi_path[500], e[1024], *p = e, *end = p + sizeof(e) - 1, tok[256], *t, *sum, *sumend; struct lws_cgi *cgi; int n, m = 0, i, uritok = -1, c; /* * give the master wsi a cgi struct */ wsi->http.cgi = lws_zalloc(sizeof(*wsi->http.cgi), "new cgi"); if (!wsi->http.cgi) { lwsl_err("%s: OOM\n", __func__); return -1; } wsi->http.cgi->response_code = HTTP_STATUS_OK; cgi = wsi->http.cgi; cgi->wsi = wsi; /* set cgi's owning wsi */ sum = cgi->summary; sumend = sum + strlen(cgi->summary) - 1; for (n = 0; n < 3; n++) { cgi->pipe_fds[n][0] = -1; cgi->pipe_fds[n][1] = -1; } /* create pipes for [stdin|stdout] and [stderr] */ for (n = 0; n < 3; n++) if (pipe(cgi->pipe_fds[n]) == -1) goto bail1; /* create cgi wsis for each stdin/out/err fd */ for (n = 0; n < 3; n++) { cgi->stdwsi[n] = lws_create_basic_wsi(wsi->context, wsi->tsi); if (!cgi->stdwsi[n]) { lwsl_err("%s: unable to create cgi stdwsi\n", __func__); goto bail2; } cgi->stdwsi[n]->cgi_channel = n; lws_vhost_bind_wsi(wsi->vhost, cgi->stdwsi[n]); lwsl_debug("%s: cgi stdwsi %p: pipe idx %d -> fd %d / %d\n", __func__, cgi->stdwsi[n], n, cgi->pipe_fds[n][!!(n == 0)], cgi->pipe_fds[n][!(n == 0)]); /* read side is 0, stdin we want the write side, others read */ cgi->stdwsi[n]->desc.sockfd = cgi->pipe_fds[n][!!(n == 0)]; if (fcntl(cgi->pipe_fds[n][!!(n == 0)], F_SETFL, O_NONBLOCK) < 0) { lwsl_err("%s: setting NONBLOCK failed\n", __func__); goto bail2; } } for (n = 0; n < 3; n++) { if (wsi->context->event_loop_ops->accept) if (wsi->context->event_loop_ops->accept(cgi->stdwsi[n])) goto bail3; if (__insert_wsi_socket_into_fds(wsi->context, cgi->stdwsi[n])) goto bail3; cgi->stdwsi[n]->parent = wsi; cgi->stdwsi[n]->sibling_list = wsi->child_list; wsi->child_list = cgi->stdwsi[n]; } lws_change_pollfd(cgi->stdwsi[LWS_STDIN], LWS_POLLIN, LWS_POLLOUT); lws_change_pollfd(cgi->stdwsi[LWS_STDOUT], LWS_POLLOUT, LWS_POLLIN); lws_change_pollfd(cgi->stdwsi[LWS_STDERR], LWS_POLLOUT, LWS_POLLIN); lwsl_debug("%s: fds in %d, out %d, err %d\n", __func__, cgi->stdwsi[LWS_STDIN]->desc.sockfd, cgi->stdwsi[LWS_STDOUT]->desc.sockfd, cgi->stdwsi[LWS_STDERR]->desc.sockfd); if (timeout_secs) lws_set_timeout(wsi, PENDING_TIMEOUT_CGI, timeout_secs); /* the cgi stdout is always sending us http1.x header data first */ wsi->hdr_state = LCHS_HEADER; /* add us to the pt list of active cgis */ lwsl_debug("%s: adding cgi %p to list\n", __func__, wsi->http.cgi); cgi->cgi_list = pt->http.cgi_list; pt->http.cgi_list = cgi; sum += lws_snprintf(sum, sumend - sum, "%s ", exec_array[0]); if (0) { char *pct = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_ENCODING); if (pct && !strcmp(pct, "gzip")) wsi->http.cgi->gzip_inflate = 1; } /* prepare his CGI env */ n = 0; if (lws_is_ssl(wsi)) env_array[n++] = "HTTPS=ON"; if (wsi->http.ah) { static const unsigned char meths[] = { WSI_TOKEN_GET_URI, WSI_TOKEN_POST_URI, WSI_TOKEN_OPTIONS_URI, WSI_TOKEN_PUT_URI, WSI_TOKEN_PATCH_URI, WSI_TOKEN_DELETE_URI, WSI_TOKEN_CONNECT, WSI_TOKEN_HEAD_URI, #ifdef LWS_WITH_HTTP2 WSI_TOKEN_HTTP_COLON_PATH, #endif }; static const char * const meth_names[] = { "GET", "POST", "OPTIONS", "PUT", "PATCH", "DELETE", "CONNECT", "HEAD", ":path" }; if (script_uri_path_len >= 0) for (m = 0; m < (int)LWS_ARRAY_SIZE(meths); m++) if (lws_hdr_total_length(wsi, meths[m]) >= script_uri_path_len) { uritok = meths[m]; break; } if (script_uri_path_len < 0 && uritok < 0) goto bail3; // if (script_uri_path_len < 0) // uritok = 0; if (m >= 0) { env_array[n++] = p; if (m < 8) { p += lws_snprintf(p, end - p, "REQUEST_METHOD=%s", meth_names[m]); sum += lws_snprintf(sum, sumend - sum, "%s ", meth_names[m]); } else { p += lws_snprintf(p, end - p, "REQUEST_METHOD=%s", lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_METHOD)); sum += lws_snprintf(sum, sumend - sum, "%s ", lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_METHOD)); } p++; } if (uritok >= 0) sum += lws_snprintf(sum, sumend - sum, "%s ", lws_hdr_simple_ptr(wsi, uritok)); env_array[n++] = p; p += lws_snprintf(p, end - p, "QUERY_STRING="); /* dump the individual URI Arg parameters */ m = 0; while (script_uri_path_len >= 0) { i = lws_hdr_copy_fragment(wsi, tok, sizeof(tok), WSI_TOKEN_HTTP_URI_ARGS, m); if (i < 0) break; t = tok; while (*t && *t != '=' && p < end - 4) *p++ = *t++; if (*t == '=') *p++ = *t++; i = urlencode(t, i- (t - tok), p, end - p); if (i > 0) { p += i; *p++ = '&'; } m++; } if (m) p--; *p++ = '\0'; if (uritok >= 0) { strcpy(cgi_path, "REQUEST_URI="); c = lws_hdr_copy(wsi, cgi_path + 12, sizeof(cgi_path) - 12, uritok); if (c < 0) goto bail3; cgi_path[sizeof(cgi_path) - 1] = '\0'; env_array[n++] = cgi_path; } sum += lws_snprintf(sum, sumend - sum, "%s", env_array[n - 1]); if (script_uri_path_len >= 0) { env_array[n++] = p; p += lws_snprintf(p, end - p, "PATH_INFO=%s", cgi_path + 12 + script_uri_path_len); p++; } } if (script_uri_path_len >= 0 && lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_REFERER)) { env_array[n++] = p; p += lws_snprintf(p, end - p, "HTTP_REFERER=%s", lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_REFERER)); p++; } if (script_uri_path_len >= 0 && lws_hdr_total_length(wsi, WSI_TOKEN_HOST)) { env_array[n++] = p; p += lws_snprintf(p, end - p, "HTTP_HOST=%s", lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST)); p++; } if (script_uri_path_len >= 0 && lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE)) { env_array[n++] = p; p += lws_snprintf(p, end - p, "HTTP_COOKIE="); m = lws_hdr_copy(wsi, p, end - p, WSI_TOKEN_HTTP_COOKIE); if (m > 0) p += lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE); *p++ = '\0'; } if (script_uri_path_len >= 0 && lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_USER_AGENT)) { env_array[n++] = p; p += lws_snprintf(p, end - p, "HTTP_USER_AGENT=%s", lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_USER_AGENT)); p++; } if (script_uri_path_len >= 0 && lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_ENCODING)) { env_array[n++] = p; p += lws_snprintf(p, end - p, "HTTP_CONTENT_ENCODING=%s", lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_ENCODING)); p++; } if (script_uri_path_len >= 0 && lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_ACCEPT)) { env_array[n++] = p; p += lws_snprintf(p, end - p, "HTTP_ACCEPT=%s", lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_ACCEPT)); p++; } if (script_uri_path_len >= 0 && lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_ACCEPT_ENCODING)) { env_array[n++] = p; p += lws_snprintf(p, end - p, "HTTP_ACCEPT_ENCODING=%s", lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_ACCEPT_ENCODING)); p++; } if (script_uri_path_len >= 0 && uritok == WSI_TOKEN_POST_URI) { if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE)) { env_array[n++] = p; p += lws_snprintf(p, end - p, "CONTENT_TYPE=%s", lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE)); p++; } if (!wsi->http.cgi->gzip_inflate && lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) { env_array[n++] = p; p += lws_snprintf(p, end - p, "CONTENT_LENGTH=%s", lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)); p++; } if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) wsi->http.cgi->post_in_expected = atoll(lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)); } env_array[n++] = "PATH=/bin:/usr/bin:/usr/local/bin:/var/www/cgi-bin"; env_array[n++] = p; p += lws_snprintf(p, end - p, "SCRIPT_PATH=%s", exec_array[0]) + 1; while (mp_cgienv) { env_array[n++] = p; p += lws_snprintf(p, end - p, "%s=%s", mp_cgienv->name, mp_cgienv->value); if (!strcmp(mp_cgienv->name, "GIT_PROJECT_ROOT")) { wsi->http.cgi->implied_chunked = 1; wsi->http.cgi->explicitly_chunked = 1; } lwsl_info(" Applying mount-specific cgi env '%s'\n", env_array[n - 1]); p++; mp_cgienv = mp_cgienv->next; } env_array[n++] = "SERVER_SOFTWARE=libwebsockets"; env_array[n] = NULL; #if 0 for (m = 0; m < n; m++) lwsl_notice(" %s\n", env_array[m]); #endif /* * Actually having made the env, as a cgi we don't need the ah * any more */ if (script_uri_path_len >= 0) lws_header_table_detach(wsi, 0); /* we are ready with the redirection pipes... run the thing */ #if !defined(LWS_HAVE_VFORK) || !defined(LWS_HAVE_EXECVPE) cgi->pid = fork(); #else cgi->pid = vfork(); #endif if (cgi->pid < 0) { lwsl_err("fork failed, errno %d", errno); goto bail3; } #if defined(__linux__) prctl(PR_SET_PDEATHSIG, SIGTERM); #endif if (script_uri_path_len >= 0) /* stops non-daemonized main processess getting SIGINT * from TTY */ setpgrp(); if (cgi->pid) { /* we are the parent process */ wsi->context->count_cgi_spawned++; lwsl_info("%s: cgi %p spawned PID %d\n", __func__, cgi, cgi->pid); /* * close: stdin:r, stdout:w, stderr:w * hide from other forks: stdin:w, stdout:r, stderr:r */ for (n = 0; n < 3; n++) { lws_plat_apply_FD_CLOEXEC(cgi->pipe_fds[n][!!(n == 0)]); close(cgi->pipe_fds[n][!(n == 0)]); } /* inform cgi owner of the child PID */ n = user_callback_handle_rxflow(wsi->protocol->callback, wsi, LWS_CALLBACK_CGI_PROCESS_ATTACH, wsi->user_space, NULL, cgi->pid); (void)n; return 0; } /* somewhere we can at least read things and enter it */ if (chdir("/tmp")) lwsl_notice("%s: Failed to chdir\n", __func__); /* We are the forked process, redirect and kill inherited things. * * Because of vfork(), we cannot do anything that changes pages in * the parent environment. Stuff that changes kernel state for the * process is OK. Stuff that happens after the execvpe() is OK. */ for (n = 0; n < 3; n++) { if (dup2(cgi->pipe_fds[n][!(n == 0)], n) < 0) { lwsl_err("%s: stdin dup2 failed\n", __func__); goto bail3; } close(cgi->pipe_fds[n][0]); close(cgi->pipe_fds[n][1]); } #if !defined(LWS_HAVE_VFORK) || !defined(LWS_HAVE_EXECVPE) for (m = 0; m < n; m++) { p = strchr(env_array[m], '='); *p++ = '\0'; setenv(env_array[m], p, 1); } execvp(exec_array[0], (char * const *)&exec_array[0]); #else execvpe(exec_array[0], (char * const *)&exec_array[0], &env_array[0]); #endif exit(1); bail3: /* drop us from the pt cgi list */ pt->http.cgi_list = cgi->cgi_list; while (--n >= 0) __remove_wsi_socket_from_fds(wsi->http.cgi->stdwsi[n]); bail2: for (n = 0; n < 3; n++) if (wsi->http.cgi->stdwsi[n]) __lws_free_wsi(cgi->stdwsi[n]); bail1: for (n = 0; n < 3; n++) { if (cgi->pipe_fds[n][0] >= 0) close(cgi->pipe_fds[n][0]); if (cgi->pipe_fds[n][1] >= 0) close(cgi->pipe_fds[n][1]); } lws_free_set_NULL(wsi->http.cgi); lwsl_err("%s: failed\n", __func__); return -1; }