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_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; }