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; }
static int callback_dynamic_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct pss *pss = (struct pss *)user; uint8_t buf[LWS_PRE + 2048], *start = &buf[LWS_PRE], *p = start, *end = &buf[sizeof(buf) - LWS_PRE - 1]; time_t t; int n; switch (reason) { case LWS_CALLBACK_HTTP: /* in contains the url part after our mountpoint /dyn, if any */ lws_snprintf(pss->path, sizeof(pss->path), "%s", (const char *)in); /* * prepare and write http headers... with regards to content- * length, there are three approaches: * * - http/1.0 or connection:close: no need, but no pipelining * - http/1.1 or connected:keep-alive * (keep-alive is default for 1.1): content-length required * - http/2: no need, LWS_WRITE_HTTP_FINAL closes the stream * * giving the api below LWS_ILLEGAL_HTTP_CONTENT_LEN instead of * a content length forces the connection response headers to * send back "connection: close", disabling keep-alive. * * If you know the final content-length, it's always OK to give * it and keep-alive can work then if otherwise possible. But * often you don't know it and avoiding having to compute it * at header-time makes life easier at the server. */ if (lws_add_http_common_headers(wsi, HTTP_STATUS_OK, "text/html", LWS_ILLEGAL_HTTP_CONTENT_LEN, /* no content len */ &p, end)) return 1; if (lws_finalize_write_http_header(wsi, start, &p, end)) return 1; pss->times = 0; pss->budget = atoi((char *)in + 1); pss->content_lines = 0; if (!pss->budget) pss->budget = 10; /* write the body separately */ lws_callback_on_writable(wsi); return 0; case LWS_CALLBACK_HTTP_WRITEABLE: if (!pss || pss->times > pss->budget) break; /* * We send a large reply in pieces of around 2KB each. * * For http/1, it's possible to send a large buffer at once, * but lws will malloc() up a temp buffer to hold any data * that the kernel didn't accept in one go. This is expensive * in memory and cpu, so it's better to stage the creation of * the data to be sent each time. * * For http/2, large data frames would block the whole * connection, not just the stream and are not allowed. Lws * will call back on writable when the stream both has transmit * credit and the round-robin fair access for sibling streams * allows it. * * For http/2, we must send the last part with * LWS_WRITE_HTTP_FINAL to close the stream representing * this transaction. */ n = LWS_WRITE_HTTP; if (pss->times == pss->budget) n = LWS_WRITE_HTTP_FINAL; if (!pss->times) { /* * the first time, we print some html title */ t = time(NULL); /* * to work with http/2, we must take care about LWS_PRE * valid behind the buffer we will send. */ p += lws_snprintf((char *)p, end - p, "<html>" "<img src=\"/libwebsockets.org-logo.png\">" "<br>Dynamic content for '%s' from mountpoint." "<br>Time: %s<br><br>" "</html>", pss->path, ctime(&t)); } else { /* * after the first time, we create bulk content. * * Again we take care about LWS_PRE valid behind the * buffer we will send. */ while (lws_ptr_diff(end, p) > 80) p += lws_snprintf((char *)p, end - p, "%d.%d: this is some content... ", pss->times, pss->content_lines++); p += lws_snprintf((char *)p, end - p, "<br><br>"); } pss->times++; if (lws_write(wsi, (uint8_t *)start, lws_ptr_diff(p, start), n) != lws_ptr_diff(p, start)) return 1; /* * HTTP/1.0 no keepalive: close network connection * HTTP/1.1 or HTTP1.0 + KA: wait / process next transaction * HTTP/2: stream ended, parent connection remains up */ if (n == LWS_WRITE_HTTP_FINAL) { if (lws_http_transaction_completed(wsi)) return -1; } else lws_callback_on_writable(wsi); return 0; default: break; } return lws_callback_http_dummy(wsi, reason, user, in, len); }
static int callback_acme_client(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct per_vhost_data__lws_acme_client *vhd = (struct per_vhost_data__lws_acme_client *) lws_protocol_vh_priv_get(lws_get_vhost(wsi), lws_get_protocol(wsi)); char buf[LWS_PRE + 2536], *start = buf + LWS_PRE, *p = start, *end = buf + sizeof(buf) - 1, digest[32], *failreason = NULL; unsigned char **pp, *pend; const char *content_type; const struct lws_protocol_vhost_options *pvo; struct lws_acme_cert_aging_args *caa; struct acme_connection *ac = NULL; struct lws_genhash_ctx hctx; struct lws *cwsi; int n, m; if (vhd) ac = vhd->ac; switch ((int)reason) { case LWS_CALLBACK_PROTOCOL_INIT: vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi), lws_get_protocol(wsi), sizeof(struct per_vhost_data__lws_acme_client)); vhd->context = lws_get_context(wsi); vhd->protocol = lws_get_protocol(wsi); vhd->vhost = lws_get_vhost(wsi); /* compute how much we need to hold all the pvo payloads */ m = 0; pvo = (const struct lws_protocol_vhost_options *)in; while (pvo) { m += strlen(pvo->value) + 1; pvo = pvo->next; } p = vhd->pvo_data = malloc(m); if (!p) return -1; pvo = (const struct lws_protocol_vhost_options *)in; while (pvo) { start = p; n = strlen(pvo->value) + 1; memcpy(start, pvo->value, n); p += n; for (m = 0; m < (int)LWS_ARRAY_SIZE(pvo_names); m++) if (!strcmp(pvo->name, pvo_names[m])) vhd->pvop[m] = start; pvo = pvo->next; } n = 0; for (m = 0; m < (int)LWS_ARRAY_SIZE(pvo_names); m++) if (!vhd->pvop[m] && m >= LWS_TLS_REQ_ELEMENT_COMMON_NAME) { lwsl_notice("%s: require pvo '%s'\n", __func__, pvo_names[m]); n |= 1; } else if (vhd->pvop[m]) lwsl_info(" %s: %s\n", pvo_names[m], vhd->pvop[m]); if (n) { free(vhd->pvo_data); vhd->pvo_data = NULL; return -1; } #if !defined(LWS_WITH_ESP32) /* * load (or create) the registration keypair while we * still have root */ if (lws_acme_load_create_auth_keys(vhd, 4096)) return 1; /* * in case we do an update, open the update files while we * still have root */ lws_snprintf(buf, sizeof(buf) - 1, "%s.upd", vhd->pvop[LWS_TLS_SET_CERT_PATH]); vhd->fd_updated_cert = lws_open(buf, LWS_O_WRONLY | LWS_O_CREAT | LWS_O_TRUNC, 0600); if (vhd->fd_updated_cert < 0) { lwsl_err("unable to create update cert file %s\n", buf); return -1; } lws_snprintf(buf, sizeof(buf) - 1, "%s.upd", vhd->pvop[LWS_TLS_SET_KEY_PATH]); vhd->fd_updated_key = lws_open(buf, LWS_O_WRONLY | LWS_O_CREAT | LWS_O_TRUNC, 0600); if (vhd->fd_updated_key < 0) { lwsl_err("unable to create update key file %s\n", buf); return -1; } #endif break; case LWS_CALLBACK_PROTOCOL_DESTROY: if (vhd && vhd->pvo_data) { free(vhd->pvo_data); vhd->pvo_data = NULL; } if (vhd) lws_acme_finished(vhd); break; case LWS_CALLBACK_VHOST_CERT_AGING: if (!vhd) break; caa = (struct lws_acme_cert_aging_args *)in; /* * Somebody is telling us about a cert some vhost is using. * * First see if the cert is getting close enough to expiry that * we *want* to do something about it. */ if ((int)(ssize_t)len > 14) break; /* * ...is this a vhost we were configured on? */ if (vhd->vhost != caa->vh) return 1; for (n = 0; n < (int)LWS_ARRAY_SIZE(vhd->pvop);n++) if (caa->element_overrides[n]) vhd->pvop_active[n] = caa->element_overrides[n]; else vhd->pvop_active[n] = vhd->pvop[n]; lwsl_notice("starting acme acquisition on %s: %s\n", lws_get_vhost_name(caa->vh), vhd->pvop_active[LWS_TLS_SET_DIR_URL]); lws_acme_start_acquisition(vhd, caa->vh); break; /* * Client */ case LWS_CALLBACK_CLIENT_ESTABLISHED: lwsl_notice("%s: CLIENT_ESTABLISHED\n", __func__); break; case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: lwsl_notice("%s: CLIENT_CONNECTION_ERROR: %p\n", __func__, wsi); break; case LWS_CALLBACK_CLOSED_CLIENT_HTTP: lwsl_notice("%s: CLOSED_CLIENT_HTTP: %p\n", __func__, wsi); break; case LWS_CALLBACK_CLOSED: lwsl_notice("%s: CLOSED: %p\n", __func__, wsi); break; case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: lwsl_notice("lws_http_client_http_response %d\n", lws_http_client_http_response(wsi)); if (!ac) break; ac->resp = lws_http_client_http_response(wsi); /* we get a new nonce each time */ if (lws_hdr_total_length(wsi, WSI_TOKEN_REPLAY_NONCE) && lws_hdr_copy(wsi, ac->replay_nonce, sizeof(ac->replay_nonce), WSI_TOKEN_REPLAY_NONCE) < 0) { lwsl_notice("%s: nonce too large\n", __func__); goto failed; } switch (ac->state) { case ACME_STATE_DIRECTORY: lejp_construct(&ac->jctx, cb_dir, vhd, jdir_tok, LWS_ARRAY_SIZE(jdir_tok)); break; case ACME_STATE_NEW_REG: break; case ACME_STATE_NEW_AUTH: lejp_construct(&ac->jctx, cb_authz, ac, jauthz_tok, LWS_ARRAY_SIZE(jauthz_tok)); break; case ACME_STATE_POLLING: case ACME_STATE_ACCEPT_CHALL: lejp_construct(&ac->jctx, cb_chac, ac, jchac_tok, LWS_ARRAY_SIZE(jchac_tok)); break; case ACME_STATE_POLLING_CSR: ac->cpos = 0; if (ac->resp != 201) break; /* * He acknowledges he will create the cert... * get the URL to GET it from in the Location * header. */ if (lws_hdr_copy(wsi, ac->challenge_uri, sizeof(ac->challenge_uri), WSI_TOKEN_HTTP_LOCATION) < 0) { lwsl_notice("%s: missing cert location:\n", __func__); goto failed; } lwsl_notice("told to fetch cert from %s\n", ac->challenge_uri); break; default: break; } break; case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER: if (!ac) break; switch (ac->state) { case ACME_STATE_DIRECTORY: break; case ACME_STATE_NEW_REG: p += lws_snprintf(p, end - p, "{" "\"resource\":\"new-reg\"," "\"contact\":[" "\"mailto:%s\"" "],\"agreement\":\"%s\"" "}", vhd->pvop_active[LWS_TLS_REQ_ELEMENT_EMAIL], ac->urls[JAD_TOS_URL]); puts(start); pkt_add_hdrs: ac->len = lws_jws_create_packet(&vhd->jwk, start, p - start, ac->replay_nonce, &ac->buf[LWS_PRE], sizeof(ac->buf) - LWS_PRE); if (ac->len < 0) { ac->len = 0; lwsl_notice("lws_jws_create_packet failed\n"); goto failed; } pp = (unsigned char **)in; pend = (*pp) + len; ac->pos = 0; content_type = "application/jose+json"; if (ac->state == ACME_STATE_POLLING_CSR) content_type = "application/pkix-cert"; if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (uint8_t *)content_type, 21, pp, pend)) { lwsl_notice("could not add content type\n"); goto failed; } n = sprintf(buf, "%d", ac->len); if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH, (uint8_t *)buf, n, pp, pend)) { lwsl_notice("could not add content length\n"); goto failed; } lws_client_http_body_pending(wsi, 1); lws_callback_on_writable(wsi); lwsl_notice("prepare to send ACME_STATE_NEW_REG\n"); break; case ACME_STATE_NEW_AUTH: p += lws_snprintf(p, end - p, "{" "\"resource\":\"new-authz\"," "\"identifier\":{" "\"type\":\"http-01\"," "\"value\":\"%s\"" "}" "}", vhd->pvop_active[LWS_TLS_REQ_ELEMENT_COMMON_NAME]); goto pkt_add_hdrs; case ACME_STATE_ACCEPT_CHALL: /* * Several of the challenges in this document makes use * of a key authorization string. A key authorization * expresses a domain holder's authorization for a * specified key to satisfy a specified challenge, by * concatenating the token for the challenge with a key * fingerprint, separated by a "." character: * * key-authz = token || '.' || * base64(JWK_Thumbprint(accountKey)) * * The "JWK_Thumbprint" step indicates the computation * specified in [RFC7638], using the SHA-256 digest. As * specified in the individual challenges below, the * token for a challenge is a JSON string comprised * entirely of characters in the base64 alphabet. * The "||" operator indicates concatenation of strings. * * keyAuthorization (required, string): The key * authorization for this challenge. This value MUST * match the token from the challenge and the client's * account key. * * draft acme-01 tls-sni-01: * * { * "keyAuthorization": "evaGxfADs...62jcerQ", * } (Signed as JWS) * * draft acme-07 tls-sni-02: * * POST /acme/authz/1234/1 * Host: example.com * Content-Type: application/jose+json * * { * "protected": base64url({ * "alg": "ES256", * "kid": "https://example.com/acme/acct/1", * "nonce": "JHb54aT_KTXBWQOzGYkt9A", * "url": "https://example.com/acme/authz/1234/1" * }), * "payload": base64url({ * "keyAuthorization": "evaGxfADs...62jcerQ" * }), * "signature": "Q1bURgJoEslbD1c5...3pYdSMLio57mQNN4" * } * * On receiving a response, the server MUST verify that * the key authorization in the response matches the * "token" value in the challenge and the client's * account key. If they do not match, then the server * MUST return an HTTP error in response to the POST * request in which the client sent the challenge. */ lws_jwk_rfc7638_fingerprint(&vhd->jwk, digest); p = start; end = &buf[sizeof(buf) - 1]; p += lws_snprintf(p, end - p, "{\"resource\":\"challenge\"," "\"type\":\"tls-sni-0%d\"," "\"keyAuthorization\":\"%s.", 1 + ac->is_sni_02, ac->chall_token); n = lws_jws_base64_enc(digest, 32, p, end - p); if (n < 0) goto failed; p += n; p += lws_snprintf(p, end - p, "\"}"); puts(start); goto pkt_add_hdrs; case ACME_STATE_POLLING: break; case ACME_STATE_POLLING_CSR: /* * "To obtain a certificate for the domain, the agent * constructs a PKCS#10 Certificate Signing Request that * asks the Let’s Encrypt CA to issue a certificate for * example.com with a specified public key. As usual, * the CSR includes a signature by the private key * corresponding to the public key in the CSR. The agent * also signs the whole CSR with the authorized * key for example.com so that the Let’s Encrypt CA * knows it’s authorized." * * IOW we must create a new RSA keypair which will be * the cert public + private key, and put the public * key in the CSR. The CSR, just for transport, is also * signed with our JWK, showing that as the owner of the * authorized JWK, the request should be allowed. * * The cert comes back with our public key in it showing * that the owner of the matching private key (we * created that keypair) is the owner of the cert. * * We feed the CSR the elements we want in the cert, * like the CN etc, and it gives us the b64URL-encoded * CSR and the PEM-encoded (public +)private key in * memory buffers. */ if (ac->goes_around) break; p += lws_snprintf(p, end - p, "{\"resource\":\"new-cert\"," "\"csr\":\""); n = lws_tls_acme_sni_csr_create(vhd->context, &vhd->pvop_active[0], (uint8_t *)p, end - p, &ac->alloc_privkey_pem, &ac->len_privkey_pem); if (n < 0) { lwsl_notice("CSR generation failed\n"); goto failed; } p += n; p += lws_snprintf(p, end - p, "\"}"); puts(start); goto pkt_add_hdrs; default: break; } break; case LWS_CALLBACK_CLIENT_HTTP_WRITEABLE: lwsl_notice("LWS_CALLBACK_CLIENT_HTTP_WRITEABLE\n"); if (!ac) break; if (ac->pos == ac->len) break; ac->buf[LWS_PRE + ac->len] = '\0'; if (lws_write(wsi, (uint8_t *)ac->buf + LWS_PRE, ac->len, LWS_WRITE_HTTP_FINAL) < 0) return -1; lwsl_notice("wrote %d\n", ac->len); ac->pos = ac->len; lws_client_http_body_pending(wsi, 0); break; /* chunked content */ case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: if (!ac) return -1; switch (ac->state) { case ACME_STATE_POLLING: case ACME_STATE_ACCEPT_CHALL: case ACME_STATE_NEW_AUTH: case ACME_STATE_DIRECTORY: ((char *)in)[len] = '\0'; puts(in); m = (int)(signed char)lejp_parse(&ac->jctx, (uint8_t *)in, len); if (m < 0 && m != LEJP_CONTINUE) { lwsl_notice("lejp parse failed %d\n", m); goto failed; } break; case ACME_STATE_NEW_REG: ((char *)in)[len] = '\0'; puts(in); break; case ACME_STATE_POLLING_CSR: /* it should be the DER cert! */ if (ac->cpos + len > sizeof(ac->buf)) { lwsl_notice("Incoming cert is too large!\n"); goto failed; } memcpy(&ac->buf[ac->cpos], in, len); ac->cpos += len; break; default: break; } break; /* unchunked content */ case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: lwsl_notice("%s: LWS_CALLBACK_RECEIVE_CLIENT_HTTP\n", __func__); { char buffer[2048 + LWS_PRE]; char *px = buffer + LWS_PRE; int lenx = sizeof(buffer) - LWS_PRE; if (lws_http_client_read(wsi, &px, &lenx) < 0) return -1; } break; case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: lwsl_notice("%s: COMPLETED_CLIENT_HTTP\n", __func__); if (!ac) return -1; switch (ac->state) { case ACME_STATE_DIRECTORY: lejp_destruct(&ac->jctx); /* check dir validity */ for (n = 0; n < 6; n++) lwsl_notice(" %d: %s\n", n, ac->urls[n]); /* * So... having the directory now... we try to * register our keys next. It's OK if it ends up * they're already registered... this eliminates any * gaps where we stored the key but registration did * not complete for some reason... */ ac->state = ACME_STATE_NEW_REG; lws_acme_report_status(vhd->vhost, LWS_CUS_REG, NULL); strcpy(buf, ac->urls[JAD_NEW_REG_URL]); cwsi = lws_acme_client_connect(vhd->context, vhd->vhost, &ac->cwsi, &ac->i, buf, "POST"); if (!cwsi) { lwsl_notice("%s: failed to connect to acme\n", __func__); goto failed; } return -1; /* close the completed client connection */ case ACME_STATE_NEW_REG: if ((ac->resp >= 200 && ac->resp < 299) || ac->resp == 409) { /* * Our account already existed, or exists now. * * Move on to requesting a cert auth. */ ac->state = ACME_STATE_NEW_AUTH; lws_acme_report_status(vhd->vhost, LWS_CUS_AUTH, NULL); strcpy(buf, ac->urls[JAD_NEW_AUTHZ_URL]); cwsi = lws_acme_client_connect(vhd->context, vhd->vhost, &ac->cwsi, &ac->i, buf, "POST"); if (!cwsi) lwsl_notice("%s: failed to connect\n", __func__); return -1; /* close the completed client connection */ } else { lwsl_notice("new-reg replied %d\n", ac->resp); goto failed; } return -1; /* close the completed client connection */ case ACME_STATE_NEW_AUTH: lejp_destruct(&ac->jctx); if (ac->resp / 100 == 4) { lws_snprintf(buf, sizeof(buf), "Auth failed: %s", ac->detail); failreason = buf; lwsl_notice("auth failed\n"); goto failed; } lwsl_notice("chall: %s (%d)\n", ac->chall_token, ac->resp); if (!ac->chall_token[0]) { lwsl_notice("no challenge\n"); goto failed; } ac->state = ACME_STATE_ACCEPT_CHALL; lws_acme_report_status(vhd->vhost, LWS_CUS_CHALLENGE, NULL); /* tls-sni-01 ... what a mess. * The stuff in * https://tools.ietf.org/html/ * draft-ietf-acme-acme-01#section-7.3 * "requires" n but it's missing from let's encrypt * tls-sni-01 challenge. The go docs say that they just * implement one hashing round regardless * https://godoc.org/golang.org/x/crypto/acme * * The go way is what is actually implemented today by * letsencrypt * * "A client responds to this challenge by constructing * a key authorization from the "token" value provided * in the challenge and the client's account key. The * client first computes the SHA-256 digest Z0 of the * UTF8-encoded key authorization, and encodes Z0 in * UTF-8 lower-case hexadecimal form." */ /* tls-sni-02 * * SAN A MUST be constructed as follows: compute the * SHA-256 digest of the UTF-8-encoded challenge token * and encode it in lowercase hexadecimal form. The * dNSName is "x.y.token.acme.invalid", where x * is the first half of the hexadecimal representation * and y is the second half. */ memset(&ac->ci, 0, sizeof(ac->ci)); /* first compute the key authorization */ lws_jwk_rfc7638_fingerprint(&vhd->jwk, digest); p = start; end = &buf[sizeof(buf) - 1]; p += lws_snprintf(p, end - p, "%s.", ac->chall_token); n = lws_jws_base64_enc(digest, 32, p, end - p); if (n < 0) goto failed; p += n; if (lws_genhash_init(&hctx, LWS_GENHASH_TYPE_SHA256)) return -1; if (lws_genhash_update(&hctx, (uint8_t *)start, lws_ptr_diff(p, start))) { lws_genhash_destroy(&hctx, NULL); return -1; } if (lws_genhash_destroy(&hctx, digest)) return -1; p = buf; for (n = 0; n < 32; n++) { p += lws_snprintf(p, end - p, "%02x", digest[n] & 0xff); if (n == (32 / 2) - 1) p = buf + 64; } p = ac->san_a; if (ac->is_sni_02) { lws_snprintf(p, sizeof(ac->san_a), "%s.%s.token.acme.invalid", buf, buf + 64); /* * SAN B MUST be constructed as follows: compute * the SHA-256 digest of the UTF-8 encoded key * authorization and encode it in lowercase * hexadecimal form. The dNSName is * "x.y.ka.acme.invalid" where x is the first * half of the hexadecimal representation and y * is the second half. */ lws_jwk_rfc7638_fingerprint(&vhd->jwk, (char *)digest); p = buf; for (n = 0; n < 32; n++) { p += lws_snprintf(p, end - p, "%02x", digest[n] & 0xff); if (n == (32 / 2) - 1) p = buf + 64; } p = ac->san_b; lws_snprintf(p, sizeof(ac->san_b), "%s.%s.ka.acme.invalid", buf, buf + 64); } else { lws_snprintf(p, sizeof(ac->san_a), "%s.%s.acme.invalid", buf, buf + 64); ac->san_b[0] = '\0'; } lwsl_notice("san_a: '%s'\n", ac->san_a); lwsl_notice("san_b: '%s'\n", ac->san_b); /* * tls-sni-01: * * The client then configures the TLS server at the * domain such that when a handshake is initiated with * the Server Name Indication extension set to * "<Zi[0:32]>.<Zi[32:64]>.acme.invalid", the * corresponding generated certificate is presented. * * tls-sni-02: * * The client MUST ensure that the certificate is * served to TLS connections specifying a Server Name * Indication (SNI) value of SAN A. */ ac->ci.vhost_name = ac->san_a; /* * we bind to exact iface of real vhost, so we can * share the listen socket by SNI */ ac->ci.iface = ac->real_vh_iface; /* listen on the same port as the vhost that triggered * us */ ac->ci.port = ac->real_vh_port; /* Skip filling in any x509 info into the ssl_ctx. * It will be done at the callback * LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS * in this callback handler (below) */ ac->ci.options = LWS_SERVER_OPTION_CREATE_VHOST_SSL_CTX | LWS_SERVER_OPTION_SKIP_PROTOCOL_INIT | LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; /* make ourselves protocols[0] for the new vhost */ ac->ci.protocols = acme_protocols; /* * vhost .user points to the ac associated with the * temporary vhost */ ac->ci.user = ac; ac->vhost = lws_create_vhost(lws_get_context(wsi), &ac->ci); if (!ac->vhost) goto failed; /* * The challenge-specific vhost is up... let the ACME * server know we are ready to roll... */ ac->goes_around = 0; cwsi = lws_acme_client_connect(vhd->context, vhd->vhost, &ac->cwsi, &ac->i, ac->challenge_uri, "POST"); if (!cwsi) { lwsl_notice("%s: failed to connect\n", __func__); goto failed; } return -1; /* close the completed client connection */ case ACME_STATE_ACCEPT_CHALL: /* * he returned something like this (which we parsed) * * { * "type": "tls-sni-01", * "status": "pending", * "uri": "https://acme-staging.api.letsencrypt.org/ * acme/challenge/xCt7bT3FaxoIQU3Qry87t5h * uKDcC-L-0ERcD5DLAZts/71100507", * "token": "j2Vs-vLI_dsza4A35SFHIU03aIe2PzFRijbqCY * dIVeE", * "keyAuthorization": "j2Vs-vLI_dsza4A35SFHIU03aIe2 * PzFRijbqCYdIVeE.nmOtdFd8Jikn6K8NnYYmT5 * vCM_PwSDT8nLdOYoFXhRU" * } * */ lwsl_notice("%s: COMPLETED accept chall: %s\n", __func__, ac->challenge_uri); poll_again: ac->state = ACME_STATE_POLLING; lws_acme_report_status(vhd->vhost, LWS_CUS_CHALLENGE, NULL); if (ac->goes_around++ == 20) { lwsl_notice("%s: too many chall retries\n", __func__); goto failed; } lws_timed_callback_vh_protocol(vhd->vhost, vhd->protocol, LWS_CALLBACK_USER + 0xac33, ac->goes_around == 1 ? 10 : 2); return -1; /* close the completed client connection */ case ACME_STATE_POLLING: if (ac->resp == 202 && strcmp(ac->status, "invalid") && strcmp(ac->status, "valid")) { lwsl_notice("status: %s\n", ac->status); goto poll_again; } if (!strcmp(ac->status, "invalid")) { lwsl_notice("%s: polling failed\n", __func__); lws_snprintf(buf, sizeof(buf), "Challenge Invalid: %s", ac->detail); failreason = buf; goto failed; } lwsl_notice("Challenge passed\n"); /* * The challenge was validated... so delete the * temp SNI vhost now its job is done */ if (ac->vhost) lws_vhost_destroy(ac->vhost); ac->vhost = NULL; /* * now our JWK is accepted as authorized to make * requests for the domain, next move is create the * CSR signed with the JWK, and send it to the ACME * server to request the actual certs. */ ac->state = ACME_STATE_POLLING_CSR; lws_acme_report_status(vhd->vhost, LWS_CUS_REQ, NULL); ac->goes_around = 0; strcpy(buf, ac->urls[JAD_NEW_CERT_URL]); cwsi = lws_acme_client_connect(vhd->context, vhd->vhost, &ac->cwsi, &ac->i, buf, "POST"); if (!cwsi) { lwsl_notice("%s: failed to connect to acme\n", __func__); goto failed; } return -1; /* close the completed client connection */ case ACME_STATE_POLLING_CSR: /* * (after POSTing the CSR)... * * If the CA decides to issue a certificate, then the * server creates a new certificate resource and * returns a URI for it in the Location header field * of a 201 (Created) response. * * HTTP/1.1 201 Created * Location: https://example.com/acme/cert/asdf * * If the certificate is available at the time of the * response, it is provided in the body of the response. * If the CA has not yet issued the certificate, the * body of this response will be empty. The client * should then send a GET request to the certificate URI * to poll for the certificate. As long as the * certificate is unavailable, the server MUST provide a * 202 (Accepted) response and include a Retry-After * header to indicate when the server believes the * certificate will be issued. */ if (ac->resp < 200 || ac->resp > 202) { lwsl_notice("CSR poll failed on resp %d\n", ac->resp); goto failed; } if (ac->resp == 200) { char *pp; int max; lwsl_notice("The cert was sent..\n"); lws_acme_report_status(vhd->vhost, LWS_CUS_ISSUE, NULL); /* * That means we have the issued cert DER in * ac->buf, length in ac->cpos; and the key in * ac->alloc_privkey_pem, length in * ac->len_privkey_pem. * * We write out a PEM copy of the cert, and a * PEM copy of the private key, using the * write-only fds we opened while we still * had root. * * Estimate the size of the PEM version of the * cert and allocate a temp buffer for it. * * This is a bit complicated because first we * drop the b64url version into the buffer at * +384, then we add the header at 0 and move * lines of it back + '\n' to make PEM. * * This avoids the need for two fullsize * allocations. */ max = (ac->cpos * 4) / 3 + 16 + 384; start = p = malloc(max); if (!p) goto failed; n = lws_b64_encode_string(ac->buf, ac->cpos, start + 384, max - 384); if (n < 0) { free(start); goto failed; } pp = start + 384; p += lws_snprintf(start, 64, "%s", "-----BEGIN CERTIFICATE-----\n"); while (n) { m = 65; if (n < m) m = n; memcpy(p, pp, m); n -= m; p += m; pp += m; if (n) *p++ = '\n'; } p += lws_snprintf(p, max - lws_ptr_diff(p, start), "%s", "\n-----END CERTIFICATE-----\n"); n = lws_plat_write_cert(vhd->vhost, 0, vhd->fd_updated_cert, start, lws_ptr_diff(p, start)); free(start); if (n) { lwsl_err("unable to write ACME cert! %d\n", n); goto failed; } /* * don't close it... we may update the certs * again */ if (lws_plat_write_cert(vhd->vhost, 1, vhd->fd_updated_key, ac->alloc_privkey_pem, ac->len_privkey_pem)) { lwsl_err("unable to write ACME key!\n"); goto failed; } /* * we have written the persistent copies */ lwsl_notice("%s: Updated certs written for %s " "to %s.upd and %s.upd\n", __func__, vhd->pvop_active[LWS_TLS_REQ_ELEMENT_COMMON_NAME], vhd->pvop_active[LWS_TLS_SET_CERT_PATH], vhd->pvop_active[LWS_TLS_SET_KEY_PATH]); /* notify lws there was a cert update */ if (lws_tls_cert_updated(vhd->context, vhd->pvop_active[LWS_TLS_SET_CERT_PATH], vhd->pvop_active[LWS_TLS_SET_KEY_PATH], ac->buf, ac->cpos, ac->alloc_privkey_pem, ac->len_privkey_pem)) { lwsl_notice("problem setting certs\n"); } lws_acme_finished(vhd); lws_acme_report_status(vhd->vhost, LWS_CUS_SUCCESS, NULL); return 0; } lws_acme_report_status(vhd->vhost, LWS_CUS_CONFIRM, NULL); /* he is preparing the cert, go again with a GET */ if (ac->goes_around++ == 30) { lwsl_notice("%s: too many retries\n", __func__); goto failed; } strcpy(buf, ac->challenge_uri); cwsi = lws_acme_client_connect(vhd->context, vhd->vhost, &ac->cwsi, &ac->i, buf, "GET"); if (!cwsi) { lwsl_notice("%s: failed to connect to acme\n", __func__); goto failed; } return -1; /* close the completed client connection */ default: break; } break; case LWS_CALLBACK_USER + 0xac33: if (!vhd) break; cwsi = lws_acme_client_connect(vhd->context, vhd->vhost, &ac->cwsi, &ac->i, ac->challenge_uri, "GET"); if (!cwsi) { lwsl_notice("%s: failed to connect\n", __func__); goto failed; } break; case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS: /* * This goes to vhost->protocols[0], but for our temp certs * vhost we created, we have arranged that to be our protocol, * so the callback will come here. * * When we created the temp vhost, we set its pvo to point * to the ac associated with the temp vhost. */ lwsl_debug("LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS\n"); ac = (struct acme_connection *)lws_get_vhost_user( (struct lws_vhost *)in); lws_acme_report_status((struct lws_vhost *)in, LWS_CUS_CREATE_REQ, "creating challenge cert"); if (lws_tls_acme_sni_cert_create((struct lws_vhost *)in, ac->san_a, ac->san_b)) { lwsl_err("%s: creating the sni test cert failed\n", __func__); return -1; } break; default: break; } return 0; failed: lwsl_err("%s: failed out\n", __func__); lws_acme_report_status(vhd->vhost, LWS_CUS_FAILED, failreason); lws_acme_finished(vhd); return -1; }
static int callback_post_demo(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct per_session_data__post_demo *pss = (struct per_session_data__post_demo *)user; unsigned char *buffer; unsigned char *p, *start, *end; int n; switch (reason) { case LWS_CALLBACK_HTTP_BODY: /* create the POST argument parser if not already existing */ if (!pss->spa) { pss->spa = lws_spa_create(wsi, param_names, ARRAY_SIZE(param_names), 1024, file_upload_cb, pss); if (!pss->spa) return -1; pss->filename[0] = '\0'; pss->file_length = 0; } /* let it parse the POST data */ if (lws_spa_process(pss->spa, in, (int)len)) return -1; break; case LWS_CALLBACK_HTTP_BODY_COMPLETION: lwsl_debug("LWS_CALLBACK_HTTP_BODY_COMPLETION\n"); /* call to inform no more payload data coming */ lws_spa_finalize(pss->spa); p = (unsigned char *)pss->result + LWS_PRE; end = p + sizeof(pss->result) - LWS_PRE - 1; p += sprintf((char *)p, "<html><body><h1>Form results (after urldecoding)</h1>" "<table><tr><td>Name</td><td>Length</td><td>Value</td></tr>"); for (n = 0; n < (int)ARRAY_SIZE(param_names); n++) { if (!lws_spa_get_string(pss->spa, n)) p += lws_snprintf((char *)p, end - p, "<tr><td><b>%s</b></td><td>0</td><td>NULL</td></tr>", param_names[n]); else p += lws_snprintf((char *)p, end - p, "<tr><td><b>%s</b></td><td>%d</td><td>%s</td></tr>", param_names[n], lws_spa_get_length(pss->spa, n), lws_spa_get_string(pss->spa, n)); } p += lws_snprintf((char *)p, end - p, "</table><br><b>filename:</b> %s, <b>length</b> %ld", pss->filename, pss->file_length); p += lws_snprintf((char *)p, end - p, "</body></html>"); pss->result_len = lws_ptr_diff(p, pss->result + LWS_PRE); n = LWS_PRE + 1024; buffer = malloc(n); p = buffer + LWS_PRE; start = p; end = p + n - LWS_PRE - 1; if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end)) goto bail; if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)"text/html", 9, &p, end)) goto bail; if (lws_add_http_header_content_length(wsi, pss->result_len, &p, end)) goto bail; if (lws_finalize_http_header(wsi, &p, end)) goto bail; n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS); if (n < 0) goto bail; free(buffer); lws_callback_on_writable(wsi); break; case LWS_CALLBACK_HTTP_WRITEABLE: if (!pss->result_len) break; lwsl_debug("LWS_CALLBACK_HTTP_WRITEABLE: sending %d\n", pss->result_len); n = lws_write(wsi, (unsigned char *)pss->result + LWS_PRE, pss->result_len, LWS_WRITE_HTTP); if (n < 0) return 1; goto try_to_reuse; case LWS_CALLBACK_HTTP_DROP_PROTOCOL: /* called when our wsi user_space is going to be destroyed */ if (pss->spa) { lws_spa_destroy(pss->spa); pss->spa = NULL; } break; default: break; } return 0; bail: free(buffer); return 1; try_to_reuse: if (lws_http_transaction_completed(wsi)) return -1; return 0; }
static int callback_lws_table_dirlisting(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct per_session_data__tbl_dir *pss = (struct per_session_data__tbl_dir *)user; char j[LWS_PRE + 16384], *p = j + LWS_PRE, *start = p, *q, *q1, *w, *end = j + sizeof(j) - LWS_PRE, e[384], s[384], s1[384]; const struct lws_protocol_vhost_options *pmo; struct fobj *f; int n, first = 1; switch (reason) { case LWS_CALLBACK_PROTOCOL_INIT: /* per vhost */ break; case LWS_CALLBACK_ESTABLISHED: lwsl_debug("LWS_CALLBACK_ESTABLISHED\n"); /* * send client the lwsgt table layout */ start = "{\"cols\":[" " {\"name\": \"Date\"}," " {\"name\": \"Size\", \"align\": \"right\"}," " {\"name\": \"Icon\"}," " {\"name\": \"Name\", \"href\": \"uri\"}," " {\"name\": \"uri\", \"hide\": \"1\" }" " ]" "}"; if (lws_write(wsi, (unsigned char *)start, strlen(start), LWS_WRITE_TEXT) < 0) return -1; /* send a view update next */ lws_callback_on_writable(wsi); break; case LWS_CALLBACK_RECEIVE: if (len > sizeof(pss->reldir) - 1) len = sizeof(pss->reldir) - 1; if (!strstr(in, "..") && !strchr(in, '~')) strncpy(pss->reldir, in, len); else len = 0; pss->reldir[len] = '\0'; if (pss->reldir[0] == '/' && !pss->reldir[1]) pss->reldir[0] = '\0'; lwsl_info("%s\n", pss->reldir); lws_callback_on_writable(wsi); break; case LWS_CALLBACK_SERVER_WRITEABLE: if (scan_dir(wsi, pss)) return 1; p += lws_snprintf(p, end - p, "{\"breadcrumbs\":["); q = pss->reldir; if (!q[0]) p += lws_snprintf(p, end - p, "{\"name\":\"top\"}"); while (*q) { q1 = strchr(q, '/'); if (!q1) { if (first) strcpy(s, "top1"); else strcpy(s, q); s1[0] = '\0'; q += strlen(q); } else { n = lws_ptr_diff(q1, q); if (n > (int)sizeof(s) - 1) n = sizeof(s) - 1; if (first) { strcpy(s1, "/"); strcpy(s, "top"); } else { strncpy(s, q, n); s[n] = '\0'; n = lws_ptr_diff(q1, pss->reldir); if (n > (int)sizeof(s1) - 1) n = sizeof(s1) - 1; strncpy(s1, pss->reldir, n); s1[n] = '\0'; } q = q1 + 1; } if (!first) p += lws_snprintf(p, end - p, ","); else first = 0; p += lws_snprintf(p, end - p, "{\"name\":\"%s\"", lws_json_purify(e, s, sizeof(e))); if (*q) { w = s1; while (w[0] == '/' && w[1] == '/') w++; p += lws_snprintf(p, end - p, ",\"url\":\"%s\"", lws_json_purify(e, w, sizeof(e))); } p += lws_snprintf(p, end - p, "}"); if (!q1) break; } p += lws_snprintf(p, end - p, "],\"data\":["); f = pss->base.next; while (f) { /* format in JSON */ p += lws_snprintf(p, end - p, "{\"Icon\":\"%s\",", lws_json_purify(e, f->icon, sizeof(e))); p += lws_snprintf(p, end - p, " \"Date\":\"%s\",", lws_json_purify(e, f->date, sizeof(e))); p += lws_snprintf(p, end - p, " \"Size\":\"%ld\",", f->size); if (f->uri) p += lws_snprintf(p, end - p, " \"uri\":\"%s\",", lws_json_purify(e, f->uri, sizeof(e))); p += lws_snprintf(p, end - p, " \"Name\":\"%s\"}", lws_json_purify(e, f->name, sizeof(e))); f = f->next; if (f) p += lws_snprintf(p, end - p, ","); } p += lws_snprintf(p, end - p, "]}"); free_scan_dir(pss); if (lws_write(wsi, (unsigned char *)start, p - start, LWS_WRITE_TEXT) < 0) return -1; break; case LWS_CALLBACK_HTTP_PMO: /* find the per-mount options we're interested in */ lwsl_debug("LWS_CALLBACK_HTTP_PMO\n"); pmo = (struct lws_protocol_vhost_options *)in; while (pmo) { if (!strcmp(pmo->name, "dir")) /* path to list files */ pss->dir = pmo->value; pmo = pmo->next; } if (!pss->dir[0]) { lwsl_err("dirlisting: \"dir\" pmo missing\n"); return 1; } break; case LWS_CALLBACK_HTTP_DROP_PROTOCOL: //lwsl_notice("LWS_CALLBACK_HTTP_DROP_PROTOCOL\n"); #if UV_VERSION_MAJOR > 0 lws_protocol_dir_kill_monitor(pss); #endif break; default: return 0; } return 0; }
LWS_VISIBLE int lws_return_http_status(struct lws *wsi, unsigned int code, const char *html_body) { struct lws_context *context = lws_get_context(wsi); struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; unsigned char *p = pt->serv_buf + LWS_PRE; unsigned char *start = p; unsigned char *end = p + context->pt_serv_buf_size - LWS_PRE; int n = 0, m = 0, len; char slen[20]; if (!html_body) html_body = ""; if (lws_add_http_header_status(wsi, code, &p, end)) return 1; if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)"text/html", 9, &p, end)) return 1; len = 35 + (int)strlen(html_body) + sprintf(slen, "%d", code); n = sprintf(slen, "%d", len); if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH, (unsigned char *)slen, n, &p, end)) return 1; if (lws_finalize_http_header(wsi, &p, end)) return 1; #if defined(LWS_WITH_HTTP2) if (wsi->http2_substream) { unsigned char *body = p + 512; /* * for HTTP/2, the headers must be sent separately, since they * go out in their own frame. That puts us in a bind that * we won't always be able to get away with two lws_write()s in * sequence, since the first may use up the writability due to * the pipe being choked or SSL_WANT_. * * However we do need to send the human-readable body, and the * END_STREAM. * * Solve it by writing the headers now... */ m = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS); if (m != lws_ptr_diff(p, start)) return 1; /* * ... but stash the body and send it as a priority next * handle_POLLOUT */ len = sprintf((char *)body, "<html><body><h1>%u</h1>%s</body></html>", code, html_body); wsi->http.tx_content_length = len; wsi->http.tx_content_remain = len; wsi->h2.pending_status_body = lws_malloc(len + LWS_PRE + 1, "pending status body"); if (!wsi->h2.pending_status_body) return -1; strcpy(wsi->h2.pending_status_body + LWS_PRE, (const char *)body); lws_callback_on_writable(wsi); return 0; } else #endif { /* * for http/1, we can just append the body after the finalized * headers and send it all in one go. */ p += lws_snprintf((char *)p, end - p - 1, "<html><body><h1>%u</h1>%s</body></html>", code, html_body); n = lws_ptr_diff(p, start); m = lws_write(wsi, start, n, LWS_WRITE_HTTP); if (m != n) return 1; } return m != n; }
LWS_VISIBLE int lws_ext_parse_options(const struct lws_extension *ext, struct lws *wsi, void *ext_user, const struct lws_ext_options *opts, const char *in, int len) { enum lws_ext_option_parser_states leap = LEAPS_SEEK_NAME; unsigned int match_map = 0, n, m, w = 0, count_options = 0, pending_close_quote = 0; struct lws_ext_option_arg oa; oa.option_name = NULL; while (opts[count_options].name) count_options++; while (len) { lwsl_ext("'%c' %d", *in, leap); switch (leap) { case LEAPS_SEEK_NAME: if (*in == ' ') break; if (*in == ',') { len = 1; break; } match_map = (1 << count_options) - 1; leap = LEAPS_EAT_NAME; w = 0; /* fallthru */ case LEAPS_EAT_NAME: oa.start = NULL; oa.len = 0; m = match_map; n = 0; pending_close_quote = 0; while (m) { if (m & 1) { lwsl_ext(" m=%d, n=%d, w=%d\n", m, n, w); if (*in == opts[n].name[w]) { if (!opts[n].name[w + 1]) { oa.option_index = n; lwsl_ext("hit %d\n", oa.option_index); leap = LEAPS_SEEK_VAL; if (len == 1) goto set_arg; break; } } else { match_map &= ~(1 << n); if (!match_map) { lwsl_ext("empty match map\n"); return -1; } } } m >>= 1; n++; } w++; break; case LEAPS_SEEK_VAL: if (*in == ' ') break; if (*in == ',') { len = 1; break; } if (*in == ';' || len == 1) { /* ie,nonoptional */ if (opts[oa.option_index].type == EXTARG_DEC) return -1; leap = LEAPS_SEEK_NAME; goto set_arg; } if (*in == '=') { w = 0; pending_close_quote = 0; if (opts[oa.option_index].type == EXTARG_NONE) return -1; leap = LEAPS_EAT_DEC; break; } return -1; case LEAPS_EAT_DEC: if (*in >= '0' && *in <= '9') { if (!w) oa.start = in; w++; if (len != 1) break; } if (!w && *in =='"') { pending_close_quote = 1; break; } if (!w) return -1; if (pending_close_quote && *in != '"' && len != 1) return -1; leap = LEAPS_SEEK_ARG_TERM; if (oa.start) oa.len = lws_ptr_diff(in, oa.start); if (len == 1) oa.len++; set_arg: ext->callback(lws_get_context(wsi), ext, wsi, LWS_EXT_CB_OPTION_SET, ext_user, (char *)&oa, 0); if (len == 1) break; if (pending_close_quote && *in == '"') break; /* fallthru */ case LEAPS_SEEK_ARG_TERM: if (*in == ' ') break; if (*in == ';') { leap = LEAPS_SEEK_NAME; break; } if (*in == ',') { len = 1; break; } return -1; } len--; in++; } return 0; }
LWS_VISIBLE int lws_tls_acme_sni_cert_create(struct lws_vhost *vhost, const char *san_a, const char *san_b) { int buflen = 0x560; uint8_t *buf = lws_malloc(buflen, "tmp cert buf"), *p = buf, *pkey_asn1; struct lws_genrsa_ctx ctx; struct lws_genrsa_elements el; uint8_t digest[32]; struct lws_genhash_ctx hash_ctx; int pkey_asn1_len = 3 * 1024; int n, keybits = lws_plat_recommended_rsa_bits(), adj; if (!buf) return 1; n = lws_genrsa_new_keypair(vhost->context, &ctx, &el, keybits); if (n < 0) { lws_jwk_destroy_genrsa_elements(&el); goto bail1; } n = sizeof(ss_cert_leadin); memcpy(p, ss_cert_leadin, n); p += n; adj = (0x0556 - 0x401) + (keybits / 4) + 1; buf[2] = adj >> 8; buf[3] = adj & 0xff; adj = (0x033e - 0x201) + (keybits / 8) + 1; buf[6] = adj >> 8; buf[7] = adj & 0xff; adj = (0x0222 - 0x201) + (keybits / 8) + 1; buf[0xc3] = adj >> 8; buf[0xc4] = adj & 0xff; adj = (0x020f - 0x201) + (keybits / 8) + 1; buf[0xd6] = adj >> 8; buf[0xd7] = adj & 0xff; adj = (0x020a - 0x201) + (keybits / 8) + 1; buf[0xdb] = adj >> 8; buf[0xdc] = adj & 0xff; *p++ = ((keybits / 8) + 1) >> 8; *p++ = ((keybits / 8) + 1) & 0xff; /* we need to drop 1 + (keybits / 8) bytes of n in here, 00 + key */ *p++ = 0x00; memcpy(p, el.e[JWK_KEY_N].buf, el.e[JWK_KEY_N].len); p += el.e[JWK_KEY_N].len; memcpy(p, ss_cert_san_leadin, sizeof(ss_cert_san_leadin)); p += sizeof(ss_cert_san_leadin); /* drop in 78 bytes of san_a */ memcpy(p, san_a, SAN_A_LENGTH); p += SAN_A_LENGTH; memcpy(p, ss_cert_sig_leadin, sizeof(ss_cert_sig_leadin)); p[17] = ((keybits / 8) + 1) >> 8; p[18] = ((keybits / 8) + 1) & 0xff; p += sizeof(ss_cert_sig_leadin); /* hash the cert plaintext */ if (lws_genhash_init(&hash_ctx, LWS_GENHASH_TYPE_SHA256)) goto bail2; if (lws_genhash_update(&hash_ctx, buf, lws_ptr_diff(p, buf))) { lws_genhash_destroy(&hash_ctx, NULL); goto bail2; } if (lws_genhash_destroy(&hash_ctx, digest)) goto bail2; /* sign the hash */ n = lws_genrsa_public_sign(&ctx, digest, LWS_GENHASH_TYPE_SHA256, p, buflen - lws_ptr_diff(p, buf)); if (n < 0) goto bail2; p += n; pkey_asn1 = lws_malloc(pkey_asn1_len, "mbed crt tmp"); if (!pkey_asn1) goto bail2; n = lws_genrsa_render_pkey_asn1(&ctx, 1, pkey_asn1, pkey_asn1_len); if (n < 0) { lws_free(pkey_asn1); goto bail2; } lwsl_debug("private key\n"); lwsl_hexdump_level(LLL_DEBUG, pkey_asn1, n); /* and to use our generated private key */ n = SSL_CTX_use_PrivateKey_ASN1(0, vhost->ssl_ctx, pkey_asn1, n); lws_free(pkey_asn1); if (n != 1) { lwsl_notice("%s: SSL_CTX_use_PrivateKey_ASN1 failed\n", __func__); } lws_genrsa_destroy(&ctx); lws_jwk_destroy_genrsa_elements(&el); if (n == 1) { lwsl_hexdump_level(LLL_DEBUG, buf, lws_ptr_diff(p, buf)); n = SSL_CTX_use_certificate_ASN1(vhost->ssl_ctx, lws_ptr_diff(p, buf), buf); if (n != 1) lwsl_notice("%s: generated cert failed to load 0x%x\n", __func__, -n); } lws_free(buf); return n != 1; bail2: lws_genrsa_destroy(&ctx); lws_jwk_destroy_genrsa_elements(&el); bail1: lws_free(buf); return -1; }
LWS_VISIBLE LWS_EXTERN int lws_tls_acme_sni_csr_create(struct lws_context *context, const char *elements[], uint8_t *csr, size_t csr_len, char **privkey_pem, size_t *privkey_len) { uint8_t *csr_in = csr; RSA *rsakey; X509_REQ *req; X509_NAME *subj; EVP_PKEY *pkey; char *p, *end; BIO *bio; long bio_len; int n, ret = -1; if (lws_tls_openssl_rsa_new_key(&rsakey, 4096)) return -1; pkey = EVP_PKEY_new(); if (!pkey) goto bail0; if (!EVP_PKEY_set1_RSA(pkey, rsakey)) goto bail1; req = X509_REQ_new(); if (!req) goto bail1; X509_REQ_set_pubkey(req, pkey); subj = X509_NAME_new(); if (!subj) goto bail2; for (n = 0; n < LWS_TLS_REQ_ELEMENT_COUNT; n++) if (lws_tls_openssl_add_nid(subj, nid_list[n], elements[n])) { lwsl_notice("%s: failed to add element %d\n", __func__, n); goto bail3; } if (X509_REQ_set_subject_name(req, subj) != 1) goto bail3; if (!X509_REQ_sign(req, pkey, EVP_sha256())) goto bail3; /* * issue the CSR as PEM to a BIO, and translate to b64urlenc without * headers, trailers, or whitespace */ bio = BIO_new(BIO_s_mem()); if (!bio) goto bail3; if (PEM_write_bio_X509_REQ(bio, req) != 1) { BIO_free(bio); goto bail3; } bio_len = BIO_get_mem_data(bio, &p); end = p + bio_len; /* strip the header line */ while (p < end && *p != '\n') p++; while (p < end && csr_len) { if (*p == '\n') { p++; continue; } if (*p == '-') break; if (*p == '+') *csr++ = '-'; else if (*p == '/') *csr++ = '_'; else *csr++ = *p; p++; csr_len--; } BIO_free(bio); if (!csr_len) { lwsl_notice("%s: need %ld for CSR\n", __func__, bio_len); goto bail3; } /* * Also return the private key as a PEM in memory * (platform may not have a filesystem) */ bio = BIO_new(BIO_s_mem()); if (!bio) goto bail3; if (PEM_write_bio_PrivateKey(bio, pkey, NULL, NULL, 0, 0, NULL) != 1) { BIO_free(bio); goto bail3; } bio_len = BIO_get_mem_data(bio, &p); *privkey_pem = malloc(bio_len); /* malloc so user code can own / free */ *privkey_len = (size_t)bio_len; if (!*privkey_pem) { lwsl_notice("%s: need %ld for private key\n", __func__, bio_len); BIO_free(bio); goto bail3; } memcpy(*privkey_pem, p, (int)(long long)bio_len); BIO_free(bio); ret = lws_ptr_diff(csr, csr_in); bail3: X509_NAME_free(subj); bail2: X509_REQ_free(req); bail1: EVP_PKEY_free(pkey); bail0: RSA_free(rsakey); return ret; }
int handshake_0405(struct lws_context *context, struct lws *wsi) { struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; struct lws_process_html_args args; unsigned char hash[20]; int n, accept_len; char *response; char *p; if (!lws_hdr_total_length(wsi, WSI_TOKEN_HOST) || !lws_hdr_total_length(wsi, WSI_TOKEN_KEY)) { lwsl_parser("handshake_04 missing pieces\n"); /* completed header processing, but missing some bits */ goto bail; } if (lws_hdr_total_length(wsi, WSI_TOKEN_KEY) >= MAX_WEBSOCKET_04_KEY_LEN) { lwsl_warn("Client key too long %d\n", MAX_WEBSOCKET_04_KEY_LEN); goto bail; } /* * since key length is restricted above (currently 128), cannot * overflow */ n = sprintf((char *)pt->serv_buf, "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", lws_hdr_simple_ptr(wsi, WSI_TOKEN_KEY)); lws_SHA1(pt->serv_buf, n, hash); accept_len = lws_b64_encode_string((char *)hash, 20, (char *)pt->serv_buf, context->pt_serv_buf_size); if (accept_len < 0) { lwsl_warn("Base64 encoded hash too long\n"); goto bail; } /* allocate the per-connection user memory (if any) */ if (lws_ensure_user_space(wsi)) goto bail; /* create the response packet */ /* make a buffer big enough for everything */ response = (char *)pt->serv_buf + MAX_WEBSOCKET_04_KEY_LEN + LWS_PRE; p = response; LWS_CPYAPP(p, "HTTP/1.1 101 Switching Protocols\x0d\x0a" "Upgrade: WebSocket\x0d\x0a" "Connection: Upgrade\x0d\x0a" "Sec-WebSocket-Accept: "); strcpy(p, (char *)pt->serv_buf); p += accept_len; /* we can only return the protocol header if: * - one came in, and ... */ if (lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL) && /* - it is not an empty string */ wsi->protocol->name && wsi->protocol->name[0]) { LWS_CPYAPP(p, "\x0d\x0aSec-WebSocket-Protocol: "); p += lws_snprintf(p, 128, "%s", wsi->protocol->name); } #ifndef LWS_NO_EXTENSIONS /* * Figure out which extensions the client has that we want to * enable on this connection, and give him back the list. * * Give him a limited write bugdet */ if (lws_extension_server_handshake(wsi, &p, 192)) goto bail; #endif LWS_CPYAPP(p, "\x0d\x0a"); args.p = p; args.max_len = lws_ptr_diff((char *)pt->serv_buf + context->pt_serv_buf_size, p); if (user_callback_handle_rxflow(wsi->protocol->callback, wsi, LWS_CALLBACK_ADD_HEADERS, wsi->user_space, &args, 0)) goto bail; p = args.p; /* end of response packet */ LWS_CPYAPP(p, "\x0d\x0a"); if (!lws_any_extension_handled(wsi, LWS_EXT_CB_HANDSHAKE_REPLY_TX, response, p - response)) { /* okay send the handshake response accepting the connection */ lwsl_parser("issuing resp pkt %d len\n", lws_ptr_diff(p, response)); #if defined(DEBUG) && ! defined(LWS_WITH_ESP8266) fwrite(response, 1, p - response, stderr); #endif n = lws_write(wsi, (unsigned char *)response, p - response, LWS_WRITE_HTTP_HEADERS); if (n != (p - response)) { lwsl_debug("handshake_0405: ERROR writing to socket\n"); goto bail; } } /* alright clean up and set ourselves into established state */ wsi->state = LWSS_ESTABLISHED; wsi->lws_rx_parse_state = LWS_RXPS_NEW; { const char * uri_ptr = lws_hdr_simple_ptr(wsi, WSI_TOKEN_GET_URI); int uri_len = lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI); const struct lws_http_mount *hit = lws_find_mount(wsi, uri_ptr, uri_len); if (hit && hit->cgienv && wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP_PMO, wsi->user_space, (void *)hit->cgienv, 0)) return 1; } return 0; bail: /* caller will free up his parsing allocations */ return -1; }
static lws_fop_fd_t lws_fops_zip_open(const struct lws_plat_file_ops *fops, const char *vfs_path, const char *vpath, lws_fop_flags_t *flags) { lws_fop_flags_t local_flags = 0; lws_fops_zip_t priv; char rp[192]; int m; /* * vpath points at the / after the fops signature in vfs_path, eg * with a vfs_path "/var/www/docs/manual.zip/index.html", vpath * will come pointing at "/index.html" */ priv = lws_zalloc(sizeof(*priv), "fops_zip priv"); if (!priv) return NULL; priv->fop_fd.fops = &fops_zip; m = sizeof(rp) - 1; if ((vpath - vfs_path - 1) < m) m = lws_ptr_diff(vpath, vfs_path) - 1; strncpy(rp, vfs_path, m); rp[m] = '\0'; /* open the zip file itself using the incoming fops, not fops_zip */ priv->zip_fop_fd = fops->LWS_FOP_OPEN(fops, rp, NULL, &local_flags); if (!priv->zip_fop_fd) { lwsl_err("unable to open zip %s\n", rp); goto bail1; } if (*vpath == '/') vpath++; m = lws_fops_zip_scan(priv, vpath, (int)strlen(vpath)); if (m) { lwsl_err("unable to find record matching '%s' %d\n", vpath, m); goto bail2; } /* the directory metadata tells us modification time, so pass it on */ priv->fop_fd.mod_time = priv->hdr.mod_time; *flags |= LWS_FOP_FLAG_MOD_TIME_VALID | LWS_FOP_FLAG_VIRTUAL; priv->fop_fd.flags = *flags; /* The zip fop_fd is left pointing at the start of the content. * * 1) Content could be uncompressed (STORE), and we can always serve * that directly * * 2) Content could be compressed (GZIP), and the client can handle * receiving GZIP... we can wrap it in a GZIP header and trailer * and serve the content part directly. The flag indicating we * are providing GZIP directly is set so lws will send the right * headers. * * 3) Content could be compressed (GZIP) but the client can't handle * receiving GZIP... we can decompress it and serve as it is * inflated piecemeal. * * 4) Content may be compressed some unknown way... fail * */ if (priv->hdr.method == ZIP_COMPRESSION_METHOD_STORE) { /* * it is stored uncompressed, leave it indicated as * uncompressed, and just serve it from inside the * zip with no gzip container; */ lwsl_info("direct zip serving (stored)\n"); priv->fop_fd.len = priv->hdr.uncomp_size; return &priv->fop_fd; } if ((*flags & LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP) && priv->hdr.method == ZIP_COMPRESSION_METHOD_DEFLATE) { /* * We can serve the gzipped file contents directly as gzip * from inside the zip container; client says it is OK. * * To convert to standalone gzip, we have to add a 10-byte * constant header and a variable 8-byte trailer around the * content. * * The 8-byte trailer is prepared now and held in the priv. */ lwsl_info("direct zip serving (gzipped)\n"); priv->fop_fd.len = sizeof(hd) + priv->hdr.comp_size + sizeof(priv->u); if (lws_is_be()) { uint8_t *p = priv->u.trailer8; *p++ = (uint8_t)priv->hdr.crc32; *p++ = (uint8_t)(priv->hdr.crc32 >> 8); *p++ = (uint8_t)(priv->hdr.crc32 >> 16); *p++ = (uint8_t)(priv->hdr.crc32 >> 24); *p++ = (uint8_t)priv->hdr.uncomp_size; *p++ = (uint8_t)(priv->hdr.uncomp_size >> 8); *p++ = (uint8_t)(priv->hdr.uncomp_size >> 16); *p = (uint8_t)(priv->hdr.uncomp_size >> 24); } else {
static int lws_tls_mbedtls_cert_info(mbedtls_x509_crt *x509, enum lws_tls_cert_info type, union lws_tls_cert_info_results *buf, size_t len) { if (!x509) return -1; switch (type) { case LWS_TLS_CERT_INFO_VALIDITY_FROM: buf->time = lws_tls_mbedtls_time_to_unix(&x509->valid_from); if (buf->time == (time_t)(long long)-1) return -1; break; case LWS_TLS_CERT_INFO_VALIDITY_TO: buf->time = lws_tls_mbedtls_time_to_unix(&x509->valid_to); if (buf->time == (time_t)(long long)-1) return -1; break; case LWS_TLS_CERT_INFO_COMMON_NAME: return lws_tls_mbedtls_get_x509_name(&x509->subject, buf, len); case LWS_TLS_CERT_INFO_ISSUER_NAME: return lws_tls_mbedtls_get_x509_name(&x509->issuer, buf, len); case LWS_TLS_CERT_INFO_USAGE: buf->usage = x509->key_usage; break; case LWS_TLS_CERT_INFO_OPAQUE_PUBLIC_KEY: { char *p = buf->ns.name; size_t r = len, u; switch (mbedtls_pk_get_type(&x509->pk)) { case MBEDTLS_PK_RSA: { mbedtls_rsa_context *rsa = mbedtls_pk_rsa(x509->pk); if (mbedtls_mpi_write_string(&rsa->N, 16, p, r, &u)) return -1; r -= u; p += u; if (mbedtls_mpi_write_string(&rsa->E, 16, p, r, &u)) return -1; p += u; buf->ns.len = lws_ptr_diff(p, buf->ns.name); break; } case MBEDTLS_PK_ECKEY: { mbedtls_ecp_keypair *ecp = mbedtls_pk_ec(x509->pk); if (mbedtls_mpi_write_string(&ecp->Q.X, 16, p, r, &u)) return -1; r -= u; p += u; if (mbedtls_mpi_write_string(&ecp->Q.Y, 16, p, r, &u)) return -1; r -= u; p += u; if (mbedtls_mpi_write_string(&ecp->Q.Z, 16, p, r, &u)) return -1; p += u; buf->ns.len = lws_ptr_diff(p, buf->ns.name); break; } default: lwsl_notice("%s: x509 has unsupported pubkey type %d\n", __func__, mbedtls_pk_get_type(&x509->pk)); return -1; } break; } default: return -1; } return 0; }
int lws_client_ws_upgrade(struct lws *wsi, const char **cce) { int n, len, okay = 0; struct lws_context *context = wsi->context; const char *pc; char *p; #if !defined(LWS_WITHOUT_EXTENSIONS) struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; char *sb = (char *)&pt->serv_buf[0]; const struct lws_ext_options *opts; const struct lws_extension *ext; char ext_name[128]; const char *c, *a; char ignore; int more = 1; #endif if (wsi->client_h2_substream) {/* !!! client ws-over-h2 not there yet */ lwsl_warn("%s: client ws-over-h2 upgrade not supported yet\n", __func__); *cce = "HS: h2 / ws upgrade unsupported"; goto bail3; } if (wsi->http.ah->http_response == 401) { lwsl_warn( "lws_client_handshake: got bad HTTP response '%d'\n", wsi->http.ah->http_response); *cce = "HS: ws upgrade unauthorized"; goto bail3; } if (wsi->http.ah->http_response != 101) { lwsl_warn( "lws_client_handshake: got bad HTTP response '%d'\n", wsi->http.ah->http_response); *cce = "HS: ws upgrade response not 101"; goto bail3; } if (lws_hdr_total_length(wsi, WSI_TOKEN_ACCEPT) == 0) { lwsl_info("no ACCEPT\n"); *cce = "HS: ACCEPT missing"; goto bail3; } p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE); if (!p) { lwsl_info("no UPGRADE\n"); *cce = "HS: UPGRADE missing"; goto bail3; } strtolower(p); if (strcmp(p, "websocket")) { lwsl_warn( "lws_client_handshake: got bad Upgrade header '%s'\n", p); *cce = "HS: Upgrade to something other than websocket"; goto bail3; } p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_CONNECTION); if (!p) { lwsl_info("no Connection hdr\n"); *cce = "HS: CONNECTION missing"; goto bail3; } strtolower(p); if (strcmp(p, "upgrade")) { lwsl_warn("lws_client_int_s_hs: bad header %s\n", p); *cce = "HS: UPGRADE malformed"; goto bail3; } pc = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS); if (!pc) { lwsl_parser("lws_client_int_s_hs: no protocol list\n"); } else lwsl_parser("lws_client_int_s_hs: protocol list '%s'\n", pc); /* * confirm the protocol the server wants to talk was in the list * of protocols we offered */ len = lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL); if (!len) { lwsl_info("%s: WSI_TOKEN_PROTOCOL is null\n", __func__); /* * no protocol name to work from, * default to first protocol */ n = 0; wsi->protocol = &wsi->vhost->protocols[0]; goto check_extensions; } p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL); len = (int)strlen(p); while (pc && *pc && !okay) { if (!strncmp(pc, p, len) && (pc[len] == ',' || pc[len] == '\0')) { okay = 1; continue; } while (*pc && *pc++ != ',') ; while (*pc == ' ') pc++; } if (!okay) { lwsl_info("%s: got bad protocol %s\n", __func__, p); *cce = "HS: PROTOCOL malformed"; goto bail2; } /* * identify the selected protocol struct and set it */ n = 0; /* keep client connection pre-bound protocol */ if (!lwsi_role_client(wsi)) wsi->protocol = NULL; while (wsi->vhost->protocols[n].callback) { if (!wsi->protocol && strcmp(p, wsi->vhost->protocols[n].name) == 0) { wsi->protocol = &wsi->vhost->protocols[n]; break; } n++; } if (!wsi->vhost->protocols[n].callback) { /* no match */ /* if server, that's already fatal */ if (!lwsi_role_client(wsi)) { lwsl_info("%s: fail protocol %s\n", __func__, p); *cce = "HS: Cannot match protocol"; goto bail2; } /* for client, find the index of our pre-bound protocol */ n = 0; while (wsi->vhost->protocols[n].callback) { if (wsi->protocol && strcmp(wsi->protocol->name, wsi->vhost->protocols[n].name) == 0) { wsi->protocol = &wsi->vhost->protocols[n]; break; } n++; } if (!wsi->vhost->protocols[n].callback) { if (wsi->protocol) lwsl_err("Failed to match protocol %s\n", wsi->protocol->name); else lwsl_err("No protocol on client\n"); goto bail2; } } lwsl_debug("Selected protocol %s\n", wsi->protocol->name); check_extensions: /* * stitch protocol choice into the vh protocol linked list * We always insert ourselves at the start of the list * * X <-> B * X <-> pAn <-> pB */ lws_same_vh_protocol_insert(wsi, n); #if !defined(LWS_WITHOUT_EXTENSIONS) /* instantiate the accepted extensions */ if (!lws_hdr_total_length(wsi, WSI_TOKEN_EXTENSIONS)) { lwsl_ext("no client extensions allowed by server\n"); goto check_accept; } /* * break down the list of server accepted extensions * and go through matching them or identifying bogons */ if (lws_hdr_copy(wsi, sb, context->pt_serv_buf_size, WSI_TOKEN_EXTENSIONS) < 0) { lwsl_warn("ext list from server failed to copy\n"); *cce = "HS: EXT: list too big"; goto bail2; } c = sb; n = 0; ignore = 0; a = NULL; while (more) { if (*c && (*c != ',' && *c != '\t')) { if (*c == ';') { ignore = 1; if (!a) a = c + 1; } if (ignore || *c == ' ') { c++; continue; } ext_name[n] = *c++; if (n < (int)sizeof(ext_name) - 1) n++; continue; } ext_name[n] = '\0'; ignore = 0; if (!*c) more = 0; else { c++; if (!n) continue; } /* check we actually support it */ lwsl_notice("checking client ext %s\n", ext_name); n = 0; ext = wsi->vhost->ws.extensions; while (ext && ext->callback) { if (strcmp(ext_name, ext->name)) { ext++; continue; } n = 1; lwsl_notice("instantiating client ext %s\n", ext_name); /* instantiate the extension on this conn */ wsi->ws->active_extensions[wsi->ws->count_act_ext] = ext; /* allow him to construct his ext instance */ if (ext->callback(lws_get_context(wsi), ext, wsi, LWS_EXT_CB_CLIENT_CONSTRUCT, (void *)&wsi->ws->act_ext_user[wsi->ws->count_act_ext], (void *)&opts, 0)) { lwsl_info(" ext %s failed construction\n", ext_name); ext++; continue; } /* * allow the user code to override ext defaults if it * wants to */ ext_name[0] = '\0'; if (user_callback_handle_rxflow(wsi->protocol->callback, wsi, LWS_CALLBACK_WS_EXT_DEFAULTS, (char *)ext->name, ext_name, sizeof(ext_name))) { *cce = "HS: EXT: failed setting defaults"; goto bail2; } if (ext_name[0] && lws_ext_parse_options(ext, wsi, wsi->ws->act_ext_user[ wsi->ws->count_act_ext], opts, ext_name, (int)strlen(ext_name))) { lwsl_err("%s: unable to parse user defaults '%s'", __func__, ext_name); *cce = "HS: EXT: failed parsing defaults"; goto bail2; } /* * give the extension the server options */ if (a && lws_ext_parse_options(ext, wsi, wsi->ws->act_ext_user[wsi->ws->count_act_ext], opts, a, lws_ptr_diff(c, a))) { lwsl_err("%s: unable to parse remote def '%s'", __func__, a); *cce = "HS: EXT: failed parsing options"; goto bail2; } if (ext->callback(lws_get_context(wsi), ext, wsi, LWS_EXT_CB_OPTION_CONFIRM, wsi->ws->act_ext_user[wsi->ws->count_act_ext], NULL, 0)) { lwsl_err("%s: ext %s rejects server options %s", __func__, ext->name, a); *cce = "HS: EXT: Rejects server options"; goto bail2; } wsi->ws->count_act_ext++; ext++; } if (n == 0) { lwsl_warn("Unknown ext '%s'!\n", ext_name); *cce = "HS: EXT: unknown ext"; goto bail2; } a = NULL; n = 0; } check_accept: #endif /* * Confirm his accept token is the one we precomputed */ p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_ACCEPT); if (strcmp(p, wsi->http.ah->initial_handshake_hash_base64)) { lwsl_warn("lws_client_int_s_hs: accept '%s' wrong vs '%s'\n", p, wsi->http.ah->initial_handshake_hash_base64); *cce = "HS: Accept hash wrong"; goto bail2; } /* allocate the per-connection user memory (if any) */ if (lws_ensure_user_space(wsi)) { lwsl_err("Problem allocating wsi user mem\n"); *cce = "HS: OOM"; goto bail2; } /* * we seem to be good to go, give client last chance to check * headers and OK it */ if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH, wsi->user_space, NULL, 0)) { *cce = "HS: Rejected by filter cb"; goto bail2; } /* clear his proxy connection timeout */ lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); /* free up his parsing allocations */ lws_header_table_detach(wsi, 0); lws_role_transition(wsi, LWSIFR_CLIENT, LRS_ESTABLISHED, &role_ops_ws); lws_restart_ws_ping_pong_timer(wsi); wsi->rxflow_change_to = LWS_RXFLOW_ALLOW; /* * create the frame buffer for this connection according to the * size mentioned in the protocol definition. If 0 there, then * use a big default for compatibility */ n = (int)wsi->protocol->rx_buffer_size; if (!n) n = context->pt_serv_buf_size; n += LWS_PRE; wsi->ws->rx_ubuf = lws_malloc(n + 4 /* 0x0000ffff zlib */, "client frame buffer"); if (!wsi->ws->rx_ubuf) { lwsl_err("Out of Mem allocating rx buffer %d\n", n); *cce = "HS: OOM"; goto bail2; } wsi->ws->rx_ubuf_alloc = n; lwsl_info("Allocating client RX buffer %d\n", n); #if !defined(LWS_WITH_ESP32) if (setsockopt(wsi->desc.sockfd, SOL_SOCKET, SO_SNDBUF, (const char *)&n, sizeof n)) { lwsl_warn("Failed to set SNDBUF to %d", n); *cce = "HS: SO_SNDBUF failed"; goto bail3; } #endif lwsl_debug("handshake OK for protocol %s\n", wsi->protocol->name); /* call him back to inform him he is up */ if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_ESTABLISHED, wsi->user_space, NULL, 0)) { *cce = "HS: Rejected at CLIENT_ESTABLISHED"; goto bail3; } return 0; bail3: return 3; bail2: return 2; }
int lws_tls_openssl_cert_info(X509 *x509, enum lws_tls_cert_info type, union lws_tls_cert_info_results *buf, size_t len) { X509_NAME *xn; #if !defined(LWS_PLAT_OPTEE) char *p; #endif if (!x509) return -1; #if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(X509_get_notBefore) #define X509_get_notBefore(x) X509_getm_notBefore(x) #define X509_get_notAfter(x) X509_getm_notAfter(x) #endif switch (type) { case LWS_TLS_CERT_INFO_VALIDITY_FROM: buf->time = lws_tls_openssl_asn1time_to_unix( X509_get_notBefore(x509)); if (buf->time == (time_t)-1) return -1; break; case LWS_TLS_CERT_INFO_VALIDITY_TO: buf->time = lws_tls_openssl_asn1time_to_unix( X509_get_notAfter(x509)); if (buf->time == (time_t)-1) return -1; break; case LWS_TLS_CERT_INFO_COMMON_NAME: #if defined(LWS_PLAT_OPTEE) return -1; #else xn = X509_get_subject_name(x509); if (!xn) return -1; X509_NAME_oneline(xn, buf->ns.name, (int)len - 2); p = strstr(buf->ns.name, "/CN="); if (p) memmove(buf->ns.name, p + 4, strlen(p + 4) + 1); buf->ns.len = (int)strlen(buf->ns.name); return 0; #endif case LWS_TLS_CERT_INFO_ISSUER_NAME: xn = X509_get_issuer_name(x509); if (!xn) return -1; X509_NAME_oneline(xn, buf->ns.name, (int)len - 1); buf->ns.len = (int)strlen(buf->ns.name); return 0; case LWS_TLS_CERT_INFO_USAGE: #if defined(LWS_HAVE_X509_get_key_usage) buf->usage = X509_get_key_usage(x509); break; #else return -1; #endif case LWS_TLS_CERT_INFO_OPAQUE_PUBLIC_KEY: { #ifndef USE_WOLFSSL size_t klen = i2d_X509_PUBKEY(X509_get_X509_PUBKEY(x509), NULL); uint8_t *tmp, *ptmp; if (!klen || klen > len) return -1; tmp = (uint8_t *)OPENSSL_malloc(klen); if (!tmp) return -1; ptmp = tmp; if (i2d_X509_PUBKEY( X509_get_X509_PUBKEY(x509), &ptmp) != (int)klen || !ptmp || lws_ptr_diff(ptmp, tmp) != (int)klen) { lwsl_info("%s: cert public key extraction failed\n", __func__); if (ptmp) OPENSSL_free(tmp); return -1; } buf->ns.len = (int)klen; memcpy(buf->ns.name, tmp, klen); OPENSSL_free(tmp); #endif return 0; } default: return -1; } return 0; }