struct lws * lws_client_connect_2(struct lws *wsi) { #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) struct lws_context *context = wsi->context; struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; const char *adsin; struct lws *wsi_piggyback = NULL; struct lws_pollfd pfd; ssize_t plen = 0; #endif struct addrinfo *result; #if defined(LWS_WITH_UNIX_SOCK) struct sockaddr_un sau; char unix_skt = 0; #endif const char *ads; sockaddr46 sa46; const struct sockaddr *psa; int n, port; const char *cce = "", *iface; const char *meth = NULL; #ifdef LWS_WITH_IPV6 char ipv6only = lws_check_opt(wsi->vhost->options, LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY | LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE); #if defined(__ANDROID__) ipv6only = 0; #endif #endif lwsl_client("%s: %p\n", __func__, wsi); #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) if (!wsi->http.ah) { cce = "ah was NULL at cc2"; lwsl_err("%s\n", cce); goto oom4; } /* we can only piggyback GET or POST */ meth = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD); if (meth && strcmp(meth, "GET") && strcmp(meth, "POST")) goto create_new_conn; /* we only pipeline connections that said it was okay */ if (!wsi->client_pipeline) goto create_new_conn; /* * let's take a look first and see if there are any already-active * client connections we can piggy-back on. */ adsin = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS); lws_vhost_lock(wsi->vhost); /* ----------------------------------- { */ lws_start_foreach_dll_safe(struct lws_dll_lws *, d, d1, wsi->vhost->dll_active_client_conns.next) { struct lws *w = lws_container_of(d, struct lws, dll_active_client_conns); lwsl_debug("%s: check %s %s %d %d\n", __func__, adsin, w->client_hostname_copy, wsi->c_port, w->c_port); if (w != wsi && w->client_hostname_copy && !strcmp(adsin, w->client_hostname_copy) && #if defined(LWS_WITH_TLS) (wsi->tls.use_ssl & LCCSCF_USE_SSL) == (w->tls.use_ssl & LCCSCF_USE_SSL) && #endif wsi->c_port == w->c_port) { /* someone else is already connected to the right guy */ /* do we know for a fact pipelining won't fly? */ if (w->keepalive_rejected) { lwsl_info("defeating pipelining due to no " "keepalive on server\n"); lws_vhost_unlock(wsi->vhost); /* } ---------- */ goto create_new_conn; } #if defined (LWS_WITH_HTTP2) /* * h2: in usable state already: just use it without * going through the queue */ if (w->client_h2_alpn && (lwsi_state(w) == LRS_H2_WAITING_TO_SEND_HEADERS || lwsi_state(w) == LRS_ESTABLISHED)) { lwsl_info("%s: just join h2 directly\n", __func__); wsi->client_h2_alpn = 1; lws_wsi_h2_adopt(w, wsi); lws_vhost_unlock(wsi->vhost); /* } ---------- */ return wsi; } #endif lwsl_info("applying %p to txn queue on %p (wsistate 0x%x)\n", wsi, w, w->wsistate); /* * ...let's add ourselves to his transaction queue... * we are adding ourselves at the HEAD */ lws_dll_lws_add_front(&wsi->dll_client_transaction_queue, &w->dll_client_transaction_queue_head); /* * h1: pipeline our headers out on him, * and wait for our turn at client transaction_complete * to take over parsing the rx. */ wsi_piggyback = w; lws_vhost_unlock(wsi->vhost); /* } ---------- */ goto send_hs; } } lws_end_foreach_dll_safe(d, d1); lws_vhost_unlock(wsi->vhost); /* } ---------------------------------- */ create_new_conn: #endif /* * clients who will create their own fresh connection keep a copy of * the hostname they originally connected to, in case other connections * want to use it too */ if (!wsi->client_hostname_copy) wsi->client_hostname_copy = lws_strdup(lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS)); /* * If we made our own connection, and we're doing a method that can take * a pipeline, we are an "active client connection". * * Add ourselves to the vhost list of those so that others can * piggyback on our transaction queue */ if (meth && (!strcmp(meth, "GET") || !strcmp(meth, "POST")) && lws_dll_is_null(&wsi->dll_client_transaction_queue) && lws_dll_is_null(&wsi->dll_active_client_conns)) { lws_vhost_lock(wsi->vhost); /* caution... we will have to unpick this on oom4 path */ lws_dll_lws_add_front(&wsi->dll_active_client_conns, &wsi->vhost->dll_active_client_conns); lws_vhost_unlock(wsi->vhost); } /* * unix socket destination? */ ads = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS); #if defined(LWS_WITH_UNIX_SOCK) if (*ads == '+') { ads++; memset(&sau, 0, sizeof(sau)); sau.sun_family = AF_UNIX; strncpy(sau.sun_path, ads, sizeof(sau.sun_path)); sau.sun_path[sizeof(sau.sun_path) - 1] = '\0'; lwsl_info("%s: Unix skt: %s\n", __func__, ads); if (sau.sun_path[0] == '@') sau.sun_path[0] = '\0'; unix_skt = 1; goto ads_known; } #endif /* * start off allowing ipv6 on connection if vhost allows it */ wsi->ipv6 = LWS_IPV6_ENABLED(wsi->vhost); #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) /* Decide what it is we need to connect to: * * Priority 1: connect to http proxy */ if (wsi->vhost->http.http_proxy_port) { plen = sprintf((char *)pt->serv_buf, "CONNECT %s:%u HTTP/1.0\x0d\x0a" "User-agent: libwebsockets\x0d\x0a", lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS), wsi->c_port); if (wsi->vhost->proxy_basic_auth_token[0]) plen += sprintf((char *)pt->serv_buf + plen, "Proxy-authorization: basic %s\x0d\x0a", wsi->vhost->proxy_basic_auth_token); plen += sprintf((char *)pt->serv_buf + plen, "\x0d\x0a"); ads = wsi->vhost->http.http_proxy_address; port = wsi->vhost->http.http_proxy_port; #else if (0) { #endif #if defined(LWS_WITH_SOCKS5) /* Priority 2: Connect to SOCK5 Proxy */ } else if (wsi->vhost->socks_proxy_port) { socks_generate_msg(wsi, SOCKS_MSG_GREETING, &plen); lwsl_client("Sending SOCKS Greeting\n"); ads = wsi->vhost->socks_proxy_address; port = wsi->vhost->socks_proxy_port; #endif } else { /* Priority 3: Connect directly */ ads = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS); port = wsi->c_port; } /* * prepare the actual connection * to whatever we decided to connect to */ lwsl_info("%s: %p: address %s\n", __func__, wsi, ads); n = lws_getaddrinfo46(wsi, ads, &result); #ifdef LWS_WITH_IPV6 if (wsi->ipv6) { struct sockaddr_in6 *sa6; if (n || !result) { /* lws_getaddrinfo46 failed, there is no usable result */ lwsl_notice("%s: lws_getaddrinfo46 failed %d\n", __func__, n); cce = "ipv6 lws_getaddrinfo46 failed"; goto oom4; } sa6 = ((struct sockaddr_in6 *)result->ai_addr); memset(&sa46, 0, sizeof(sa46)); sa46.sa6.sin6_family = AF_INET6; switch (result->ai_family) { case AF_INET: if (ipv6only) break; /* map IPv4 to IPv6 */ bzero((char *)&sa46.sa6.sin6_addr, sizeof(sa46.sa6.sin6_addr)); sa46.sa6.sin6_addr.s6_addr[10] = 0xff; sa46.sa6.sin6_addr.s6_addr[11] = 0xff; memcpy(&sa46.sa6.sin6_addr.s6_addr[12], &((struct sockaddr_in *)result->ai_addr)->sin_addr, sizeof(struct in_addr)); lwsl_notice("uplevelling AF_INET to AF_INET6\n"); break; case AF_INET6: memcpy(&sa46.sa6.sin6_addr, &sa6->sin6_addr, sizeof(struct in6_addr)); sa46.sa6.sin6_scope_id = sa6->sin6_scope_id; sa46.sa6.sin6_flowinfo = sa6->sin6_flowinfo; break; default: lwsl_err("Unknown address family\n"); freeaddrinfo(result); cce = "unknown address family"; goto oom4; } } else #endif /* use ipv6 */ /* use ipv4 */ { void *p = NULL; if (!n) { struct addrinfo *res = result; /* pick the first AF_INET (IPv4) result */ while (!p && res) { switch (res->ai_family) { case AF_INET: p = &((struct sockaddr_in *)res->ai_addr)->sin_addr; break; } res = res->ai_next; } #if defined(LWS_FALLBACK_GETHOSTBYNAME) } else if (n == EAI_SYSTEM) { struct hostent *host; lwsl_info("getaddrinfo (ipv4) failed, trying gethostbyname\n"); host = gethostbyname(ads); if (host) { p = host->h_addr; } else { lwsl_err("gethostbyname failed\n"); cce = "gethostbyname (ipv4) failed"; goto oom4; } #endif } else { lwsl_err("getaddrinfo failed: %d\n", n); cce = "getaddrinfo failed"; goto oom4; } if (!p) { if (result) freeaddrinfo(result); lwsl_err("Couldn't identify address\n"); cce = "unable to lookup address"; goto oom4; } sa46.sa4.sin_family = AF_INET; sa46.sa4.sin_addr = *((struct in_addr *)p); bzero(&sa46.sa4.sin_zero, 8); } if (result) freeaddrinfo(result); #if defined(LWS_WITH_UNIX_SOCK) ads_known: #endif /* now we decided on ipv4 or ipv6, set the port */ if (!lws_socket_is_valid(wsi->desc.sockfd)) { if (wsi->context->event_loop_ops->check_client_connect_ok && wsi->context->event_loop_ops->check_client_connect_ok(wsi)) { cce = "waiting for event loop watcher to close"; goto oom4; } #if defined(LWS_WITH_UNIX_SOCK) if (unix_skt) { wsi->unix_skt = 1; wsi->desc.sockfd = socket(AF_UNIX, SOCK_STREAM, 0); } else #endif { #ifdef LWS_WITH_IPV6 if (wsi->ipv6) wsi->desc.sockfd = socket(AF_INET6, SOCK_STREAM, 0); else #endif wsi->desc.sockfd = socket(AF_INET, SOCK_STREAM, 0); } if (!lws_socket_is_valid(wsi->desc.sockfd)) { lwsl_warn("Unable to open socket\n"); cce = "unable to open socket"; goto oom4; } if (lws_plat_set_socket_options(wsi->vhost, wsi->desc.sockfd, #if defined(LWS_WITH_UNIX_SOCK) unix_skt)) { #else 0)) { #endif lwsl_err("Failed to set wsi socket options\n"); compatible_close(wsi->desc.sockfd); cce = "set socket opts failed"; goto oom4; } lwsi_set_state(wsi, LRS_WAITING_CONNECT); if (wsi->context->event_loop_ops->accept) if (wsi->context->event_loop_ops->accept(wsi)) { compatible_close(wsi->desc.sockfd); cce = "event loop accept failed"; goto oom4; } if (__insert_wsi_socket_into_fds(wsi->context, wsi)) { compatible_close(wsi->desc.sockfd); cce = "insert wsi failed"; goto oom4; } lws_change_pollfd(wsi, 0, LWS_POLLIN); /* * past here, we can't simply free the structs as error * handling as oom4 does. We have to run the whole close flow. */ if (!wsi->protocol) wsi->protocol = &wsi->vhost->protocols[0]; wsi->protocol->callback(wsi, LWS_CALLBACK_WSI_CREATE, wsi->user_space, NULL, 0); lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CONNECT_RESPONSE, AWAITING_TIMEOUT); iface = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_IFACE); if (iface) { n = lws_socket_bind(wsi->vhost, wsi->desc.sockfd, 0, iface); if (n < 0) { cce = "unable to bind socket"; goto failed; } } } #if defined(LWS_WITH_UNIX_SOCK) if (unix_skt) { psa = (const struct sockaddr *)&sau; n = sizeof(sau); } else #endif { #ifdef LWS_WITH_IPV6 if (wsi->ipv6) { sa46.sa6.sin6_port = htons(port); n = sizeof(struct sockaddr_in6); psa = (const struct sockaddr *)&sa46; } else #endif { sa46.sa4.sin_port = htons(port); n = sizeof(struct sockaddr); psa = (const struct sockaddr *)&sa46; } } if (connect(wsi->desc.sockfd, (const struct sockaddr *)psa, n) == -1 || LWS_ERRNO == LWS_EISCONN) { if (LWS_ERRNO == LWS_EALREADY || LWS_ERRNO == LWS_EINPROGRESS || LWS_ERRNO == LWS_EWOULDBLOCK #ifdef _WIN32 || LWS_ERRNO == WSAEINVAL #endif ) { lwsl_client("nonblocking connect retry (errno = %d)\n", LWS_ERRNO); if (lws_plat_check_connection_error(wsi)) { cce = "socket connect failed"; goto failed; } /* * must do specifically a POLLOUT poll to hear * about the connect completion */ if (lws_change_pollfd(wsi, 0, LWS_POLLOUT)) { cce = "POLLOUT set failed"; goto failed; } return wsi; } if (LWS_ERRNO != LWS_EISCONN) { lwsl_notice("Connect failed errno=%d\n", LWS_ERRNO); cce = "connect failed"; goto failed; } } lwsl_client("connected\n"); #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) /* we are connected to server, or proxy */ /* http proxy */ if (wsi->vhost->http.http_proxy_port) { /* * OK from now on we talk via the proxy, so connect to that * * (will overwrite existing pointer, * leaving old string/frag there but unreferenced) */ if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS, wsi->vhost->http.http_proxy_address)) goto failed; wsi->c_port = wsi->vhost->http.http_proxy_port; n = send(wsi->desc.sockfd, (char *)pt->serv_buf, (int)plen, MSG_NOSIGNAL); if (n < 0) { lwsl_debug("ERROR writing to proxy socket\n"); cce = "proxy write failed"; goto failed; } lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_PROXY_RESPONSE, AWAITING_TIMEOUT); lwsi_set_state(wsi, LRS_WAITING_PROXY_REPLY); return wsi; } #endif #if defined(LWS_WITH_SOCKS5) /* socks proxy */ else if (wsi->vhost->socks_proxy_port) { n = send(wsi->desc.sockfd, (char *)pt->serv_buf, plen, MSG_NOSIGNAL); if (n < 0) { lwsl_debug("ERROR writing socks greeting\n"); cce = "socks write failed"; goto failed; } lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SOCKS_GREETING_REPLY, AWAITING_TIMEOUT); lwsi_set_state(wsi, LRS_WAITING_SOCKS_GREETING_REPLY); return wsi; } #endif #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) send_hs: if (wsi_piggyback && !lws_dll_is_null(&wsi->dll_client_transaction_queue)) { /* * We are pipelining on an already-established connection... * we can skip tls establishment. */ lwsi_set_state(wsi, LRS_H1C_ISSUE_HANDSHAKE2); /* * we can't send our headers directly, because they have to * be sent when the parent is writeable. The parent will check * for anybody on his client transaction queue that is in * LRS_H1C_ISSUE_HANDSHAKE2, and let them write. * * If we are trying to do this too early, before the master * connection has written his own headers, then it will just * wait in the queue until it's possible to send them. */ lws_callback_on_writable(wsi_piggyback); lwsl_info("%s: wsi %p: waiting to send headers (parent state %x)\n", __func__, wsi, lwsi_state(wsi_piggyback)); } else { lwsl_info("%s: wsi %p: client creating own connection\n", __func__, wsi); /* we are making our own connection */ lwsi_set_state(wsi, LRS_H1C_ISSUE_HANDSHAKE); /* * provoke service to issue the handshake directly. * * we need to do it this way because in the proxy case, this is * the next state and executed only if and when we get a good * proxy response inside the state machine... but notice in * SSL case this may not have sent anything yet with 0 return, * and won't until many retries from main loop. To stop that * becoming endless, cover with a timeout. */ lws_set_timeout(wsi, PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE, AWAITING_TIMEOUT); pfd.fd = wsi->desc.sockfd; pfd.events = LWS_POLLIN; pfd.revents = LWS_POLLIN; n = lws_service_fd(context, &pfd); if (n < 0) { cce = "first service failed"; goto failed; } if (n) /* returns 1 on failure after closing wsi */ return NULL; } #endif return wsi; oom4: if (lwsi_role_client(wsi) /* && lwsi_state_est(wsi) */) { wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_CONNECTION_ERROR, wsi->user_space, (void *)cce, strlen(cce)); wsi->already_did_cce = 1; } /* take care that we might be inserted in fds already */ if (wsi->position_in_fds_table != LWS_NO_FDS_POS) goto failed1; /* * We can't be an active client connection any more, if we thought * that was what we were going to be doing. It should be if we are * failing by oom4 path, we are still called by * lws_client_connect_via_info() and will be returning NULL to that, * so nobody else should have had a chance to queue on us. */ { struct lws_vhost *vhost = wsi->vhost; lws_vhost_lock(vhost); __lws_free_wsi(wsi); lws_vhost_unlock(vhost); } return NULL; failed: wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_CONNECTION_ERROR, wsi->user_space, (void *)cce, strlen(cce)); wsi->already_did_cce = 1; failed1: lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "client_connect2"); return NULL; }
LWS_VISIBLE int lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct lws_ssl_info *si; #ifdef LWS_WITH_CGI struct lws_cgi_args *args; #endif #if defined(LWS_WITH_CGI) || defined(LWS_WITH_HTTP_PROXY) char buf[8192]; int n; #endif #if defined(LWS_WITH_HTTP_PROXY) unsigned char **p, *end; struct lws *parent; #endif switch (reason) { #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) case LWS_CALLBACK_HTTP: #ifndef LWS_NO_SERVER if (lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL)) return -1; if (lws_http_transaction_completed(wsi)) #endif return -1; break; #if !defined(LWS_NO_SERVER) case LWS_CALLBACK_HTTP_BODY_COMPLETION: case LWS_CALLBACK_HTTP_FILE_COMPLETION: if (lws_http_transaction_completed(wsi)) return -1; break; #endif case LWS_CALLBACK_HTTP_WRITEABLE: #ifdef LWS_WITH_CGI if (wsi->reason_bf & (LWS_CB_REASON_AUX_BF__CGI_HEADERS | LWS_CB_REASON_AUX_BF__CGI)) { n = lws_cgi_write_split_stdout_headers(wsi); if (n < 0) { lwsl_debug("AUX_BF__CGI forcing close\n"); return -1; } if (!n) lws_rx_flow_control( wsi->http.cgi->stdwsi[LWS_STDOUT], 1); if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__CGI_HEADERS) wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__CGI_HEADERS; else wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__CGI; if (wsi->http.cgi && wsi->http.cgi->cgi_transaction_over) return -1; break; } if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__CGI_CHUNK_END) { if (!wsi->http2_substream) { memcpy(buf + LWS_PRE, "0\x0d\x0a\x0d\x0a", 5); lwsl_debug("writing chunk term and exiting\n"); n = lws_write(wsi, (unsigned char *)buf + LWS_PRE, 5, LWS_WRITE_HTTP); } else n = lws_write(wsi, (unsigned char *)buf + LWS_PRE, 0, LWS_WRITE_HTTP_FINAL); /* always close after sending it */ return -1; } #endif #if defined(LWS_WITH_HTTP_PROXY) if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__PROXY_HEADERS) { wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__PROXY_HEADERS; lwsl_debug("%s: %p: issuing proxy headers\n", __func__, wsi); n = lws_write(wsi, wsi->http.pending_return_headers + LWS_PRE, wsi->http.pending_return_headers_len, LWS_WRITE_HTTP_HEADERS); lws_free_set_NULL(wsi->http.pending_return_headers); if (n < 0) { lwsl_err("%s: EST_CLIENT_HTTP: write failed\n", __func__); return -1; } lws_callback_on_writable(wsi); break; } if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__PROXY) { char *px = buf + LWS_PRE; int lenx = sizeof(buf) - LWS_PRE - 32; /* * our sink is writeable and our source has something * to read. So read a lump of source material of * suitable size to send or what's available, whichever * is the smaller. */ wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__PROXY; if (!lws_get_child(wsi)) break; /* this causes LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ */ if (lws_http_client_read(lws_get_child(wsi), &px, &lenx) < 0) { lwsl_info("%s: LWS_CB_REASON_AUX_BF__PROXY: " "client closed\n", __func__); stream_close(wsi); return -1; } break; } if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__PROXY_TRANS_END) { lwsl_info("%s: LWS_CB_REASON_AUX_BF__PROXY_TRANS_END\n", __func__); wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__PROXY_TRANS_END; if (stream_close(wsi)) return -1; if (lws_http_transaction_completed(wsi)) return -1; } #endif break; #if defined(LWS_WITH_HTTP_PROXY) case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: assert(lws_get_parent(wsi)); if (!lws_get_parent(wsi)) break; lws_get_parent(wsi)->reason_bf |= LWS_CB_REASON_AUX_BF__PROXY; lws_callback_on_writable(lws_get_parent(wsi)); break; case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: { char *out = buf + LWS_PRE; assert(lws_get_parent(wsi)); if (wsi->http.proxy_parent_chunked) { if (len > sizeof(buf) - LWS_PRE - 16) { lwsl_err("oversize buf %d %d\n", (int)len, (int)sizeof(buf) - LWS_PRE - 16); return -1; } /* * this only needs dealing with on http/1.1 to allow * pipelining */ n = lws_snprintf(out, 14, "%X\x0d\x0a", (int)len); out += n; memcpy(out, in, len); out += len; *out++ = '\x0d'; *out++ = '\x0a'; n = lws_write(lws_get_parent(wsi), (unsigned char *)buf + LWS_PRE, len + n + 2, LWS_WRITE_HTTP); } else n = lws_write(lws_get_parent(wsi), (unsigned char *)in, len, LWS_WRITE_HTTP); if (n < 0) return -1; break; } /* this handles the proxy case... */ case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: { unsigned char *start, *p, *end; /* * We want to proxy these headers, but we are being called * at the point the onward client was established, which is * unrelated to the state or writability of our proxy * connection. * * Therefore produce the headers using the onward client ah * while we have it, and stick them on the output buflist to be * written on the proxy connection as soon as convenient. */ parent = lws_get_parent(wsi); if (!parent) return 0; start = p = (unsigned char *)buf + LWS_PRE; end = p + sizeof(buf) - LWS_PRE - 256; if (lws_add_http_header_status(lws_get_parent(wsi), lws_http_client_http_response(wsi), &p, end)) return 1; /* * copy these headers from the client connection to the parent */ proxy_header(parent, wsi, end, 256, WSI_TOKEN_HTTP_CONTENT_LENGTH, &p, end); proxy_header(parent, wsi, end, 256, WSI_TOKEN_HTTP_CONTENT_TYPE, &p, end); proxy_header(parent, wsi, end, 256, WSI_TOKEN_HTTP_ETAG, &p, end); proxy_header(parent, wsi, end, 256, WSI_TOKEN_HTTP_ACCEPT_LANGUAGE, &p, end); proxy_header(parent, wsi, end, 256, WSI_TOKEN_HTTP_CONTENT_ENCODING, &p, end); proxy_header(parent, wsi, end, 256, WSI_TOKEN_HTTP_CACHE_CONTROL, &p, end); if (!parent->http2_substream) if (lws_add_http_header_by_token(parent, WSI_TOKEN_CONNECTION, (unsigned char *)"close", 5, &p, end)) return -1; /* * We proxy using h1 only atm, and strip any chunking so it * can go back out on h2 just fine. * * However if we are actually going out on h1, we need to add * our own chunking since we still don't know the size. */ if (!parent->http2_substream && !lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) { lwsl_debug("downstream parent chunked\n"); if (lws_add_http_header_by_token(parent, WSI_TOKEN_HTTP_TRANSFER_ENCODING, (unsigned char *)"chunked", 7, &p, end)) return -1; wsi->http.proxy_parent_chunked = 1; } if (lws_finalize_http_header(parent, &p, end)) return 1; parent->http.pending_return_headers_len = lws_ptr_diff(p, start); parent->http.pending_return_headers = lws_malloc(parent->http.pending_return_headers_len + LWS_PRE, "return proxy headers"); if (!parent->http.pending_return_headers) return -1; memcpy(parent->http.pending_return_headers + LWS_PRE, start, parent->http.pending_return_headers_len); parent->reason_bf |= LWS_CB_REASON_AUX_BF__PROXY_HEADERS; lwsl_debug("%s: LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: " "prepared headers\n", __func__); lws_callback_on_writable(parent); break; } case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: lwsl_info("%s: COMPLETED_CLIENT_HTTP: %p (parent %p)\n", __func__, wsi, lws_get_parent(wsi)); if (!lws_get_parent(wsi)) break; lws_get_parent(wsi)->reason_bf |= LWS_CB_REASON_AUX_BF__PROXY_TRANS_END; lws_callback_on_writable(lws_get_parent(wsi)); break; case LWS_CALLBACK_CLOSED_CLIENT_HTTP: if (!lws_get_parent(wsi)) break; lwsl_err("%s: LWS_CALLBACK_CLOSED_CLIENT_HTTP\n", __func__); lws_set_timeout(lws_get_parent(wsi), LWS_TO_KILL_ASYNC, PENDING_TIMEOUT_KILLED_BY_PROXY_CLIENT_CLOSE); break; case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER: parent = lws_get_parent(wsi); if (!parent) break; p = (unsigned char **)in; end = (*p) + len; /* * copy these headers from the parent request to the client * connection's request */ proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf), WSI_TOKEN_HOST, p, end); proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf), WSI_TOKEN_HTTP_ETAG, p, end); proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf), WSI_TOKEN_HTTP_IF_MODIFIED_SINCE, p, end); proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf), WSI_TOKEN_HTTP_ACCEPT_LANGUAGE, p, end); proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf), WSI_TOKEN_HTTP_ACCEPT_ENCODING, p, end); proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf), WSI_TOKEN_HTTP_CACHE_CONTROL, p, end); buf[0] = '\0'; lws_get_peer_simple(parent, buf, sizeof(buf)); if (lws_add_http_header_by_token(wsi, WSI_TOKEN_X_FORWARDED_FOR, (unsigned char *)buf, (int)strlen(buf), p, end)) return -1; break; #endif #ifdef LWS_WITH_CGI /* CGI IO events (POLLIN/OUT) appear here, our default policy is: * * - POST data goes on subprocess stdin * - subprocess stdout goes on http via writeable callback * - subprocess stderr goes to the logs */ case LWS_CALLBACK_CGI: args = (struct lws_cgi_args *)in; switch (args->ch) { /* which of stdin/out/err ? */ case LWS_STDIN: /* TBD stdin rx flow control */ break; case LWS_STDOUT: /* quench POLLIN on STDOUT until MASTER got writeable */ lws_rx_flow_control(args->stdwsi[LWS_STDOUT], 0); wsi->reason_bf |= LWS_CB_REASON_AUX_BF__CGI; /* when writing to MASTER would not block */ lws_callback_on_writable(wsi); break; case LWS_STDERR: n = lws_get_socket_fd(args->stdwsi[LWS_STDERR]); if (n < 0) break; n = read(n, buf, sizeof(buf) - 2); if (n > 0) { if (buf[n - 1] != '\n') buf[n++] = '\n'; buf[n] = '\0'; lwsl_notice("CGI-stderr: %s\n", buf); } break; } break; case LWS_CALLBACK_CGI_TERMINATED: lwsl_debug("LWS_CALLBACK_CGI_TERMINATED: %d %" PRIu64 "\n", wsi->http.cgi->explicitly_chunked, (uint64_t)wsi->http.cgi->content_length); if (!wsi->http.cgi->explicitly_chunked && !wsi->http.cgi->content_length) { /* send terminating chunk */ lwsl_debug("LWS_CALLBACK_CGI_TERMINATED: ending\n"); wsi->reason_bf |= LWS_CB_REASON_AUX_BF__CGI_CHUNK_END; lws_callback_on_writable(wsi); lws_set_timeout(wsi, PENDING_TIMEOUT_CGI, 3); break; } return -1; case LWS_CALLBACK_CGI_STDIN_DATA: /* POST body for stdin */ args = (struct lws_cgi_args *)in; args->data[args->len] = '\0'; if (!args->stdwsi[LWS_STDIN]) return -1; n = lws_get_socket_fd(args->stdwsi[LWS_STDIN]); if (n < 0) return -1; #if defined(LWS_WITH_ZLIB) if (wsi->http.cgi->gzip_inflate) { /* gzip handling */ if (!wsi->http.cgi->gzip_init) { lwsl_info("inflating gzip\n"); memset(&wsi->http.cgi->inflate, 0, sizeof(wsi->http.cgi->inflate)); if (inflateInit2(&wsi->http.cgi->inflate, 16 + 15) != Z_OK) { lwsl_err("%s: iniflateInit failed\n", __func__); return -1; } wsi->http.cgi->gzip_init = 1; } wsi->http.cgi->inflate.next_in = args->data; wsi->http.cgi->inflate.avail_in = args->len; do { wsi->http.cgi->inflate.next_out = wsi->http.cgi->inflate_buf; wsi->http.cgi->inflate.avail_out = sizeof(wsi->http.cgi->inflate_buf); n = inflate(&wsi->http.cgi->inflate, Z_SYNC_FLUSH); switch (n) { case Z_NEED_DICT: case Z_STREAM_ERROR: case Z_DATA_ERROR: case Z_MEM_ERROR: inflateEnd(&wsi->http.cgi->inflate); wsi->http.cgi->gzip_init = 0; lwsl_err("zlib error inflate %d\n", n); return -1; } if (wsi->http.cgi->inflate.avail_out != sizeof(wsi->http.cgi->inflate_buf)) { int written; written = write(args->stdwsi[LWS_STDIN]->desc.filefd, wsi->http.cgi->inflate_buf, sizeof(wsi->http.cgi->inflate_buf) - wsi->http.cgi->inflate.avail_out); if (written != (int)( sizeof(wsi->http.cgi->inflate_buf) - wsi->http.cgi->inflate.avail_out)) { lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA: " "sent %d only %d went", n, args->len); } if (n == Z_STREAM_END) { lwsl_err("gzip inflate end\n"); inflateEnd(&wsi->http.cgi->inflate); wsi->http.cgi->gzip_init = 0; break; } } else break; if (wsi->http.cgi->inflate.avail_out) break; } while (1); return args->len; } #endif /* WITH_ZLIB */ n = write(n, args->data, args->len); // lwsl_hexdump_notice(args->data, args->len); if (n < args->len) lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA: " "sent %d only %d went", n, args->len); if (wsi->http.cgi->post_in_expected && args->stdwsi[LWS_STDIN] && args->stdwsi[LWS_STDIN]->desc.filefd > 0) { wsi->http.cgi->post_in_expected -= n; if (!wsi->http.cgi->post_in_expected) { struct lws *siwsi = args->stdwsi[LWS_STDIN]; lwsl_debug("%s: expected POST in end: " "closing stdin wsi %p, fd %d\n", __func__, siwsi, siwsi->desc.sockfd); __remove_wsi_socket_from_fds(siwsi); lwsi_set_state(siwsi, LRS_DEAD_SOCKET); siwsi->socket_is_permanently_unusable = 1; lws_remove_child_from_any_parent(siwsi); if (wsi->context->event_loop_ops-> close_handle_manually) { wsi->context->event_loop_ops-> close_handle_manually(siwsi); siwsi->told_event_loop_closed = 1; } else { compatible_close(siwsi->desc.sockfd); __lws_free_wsi(siwsi); } wsi->http.cgi->pipe_fds[LWS_STDIN][1] = -1; args->stdwsi[LWS_STDIN] = NULL; } } return n; #endif /* WITH_CGI */ #endif /* ROLE_ H1 / H2 */ case LWS_CALLBACK_SSL_INFO: si = in; (void)si; lwsl_notice("LWS_CALLBACK_SSL_INFO: where: 0x%x, ret: 0x%x\n", si->where, si->ret); break; #if LWS_MAX_SMP > 1 case LWS_CALLBACK_GET_THREAD_ID: return (int)(unsigned long long)pthread_self(); #endif default: break; } return 0; }
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; }