int websocket_server_callback(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { websocket_user_info *info = (websocket_user_info *)user; switch (reason) { case LWS_CALLBACK_ESTABLISHED: // just log message that someone is connecting { info->conn = std::make_shared<websocket_connection>(wsi); websocket_server *server = (websocket_server *)lws_context_user(info->context); info->conn->set_world(server->world()); server->add_websocket(info->conn); info->conn->show_welcome(); lws_callback_on_writable(wsi); break; } case LWS_CALLBACK_RECEIVE: // the funny part { info->conn->buffered_writer::writeln(); info->conn->process_input((char *)in); lws_callback_on_writable(wsi); break; } case LWS_CALLBACK_SERVER_WRITEABLE: { info->conn->show_prompt(); info->conn->write_from_buffer(); break; } default: break; } return 0; }
int lws_ssl_client_connect1(struct lws *wsi) { struct lws_context *context = wsi->context; int n = 0; lws_latency_pre(context, wsi); n = lws_tls_client_connect(wsi); lws_latency(context, wsi, "SSL_connect hs", n, n > 0); switch (n) { case LWS_SSL_CAPABLE_ERROR: return -1; case LWS_SSL_CAPABLE_DONE: return 1; /* connected */ case LWS_SSL_CAPABLE_MORE_SERVICE_WRITE: lws_callback_on_writable(wsi); /* fallthru */ case LWS_SSL_CAPABLE_MORE_SERVICE_READ: lwsi_set_state(wsi, LRS_WAITING_SSL); break; case LWS_SSL_CAPABLE_MORE_SERVICE: break; } return 0; /* retry */ }
int lws_ssl_client_connect2(struct lws *wsi, char *errbuf, int len) { int n = 0; if (lwsi_state(wsi) == LRS_WAITING_SSL) { lws_latency_pre(wsi->context, wsi); n = lws_tls_client_connect(wsi); lwsl_debug("%s: SSL_connect says %d\n", __func__, n); lws_latency(wsi->context, wsi, "SSL_connect LRS_WAITING_SSL", n, n > 0); switch (n) { case LWS_SSL_CAPABLE_ERROR: lws_snprintf(errbuf, len, "client connect failed"); return -1; case LWS_SSL_CAPABLE_DONE: break; /* connected */ case LWS_SSL_CAPABLE_MORE_SERVICE_WRITE: lws_callback_on_writable(wsi); /* fallthru */ case LWS_SSL_CAPABLE_MORE_SERVICE_READ: lwsi_set_state(wsi, LRS_WAITING_SSL); /* fallthru */ case LWS_SSL_CAPABLE_MORE_SERVICE: return 0; } } if (lws_tls_client_confirm_peer_cert(wsi, errbuf, len)) return -1; return 1; }
void ps_websockets_session_over (void * transport, guint64 session_id, gboolean timeout){ if (transport == NULL || !timeout) return; ps_websockets_client * client = (ps_websockets_client * )transport; ps_mutex_lock (&client->mutex); client->session_timeout = 1; lws_callback_on_writable (client->wsi); ps_mutex_unlock (&client->mutex); }
int lws_ssl_client_connect1(struct lws *wsi) { struct lws_context *context = wsi->context; struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; char *p = (char *)&pt->serv_buf[0]; char *sb = p; int n; lws_latency_pre(context, wsi); n = SSL_connect(wsi->ssl); lws_latency(context, wsi, "SSL_connect LWSCM_WSCL_ISSUE_HANDSHAKE", n, n > 0); if (n < 0) { n = SSL_get_error(wsi->ssl, n); if (n == SSL_ERROR_WANT_READ) goto some_wait; if (n == SSL_ERROR_WANT_WRITE) { /* * wants us to retry connect due to * state of the underlying ssl layer... * but since it may be stalled on * blocked write, no incoming data may * arrive to trigger the retry. * Force (possibly many times if the SSL * state persists in returning the * condition code, but other sockets * are getting serviced inbetweentimes) * us to get called back when writable. */ lwsl_info("%s: WANT_WRITE... retrying\n", __func__); lws_callback_on_writable(wsi); some_wait: wsi->mode = LWSCM_WSCL_WAITING_SSL; return 0; /* no error */ } n = -1; } if (n <= 0) { /* * retry if new data comes until we * run into the connection timeout or win */ n = ERR_get_error(); if (n != SSL_ERROR_NONE) { lwsl_err("SSL connect error %lu: %s\n", n, ERR_error_string(n, sb)); return 0; } } return 1; }
void FLwsWebSocket::Send(const void* Data, SIZE_T Size, bool bIsBinary) { bool QueueWasEmpty = SendQueue.IsEmpty(); SendQueue.Enqueue(MakeShareable(new FLwsSendBuffer((const uint8*) Data, Size, bIsBinary))); if (LwsConnection != nullptr && QueueWasEmpty) { lws_callback_on_writable(LwsConnection); } }
void FLwsWebSocket::Close(int32 Code, const FString& Reason) { CloseCode = Code; CloseReason = Reason; if (LwsConnection != nullptr && SendQueue.IsEmpty()) { lws_callback_on_writable(LwsConnection); } }
static void mon_cb(uv_fs_event_t *handle, const char *filename, int events, int status) { struct per_session_data__tbl_dir *pss = handle->data; //lwsl_notice("%s\n", __func__); if (pss && pss->wsi) lws_callback_on_writable(pss->wsi); }
gboolean discord_ws_keepalive_loop(gpointer data, gint fd, b_input_condition cond) { struct im_connection *ic = data; discord_data *dd = ic->proto_data; if (dd->state == WS_READY) { lws_callback_on_writable(dd->lws); } return TRUE; }
void lws_set_protocol_write_pending(struct lws_context *context, struct lws *wsi, enum lws_pending_protocol_send pend) { lwsl_info("setting pps %d\n", pend); if (wsi->pps) lwsl_err("pps overwrite\n"); wsi->pps = pend; lws_rx_flow_control(wsi, 0); lws_callback_on_writable(context, wsi); }
void Socket::send(char *paddedBuffer, size_t length, bool binary, bool transferOwnership) { SocketExtension *ext = (SocketExtension *) extension; SocketExtension::Message message = { binary, paddedBuffer, length, transferOwnership }; ext->messages.push(message); // Request notification when writing is allowed lws_callback_on_writable(wsi); }
static int unreal_networking_client( struct lws *Wsi, enum lws_callback_reasons Reason, void *User, void *In, size_t Len) { struct lws_context *Context = lws_get_context(Wsi); FWebSocket* Socket = (FWebSocket*)lws_context_user(Context); switch (Reason) { case LWS_CALLBACK_CLIENT_ESTABLISHED: { Socket->ConnectedCallBack.Broadcast(); lws_set_timeout(Wsi, NO_PENDING_TIMEOUT, 0); check(Socket->Wsi == Wsi); } break; case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: { Socket->ErrorCallBack.Broadcast(); return -1; } break; case LWS_CALLBACK_CLIENT_RECEIVE: { // push it on the socket. Socket->OnRawRecieve(In, (uint32)Len, !!lws_frame_is_binary(Wsi)); check(Socket->Wsi == Wsi); lws_set_timeout(Wsi, NO_PENDING_TIMEOUT, 0); break; } case LWS_CALLBACK_CLIENT_WRITEABLE: { check(Socket->Wsi == Wsi); Socket->OnRawWebSocketWritable(Wsi); lws_callback_on_writable(Wsi); lws_set_timeout(Wsi, NO_PENDING_TIMEOUT, 0); break; } case LWS_CALLBACK_CLOSED: { Socket->ErrorCallBack.Broadcast(); return -1; } } return 0; }
int callback(clws::lws *wsi, clws::lws_callback_reasons reason, void *user, void *in, size_t len) { lws::ServerInternals *serverInternals = (lws::ServerInternals *) lws_context_user(lws_get_context(wsi)); SocketExtension *ext = (SocketExtension *) user; switch (reason) { case clws::LWS_CALLBACK_SERVER_WRITEABLE: { SocketExtension::Message &message = ext->messages.front(); lws_write(wsi, (unsigned char *) message.buffer + LWS_SEND_BUFFER_PRE_PADDING, message.length, message.binary ? clws::LWS_WRITE_BINARY : clws::LWS_WRITE_TEXT); if (message.owned) { delete [] message.buffer; } ext->messages.pop(); if (!ext->messages.empty()) { lws_callback_on_writable(wsi); } break; } case clws::LWS_CALLBACK_ESTABLISHED: { new (&ext->messages) queue<SocketExtension::Message>; serverInternals->connectionCallback({wsi, ext}); break; } case clws::LWS_CALLBACK_CLOSED: { while (!ext->messages.empty()) { delete [] ext->messages.front().buffer; ext->messages.pop(); } ext->messages.~queue<SocketExtension::Message>(); serverInternals->disconnectionCallback({wsi, ext}); break; } case clws::LWS_CALLBACK_RECEIVE: { serverInternals->messageCallback({wsi, ext}, std::string((char *) in, len)); break; } default: break; } return 0; }
LWS_VISIBLE int lws_callback_on_writable_all_protocol(const struct lws_context *context, const struct lws_protocols *protocol) { struct lws *wsi; int n; for (n = 0; n < context->fds_count; n++) { wsi = wsi_from_fd(context,context->fds[n].fd); if (!wsi) continue; if (wsi->protocol == protocol) lws_callback_on_writable(wsi); } return 0; }
int ps_websockets_send_message (void * transport, json_t * message) { if (message == NULL) return -1; if (transport == NULL) { g_free(message); return -1; } ps_websockets_client * client = (ps_websockets_client *)transport; ps_mutex_lock(&client->mutex); char * payload = json_dumps (message, JSON_INDENT(3) | JSON_PRESERVE_ORDER); g_async_queue_push (client->messages, payload); lws_callback_on_writable (client->wsi); ps_mutex_unlock (&client->mutex); json_decref (message); return 0; }
LWS_VISIBLE int lws_callback_on_writable_all_protocol(const struct lws_context *context, const struct lws_protocols *protocol) { const struct lws_context_per_thread *pt = &context->pt[0]; unsigned int n, m = context->count_threads; struct lws *wsi; while (m--) { for (n = 0; n < pt->fds_count; n++) { wsi = wsi_from_fd(context, pt->fds[n].fd); if (!wsi) continue; if (wsi->protocol == protocol) lws_callback_on_writable(wsi); } pt++; } return 0; }
int janus_websockets_send_message(janus_transport_session *transport, void *request_id, gboolean admin, json_t *message) { if(message == NULL) return -1; if(transport == NULL || g_atomic_int_get(&transport->destroyed)) { json_decref(message); return -1; } janus_mutex_lock(&transport->mutex); janus_websockets_client *client = (janus_websockets_client *)transport->transport_p; if(!client) { json_decref(message); janus_mutex_unlock(&transport->mutex); return -1; } /* Convert to string and enqueue */ char *payload = json_dumps(message, json_format); g_async_queue_push(client->messages, payload); lws_callback_on_writable(client->wsi); janus_mutex_unlock(&transport->mutex); json_decref(message); return 0; }
void FWebSocket::Flush() { auto PendingMesssages = OutgoingBuffer.Num(); while (OutgoingBuffer.Num() > 0 && !IsServerSide) { #if !PLATFORM_HTML5 if (Protocols) { lws_callback_on_writable_all_protocol(Context, &Protocols[0]); } else { lws_callback_on_writable(Wsi); } #endif HandlePacket(); if (PendingMesssages <= OutgoingBuffer.Num()) // FIXME! { UE_LOG(LogHTML5Networking, Warning, TEXT("Unable to flush all of OutgoingBuffer in FWebSocket.")); break; } }; }
void FLwsWebSocket::SendFromQueue() { if (LwsConnection == nullptr) { return; } TSharedPtr<FLwsSendBuffer> CurrentBuffer; if (SendQueue.Peek(CurrentBuffer)) { bool FinishedSending = CurrentBuffer->Write(LwsConnection); if (FinishedSending) { SendQueue.Dequeue(CurrentBuffer); } } // If we still have data to send, ask for a notification when ready to send more if (!SendQueue.IsEmpty()) { lws_callback_on_writable(LwsConnection); } }
int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct per_session_data__http *pss = (struct per_session_data__http *)user; unsigned char buffer[4096 + LWS_PRE]; unsigned long amount, file_len, sent; char leaf_path[1024]; const char *mimetype; char *other_headers; unsigned char *end; struct timeval tv; unsigned char *p; char buf[256]; char b64[64]; int n, m; #ifdef EXTERNAL_POLL struct lws_pollargs *pa = (struct lws_pollargs *)in; #endif switch (reason) { case LWS_CALLBACK_HTTP: if (debug_level & LLL_INFO) { dump_handshake_info(wsi); /* dump the individual URI Arg parameters */ n = 0; while (lws_hdr_copy_fragment(wsi, buf, sizeof(buf), WSI_TOKEN_HTTP_URI_ARGS, n) > 0) { lwsl_notice("URI Arg %d: %s\n", ++n, buf); } } { char name[100], rip[50]; lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), name, sizeof(name), rip, sizeof(rip)); sprintf(buf, "%s (%s)", name, rip); lwsl_notice("HTTP connect from %s\n", buf); } if (len < 1) { lws_return_http_status(wsi, HTTP_STATUS_BAD_REQUEST, NULL); goto try_to_reuse; } /* this example server has no concept of directories */ if (strchr((const char *)in + 1, '/')) { lws_return_http_status(wsi, HTTP_STATUS_FORBIDDEN, NULL); goto try_to_reuse; } /* if a legal POST URL, let it continue and accept data */ if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI)) return 0; /* check for the "send a big file by hand" example case */ if (!strcmp((const char *)in, "/leaf.jpg")) { if (strlen(resource_path) > sizeof(leaf_path) - 10) return -1; sprintf(leaf_path, "%s/leaf.jpg", resource_path); /* well, let's demonstrate how to send the hard way */ p = buffer + LWS_PRE; end = p + sizeof(buffer) - LWS_PRE; pss->fd = lws_plat_file_open(wsi, leaf_path, &file_len, LWS_O_RDONLY); if (pss->fd == LWS_INVALID_FILE) { lwsl_err("faild to open file %s\n", leaf_path); return -1; } /* * we will send a big jpeg file, but it could be * anything. Set the Content-Type: appropriately * so the browser knows what to do with it. * * Notice we use the APIs to build the header, which * will do the right thing for HTTP 1/1.1 and HTTP2 * depending on what connection it happens to be working * on */ if (lws_add_http_header_status(wsi, 200, &p, end)) return 1; if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_SERVER, (unsigned char *)"libwebsockets", 13, &p, end)) return 1; if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)"image/jpeg", 10, &p, end)) return 1; if (lws_add_http_header_content_length(wsi, file_len, &p, end)) return 1; if (lws_finalize_http_header(wsi, &p, end)) return 1; /* * send the http headers... * this won't block since it's the first payload sent * on the connection since it was established * (too small for partial) * * Notice they are sent using LWS_WRITE_HTTP_HEADERS * which also means you can't send body too in one step, * this is mandated by changes in HTTP2 */ *p = '\0'; lwsl_info("%s\n", buffer + LWS_PRE); n = lws_write(wsi, buffer + LWS_PRE, p - (buffer + LWS_PRE), LWS_WRITE_HTTP_HEADERS); if (n < 0) { lws_plat_file_close(wsi, pss->fd); return -1; } /* * book us a LWS_CALLBACK_HTTP_WRITEABLE callback */ lws_callback_on_writable(wsi); break; } /* if not, send a file the easy way */ strcpy(buf, resource_path); if (strcmp(in, "/")) { if (*((const char *)in) != '/') strcat(buf, "/"); strncat(buf, in, sizeof(buf) - strlen(resource_path)); } else /* default file to serve */ strcat(buf, "/test.html"); buf[sizeof(buf) - 1] = '\0'; /* refuse to serve files we don't understand */ mimetype = get_mimetype(buf); if (!mimetype) { lwsl_err("Unknown mimetype for %s\n", buf); lws_return_http_status(wsi, HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, NULL); return -1; } /* demonstrates how to set a cookie on / */ other_headers = leaf_path; p = (unsigned char *)leaf_path; if (!strcmp((const char *)in, "/") && !lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE)) { /* this isn't very unguessable but it'll do for us */ gettimeofday(&tv, NULL); n = sprintf(b64, "test=LWS_%u_%u_COOKIE;Max-Age=360000", (unsigned int)tv.tv_sec, (unsigned int)tv.tv_usec); if (lws_add_http_header_by_name(wsi, (unsigned char *)"set-cookie:", (unsigned char *)b64, n, &p, (unsigned char *)leaf_path + sizeof(leaf_path))) return 1; } if (lws_is_ssl(wsi) && lws_add_http_header_by_name(wsi, (unsigned char *) "Strict-Transport-Security:", (unsigned char *) "max-age=15768000 ; " "includeSubDomains", 36, &p, (unsigned char *)leaf_path + sizeof(leaf_path))) return 1; n = (char *)p - leaf_path; n = lws_serve_http_file(wsi, buf, mimetype, other_headers, n); if (n < 0 || ((n > 0) && lws_http_transaction_completed(wsi))) return -1; /* error or can't reuse connection: close the socket */ /* * notice that the sending of the file completes asynchronously, * we'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION callback when * it's done */ break; case LWS_CALLBACK_HTTP_BODY: strncpy(buf, in, 20); buf[20] = '\0'; if (len < 20) buf[len] = '\0'; lwsl_notice("LWS_CALLBACK_HTTP_BODY: %s... len %d\n", (const char *)buf, (int)len); break; case LWS_CALLBACK_HTTP_BODY_COMPLETION: lwsl_notice("LWS_CALLBACK_HTTP_BODY_COMPLETION\n"); /* the whole of the sent body arrived, close or reuse the connection */ lws_return_http_status(wsi, HTTP_STATUS_OK, NULL); goto try_to_reuse; case LWS_CALLBACK_HTTP_FILE_COMPLETION: goto try_to_reuse; case LWS_CALLBACK_HTTP_WRITEABLE: lwsl_info("LWS_CALLBACK_HTTP_WRITEABLE\n"); if (pss->fd == LWS_INVALID_FILE) goto try_to_reuse; /* * we can send more of whatever it is we were sending */ sent = 0; do { /* we'd like the send this much */ n = sizeof(buffer) - LWS_PRE; /* but if the peer told us he wants less, we can adapt */ m = lws_get_peer_write_allowance(wsi); /* -1 means not using a protocol that has this info */ if (m == 0) /* right now, peer can't handle anything */ goto later; if (m != -1 && m < n) /* he couldn't handle that much */ n = m; n = lws_plat_file_read(wsi, pss->fd, &amount, buffer + LWS_PRE, n); /* problem reading, close conn */ if (n < 0) { lwsl_err("problem reading file\n"); goto bail; } n = (int)amount; /* sent it all, close conn */ if (n == 0) goto penultimate; /* * To support HTTP2, must take care about preamble space * * identification of when we send the last payload frame * is handled by the library itself if you sent a * content-length header */ m = lws_write(wsi, buffer + LWS_PRE, n, LWS_WRITE_HTTP); if (m < 0) { lwsl_err("write failed\n"); /* write failed, close conn */ goto bail; } if (m) /* while still active, extend timeout */ lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, 5); sent += m; } while (!lws_send_pipe_choked(wsi) && (sent < 1024 * 1024)); later: lws_callback_on_writable(wsi); break; penultimate: lws_plat_file_close(wsi, pss->fd); pss->fd = LWS_INVALID_FILE; goto try_to_reuse; bail: lws_plat_file_close(wsi, pss->fd); return -1; /* * callback for confirming to continue with client IP appear in * protocol 0 callback since no websocket protocol has been agreed * yet. You can just ignore this if you won't filter on client IP * since the default uhandled callback return is 0 meaning let the * connection continue. */ case LWS_CALLBACK_FILTER_NETWORK_CONNECTION: /* if we returned non-zero from here, we kill the connection */ break; /* * callbacks for managing the external poll() array appear in * protocol 0 callback */ case LWS_CALLBACK_LOCK_POLL: /* * lock mutex to protect pollfd state * called before any other POLL related callback * if protecting wsi lifecycle change, len == 1 */ test_server_lock(len); break; case LWS_CALLBACK_UNLOCK_POLL: /* * unlock mutex to protect pollfd state when * called after any other POLL related callback * if protecting wsi lifecycle change, len == 1 */ test_server_unlock(len); break; #ifdef EXTERNAL_POLL case LWS_CALLBACK_ADD_POLL_FD: if (count_pollfds >= max_poll_elements) { lwsl_err("LWS_CALLBACK_ADD_POLL_FD: too many sockets to track\n"); return 1; } fd_lookup[pa->fd] = count_pollfds; pollfds[count_pollfds].fd = pa->fd; pollfds[count_pollfds].events = pa->events; pollfds[count_pollfds++].revents = 0; break; case LWS_CALLBACK_DEL_POLL_FD: if (!--count_pollfds) break; m = fd_lookup[pa->fd]; /* have the last guy take up the vacant slot */ pollfds[m] = pollfds[count_pollfds]; fd_lookup[pollfds[count_pollfds].fd] = m; break; case LWS_CALLBACK_CHANGE_MODE_POLL_FD: pollfds[fd_lookup[pa->fd]].events = pa->events; break; #endif case LWS_CALLBACK_GET_THREAD_ID: /* * if you will call "lws_callback_on_writable" * from a different thread, return the caller thread ID * here so lws can use this information to work out if it * should signal the poll() loop to exit and restart early */ /* return pthread_getthreadid_np(); */ break; default: break; } return 0; /* if we're on HTTP1.1 or 2.0, will keep the idle connection alive */ try_to_reuse: if (lws_http_transaction_completed(wsi)) return -1; return 0; }
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; }
int lws_client_socket_service(struct lws_context *context, struct lws *wsi, struct lws_pollfd *pollfd) { struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; char *p = (char *)&pt->serv_buf[0]; char *sb = p; unsigned char c; int n, len; switch (wsi->mode) { case LWSCM_WSCL_WAITING_CONNECT: /* * we are under PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE * timeout protection set in client-handshake.c */ if (!lws_client_connect_2(wsi)) { /* closed */ lwsl_client("closed\n"); return -1; } /* either still pending connection, or changed mode */ return 0; case LWSCM_WSCL_WAITING_PROXY_REPLY: /* handle proxy hung up on us */ if (pollfd->revents & LWS_POLLHUP) { lwsl_warn("Proxy connection %p (fd=%d) dead\n", (void *)wsi, pollfd->fd); lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); return 0; } n = recv(wsi->sock, sb, LWS_MAX_SOCKET_IO_BUF, 0); if (n < 0) { if (LWS_ERRNO == LWS_EAGAIN) { lwsl_debug("Proxy read returned EAGAIN... retrying\n"); return 0; } lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); lwsl_err("ERROR reading from proxy socket\n"); return 0; } pt->serv_buf[13] = '\0'; if (strcmp(sb, "HTTP/1.0 200 ") && strcmp(sb, "HTTP/1.1 200 ")) { lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); lwsl_err("ERROR proxy: %s\n", sb); return 0; } /* clear his proxy connection timeout */ lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); /* fallthru */ case LWSCM_WSCL_ISSUE_HANDSHAKE: /* * we are under PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE * timeout protection set in client-handshake.c * * take care of our lws_callback_on_writable * happening at a time when there's no real connection yet */ if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) return -1; lws_libev_io(wsi, LWS_EV_STOP | LWS_EV_WRITE); #ifdef LWS_OPENSSL_SUPPORT /* we can retry this... just cook the SSL BIO the first time */ if (wsi->use_ssl && !wsi->ssl) { #if defined(CYASSL_SNI_HOST_NAME) || defined(WOLFSSL_SNI_HOST_NAME) || defined(SSL_CTRL_SET_TLSEXT_HOSTNAME) const char *hostname = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_HOST); #endif wsi->ssl = SSL_new(context->ssl_client_ctx); #ifndef USE_WOLFSSL SSL_set_mode(wsi->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); #endif /* * use server name indication (SNI), if supported, * when establishing connection */ #ifdef USE_WOLFSSL #ifdef USE_OLD_CYASSL #ifdef CYASSL_SNI_HOST_NAME CyaSSL_UseSNI(wsi->ssl, CYASSL_SNI_HOST_NAME, hostname, strlen(hostname)); #endif #else #ifdef WOLFSSL_SNI_HOST_NAME wolfSSL_UseSNI(wsi->ssl, WOLFSSL_SNI_HOST_NAME, hostname, strlen(hostname)); #endif #endif #else #ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME SSL_set_tlsext_host_name(wsi->ssl, hostname); #endif #endif #ifdef USE_WOLFSSL /* * wolfSSL/CyaSSL does certificate verification differently * from OpenSSL. * If we should ignore the certificate, we need to set * this before SSL_new and SSL_connect is called. * Otherwise the connect will simply fail with error * code -155 */ #ifdef USE_OLD_CYASSL if (wsi->use_ssl == 2) CyaSSL_set_verify(wsi->ssl, SSL_VERIFY_NONE, NULL); #else if (wsi->use_ssl == 2) wolfSSL_set_verify(wsi->ssl, SSL_VERIFY_NONE, NULL); #endif #endif /* USE_WOLFSSL */ wsi->client_bio = BIO_new_socket(wsi->sock, BIO_NOCLOSE); SSL_set_bio(wsi->ssl, wsi->client_bio, wsi->client_bio); #ifdef USE_WOLFSSL #ifdef USE_OLD_CYASSL CyaSSL_set_using_nonblock(wsi->ssl, 1); #else wolfSSL_set_using_nonblock(wsi->ssl, 1); #endif #else BIO_set_nbio(wsi->client_bio, 1); /* nonblocking */ #endif SSL_set_ex_data(wsi->ssl, openssl_websocket_private_data_index, context); } if (wsi->use_ssl) { lws_latency_pre(context, wsi); n = SSL_connect(wsi->ssl); lws_latency(context, wsi, "SSL_connect LWSCM_WSCL_ISSUE_HANDSHAKE", n, n > 0); if (n < 0) { n = SSL_get_error(wsi->ssl, n); if (n == SSL_ERROR_WANT_READ) goto some_wait; if (n == SSL_ERROR_WANT_WRITE) { /* * wants us to retry connect due to * state of the underlying ssl layer... * but since it may be stalled on * blocked write, no incoming data may * arrive to trigger the retry. * Force (possibly many times if the SSL * state persists in returning the * condition code, but other sockets * are getting serviced inbetweentimes) * us to get called back when writable. */ lwsl_info("%s: WANT_WRITE... retrying\n", __func__); lws_callback_on_writable(wsi); some_wait: wsi->mode = LWSCM_WSCL_WAITING_SSL; return 0; /* no error */ } n = -1; } if (n <= 0) { /* * retry if new data comes until we * run into the connection timeout or win */ n = ERR_get_error(); if (n != SSL_ERROR_NONE) { lwsl_err("SSL connect error %lu: %s\n", n, ERR_error_string(n, sb)); return 0; } } } else wsi->ssl = NULL; /* fallthru */ case LWSCM_WSCL_WAITING_SSL: if (wsi->use_ssl) { if (wsi->mode == LWSCM_WSCL_WAITING_SSL) { lws_latency_pre(context, wsi); n = SSL_connect(wsi->ssl); lws_latency(context, wsi, "SSL_connect LWSCM_WSCL_WAITING_SSL", n, n > 0); if (n < 0) { n = SSL_get_error(wsi->ssl, n); if (n == SSL_ERROR_WANT_READ) goto some_wait; if (n == SSL_ERROR_WANT_WRITE) { /* * wants us to retry connect due to * state of the underlying ssl layer... * but since it may be stalled on * blocked write, no incoming data may * arrive to trigger the retry. * Force (possibly many times if the SSL * state persists in returning the * condition code, but other sockets * are getting serviced inbetweentimes) * us to get called back when writable. */ lwsl_info("SSL_connect WANT_WRITE... retrying\n"); lws_callback_on_writable(wsi); goto some_wait; } n = -1; } if (n <= 0) { /* * retry if new data comes until we * run into the connection timeout or win */ n = ERR_get_error(); if (n != SSL_ERROR_NONE) { lwsl_err("SSL connect error %lu: %s\n", n, ERR_error_string(n, sb)); return 0; } } } #ifndef USE_WOLFSSL /* * See comment above about wolfSSL certificate * verification */ lws_latency_pre(context, wsi); n = SSL_get_verify_result(wsi->ssl); lws_latency(context, wsi, "SSL_get_verify_result LWS_CONNMODE..HANDSHAKE", n, n > 0); if (n != X509_V_OK) { if ((n == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT || n == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) && wsi->use_ssl == 2) { lwsl_notice("accepting self-signed certificate\n"); } else { lwsl_err("server's cert didn't look good, X509_V_ERR = %d: %s\n", n, ERR_error_string(n, sb)); lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); return 0; } } #endif /* USE_WOLFSSL */ } else wsi->ssl = NULL; #endif wsi->mode = LWSCM_WSCL_ISSUE_HANDSHAKE2; lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CLIENT_HS_SEND, AWAITING_TIMEOUT); /* fallthru */ case LWSCM_WSCL_ISSUE_HANDSHAKE2: p = lws_generate_client_handshake(wsi, p); if (p == NULL) { lwsl_err("Failed to generate handshake for client\n"); lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); return 0; } /* send our request to the server */ lws_latency_pre(context, wsi); n = lws_ssl_capable_write(wsi, (unsigned char *)sb, p - sb); lws_latency(context, wsi, "send lws_issue_raw", n, n == p - sb); switch (n) { case LWS_SSL_CAPABLE_ERROR: lwsl_debug("ERROR writing to client socket\n"); lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); return 0; case LWS_SSL_CAPABLE_MORE_SERVICE: lws_callback_on_writable(wsi); break; } wsi->u.hdr.parser_state = WSI_TOKEN_NAME_PART; wsi->u.hdr.lextable_pos = 0; wsi->mode = LWSCM_WSCL_WAITING_SERVER_REPLY; lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE, AWAITING_TIMEOUT); break; case LWSCM_WSCL_WAITING_SERVER_REPLY: /* handle server hung up on us */ if (pollfd->revents & LWS_POLLHUP) { lwsl_debug("Server connection %p (fd=%d) dead\n", (void *)wsi, pollfd->fd); goto bail3; } if (!(pollfd->revents & LWS_POLLIN)) break; /* interpret the server response */ /* * HTTP/1.1 101 Switching Protocols * Upgrade: websocket * Connection: Upgrade * Sec-WebSocket-Accept: me89jWimTRKTWwrS3aRrL53YZSo= * Sec-WebSocket-Nonce: AQIDBAUGBwgJCgsMDQ4PEC== * Sec-WebSocket-Protocol: chat */ /* * we have to take some care here to only take from the * socket bytewise. The browser may (and has been seen to * in the case that onopen() performs websocket traffic) * coalesce both handshake response and websocket traffic * in one packet, since at that point the connection is * definitively ready from browser pov. */ len = 1; while (wsi->u.hdr.parser_state != WSI_PARSING_COMPLETE && len > 0) { n = lws_ssl_capable_read(wsi, &c, 1); lws_latency(context, wsi, "send lws_issue_raw", n, n == 1); switch (n) { case 0: case LWS_SSL_CAPABLE_ERROR: goto bail3; case LWS_SSL_CAPABLE_MORE_SERVICE: return 0; } if (lws_parse(wsi, c)) { lwsl_warn("problems parsing header\n"); goto bail3; } } /* * hs may also be coming in multiple packets, there is a 5-sec * libwebsocket timeout still active here too, so if parsing did * not complete just wait for next packet coming in this state */ if (wsi->u.hdr.parser_state != WSI_PARSING_COMPLETE) break; /* * otherwise deal with the handshake. If there's any * packet traffic already arrived we'll trigger poll() again * right away and deal with it that way */ return lws_client_interpret_server_handshake(wsi); bail3: lwsl_info("closing conn at LWS_CONNMODE...SERVER_REPLY\n"); lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); return -1; case LWSCM_WSCL_WAITING_EXTENSION_CONNECT: lwsl_ext("LWSCM_WSCL_WAITING_EXTENSION_CONNECT\n"); break; case LWSCM_WSCL_PENDING_CANDIDATE_CHILD: lwsl_ext("LWSCM_WSCL_PENDING_CANDIDATE_CHILD\n"); break; default: break; } return 0; }
static int callback_minimal(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct per_session_data__minimal *pss = (struct per_session_data__minimal *)user; struct per_vhost_data__minimal *vhd = (struct per_vhost_data__minimal *) lws_protocol_vh_priv_get(lws_get_vhost(wsi), lws_get_protocol(wsi)); const struct lws_protocol_vhost_options *pvo; const struct msg *pmsg; void *retval; int n, m, r = 0; switch (reason) { case LWS_CALLBACK_PROTOCOL_INIT: /* create our per-vhost struct */ vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi), lws_get_protocol(wsi), sizeof(struct per_vhost_data__minimal)); if (!vhd) return 1; pthread_mutex_init(&vhd->lock_ring, NULL); /* recover the pointer to the globals struct */ pvo = lws_pvo_search( (const struct lws_protocol_vhost_options *)in, "config"); if (!pvo || !pvo->value) { lwsl_err("%s: Can't find \"config\" pvo\n", __func__); return 1; } vhd->config = pvo->value; vhd->context = lws_get_context(wsi); vhd->protocol = lws_get_protocol(wsi); vhd->vhost = lws_get_vhost(wsi); vhd->ring = lws_ring_create(sizeof(struct msg), 8, __minimal_destroy_message); if (!vhd->ring) { lwsl_err("%s: failed to create ring\n", __func__); return 1; } /* start the content-creating threads */ for (n = 0; n < (int)LWS_ARRAY_SIZE(vhd->pthread_spam); n++) if (pthread_create(&vhd->pthread_spam[n], NULL, thread_spam, vhd)) { lwsl_err("thread creation failed\n"); r = 1; goto init_fail; } break; case LWS_CALLBACK_PROTOCOL_DESTROY: init_fail: vhd->finished = 1; for (n = 0; n < (int)LWS_ARRAY_SIZE(vhd->pthread_spam); n++) if (vhd->pthread_spam[n]) pthread_join(vhd->pthread_spam[n], &retval); if (vhd->ring) lws_ring_destroy(vhd->ring); pthread_mutex_destroy(&vhd->lock_ring); break; case LWS_CALLBACK_ESTABLISHED: /* add ourselves to the list of live pss held in the vhd */ lws_ll_fwd_insert(pss, pss_list, vhd->pss_list); pss->tail = lws_ring_get_oldest_tail(vhd->ring); pss->wsi = wsi; break; case LWS_CALLBACK_CLOSED: /* remove our closing pss from the list of live pss */ lws_ll_fwd_remove(struct per_session_data__minimal, pss_list, pss, vhd->pss_list); break; case LWS_CALLBACK_SERVER_WRITEABLE: pthread_mutex_lock(&vhd->lock_ring); /* --------- ring lock { */ pmsg = lws_ring_get_element(vhd->ring, &pss->tail); if (!pmsg) { pthread_mutex_unlock(&vhd->lock_ring); /* } ring lock ------- */ break; } /* notice we allowed for LWS_PRE in the payload already */ m = lws_write(wsi, ((unsigned char *)pmsg->payload) + LWS_PRE, pmsg->len, LWS_WRITE_TEXT); if (m < (int)pmsg->len) { pthread_mutex_unlock(&vhd->lock_ring); /* } ring lock ------- */ lwsl_err("ERROR %d writing to ws socket\n", m); return -1; } lws_ring_consume_and_update_oldest_tail( vhd->ring, /* lws_ring object */ struct per_session_data__minimal, /* type of objects with tails */ &pss->tail, /* tail of guy doing the consuming */ 1, /* number of payload objects being consumed */ vhd->pss_list, /* head of list of objects with tails */ tail, /* member name of tail in objects with tails */ pss_list /* member name of next object in objects with tails */ ); /* more to do? */ if (lws_ring_get_element(vhd->ring, &pss->tail)) /* come back as soon as we can write more */ lws_callback_on_writable(pss->wsi); pthread_mutex_unlock(&vhd->lock_ring); /* } ring lock ------- */ break; case LWS_CALLBACK_RECEIVE: break; case LWS_CALLBACK_EVENT_WAIT_CANCELLED: /* * When the "spam" threads add a message to the ringbuffer, * they create this event in the lws service thread context * using lws_cancel_service(). * * We respond by scheduling a writable callback for all * connected clients. */ lws_start_foreach_llp(struct per_session_data__minimal **, ppss, vhd->pss_list) { lws_callback_on_writable((*ppss)->wsi); } lws_end_foreach_llp(ppss, pss_list); break; default: break; } return r; }
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_minimal_broker(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct pss *pss = (struct pss *)user; int n; switch (reason) { case LWS_CALLBACK_PROTOCOL_INIT: goto try; case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: lwsl_err("CLIENT_CONNECTION_ERROR: %s\n", in ? (char *)in : "(null)"); client_wsi = NULL; lws_timed_callback_vh_protocol(lws_get_vhost(wsi), lws_get_protocol(wsi), LWS_CALLBACK_USER, 1); break; /* --- client callbacks --- */ case LWS_CALLBACK_CLIENT_ESTABLISHED: lwsl_user("%s: established\n", __func__); lws_set_timer_usecs(wsi, 5 * LWS_USEC_PER_SEC); break; case LWS_CALLBACK_CLIENT_WRITEABLE: if (pss->send_a_ping) { uint8_t ping[LWS_PRE + 125]; int m; pss->send_a_ping = 0; n = 0; if (!zero_length_ping) n = lws_snprintf((char *)ping + LWS_PRE, 125, "ping body!"); lwsl_user("Sending PING %d...\n", n); m = lws_write(wsi, ping + LWS_PRE, n, LWS_WRITE_PING); if (m < n) { lwsl_err("sending ping failed: %d\n", m); return -1; } lws_callback_on_writable(wsi); } break; case LWS_CALLBACK_WS_CLIENT_DROP_PROTOCOL: client_wsi = NULL; lws_timed_callback_vh_protocol(lws_get_vhost(wsi), lws_get_protocol(wsi), LWS_CALLBACK_USER, 1); break; case LWS_CALLBACK_CLIENT_RECEIVE_PONG: lwsl_user("LWS_CALLBACK_CLIENT_RECEIVE_PONG\n"); lwsl_hexdump_notice(in, len); break; case LWS_CALLBACK_TIMER: /* we want to send a ws PING every few seconds */ pss->send_a_ping = 1; lws_callback_on_writable(wsi); lws_set_timer_usecs(wsi, 5 * LWS_USEC_PER_SEC); break; /* rate-limited client connect retries */ case LWS_CALLBACK_USER: lwsl_notice("%s: LWS_CALLBACK_USER\n", __func__); try: if (connect_client()) lws_timed_callback_vh_protocol(lws_get_vhost(wsi), lws_get_protocol(wsi), LWS_CALLBACK_USER, 1); break; default: break; } return lws_callback_http_dummy(wsi, reason, user, in, len); } static const struct lws_protocols protocols[] = { { "lws-ping-test", callback_minimal_broker, sizeof(struct pss), 0, }, { NULL, NULL, 0, 0 } }; static void sigint_handler(int sig) { interrupted = 1; } int main(int argc, const char **argv) { struct lws_context_creation_info info; const char *p; int n = 0, logs = LLL_USER | LLL_ERR | LLL_WARN | LLL_NOTICE /* for LLL_ verbosity above NOTICE to be built into lws, * lws must have been configured and built with * -DCMAKE_BUILD_TYPE=DEBUG instead of =RELEASE */ /* | LLL_INFO */ /* | LLL_PARSER */ /* | LLL_HEADER */ /* | LLL_EXT */ /* | LLL_CLIENT */ /* | LLL_LATENCY */ /* | LLL_DEBUG */; signal(SIGINT, sigint_handler); if ((p = lws_cmdline_option(argc, argv, "-d"))) logs = atoi(p); lws_set_log_level(logs, NULL); lwsl_user("LWS minimal ws client PING\n"); memset(&info, 0, sizeof info); /* otherwise uninitialized garbage */ info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; info.port = CONTEXT_PORT_NO_LISTEN; /* we do not run any server */ info.protocols = protocols; #if defined(LWS_WITH_MBEDTLS) /* * OpenSSL uses the system trust store. mbedTLS has to be told which * CA to trust explicitly. */ info.client_ssl_ca_filepath = "./libwebsockets.org.cer"; #endif if (lws_cmdline_option(argc, argv, "-z")) zero_length_ping = 1; if ((p = lws_cmdline_option(argc, argv, "--protocol"))) pro = p; if ((p = lws_cmdline_option(argc, argv, "--server"))) { server_address = p; pro = "lws-minimal"; ssl_connection |= LCCSCF_ALLOW_SELFSIGNED; } if ((p = lws_cmdline_option(argc, argv, "--port"))) port = atoi(p); /* * since we know this lws context is only ever going to be used with * one client wsis / fds / sockets at a time, let lws know it doesn't * have to use the default allocations for fd tables up to ulimit -n. * It will just allocate for 1 internal and 1 (+ 1 http2 nwsi) that we * will use. */ info.fd_limit_per_thread = 1 + 1 + 1; context = lws_create_context(&info); if (!context) { lwsl_err("lws init failed\n"); return 1; } while (n >= 0 && !interrupted) n = lws_service(context, 1000); lws_context_destroy(context); lwsl_user("Completed\n"); return 0; }
static int callback_lws_mirror(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct per_session_data__lws_mirror *pss = (struct per_session_data__lws_mirror *)user; struct per_vhost_data__lws_mirror *v = (struct per_vhost_data__lws_mirror *) lws_protocol_vh_priv_get(lws_get_vhost(wsi), lws_get_protocol(wsi)); int n, m; switch (reason) { case LWS_CALLBACK_ESTABLISHED: lwsl_info("%s: LWS_CALLBACK_ESTABLISHED\n", __func__); pss->ringbuffer_tail = v->ringbuffer_head; pss->wsi = wsi; break; case LWS_CALLBACK_PROTOCOL_INIT: /* per vhost */ lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi), lws_get_protocol(wsi), sizeof(struct per_vhost_data__lws_mirror)); break; case LWS_CALLBACK_PROTOCOL_DESTROY: /* per vhost */ if (!v) break; lwsl_info("%s: mirror protocol cleaning up %p\n", __func__, v); for (n = 0; n < ARRAY_SIZE(v->ringbuffer); n++) if (v->ringbuffer[n].payload) { free(v->ringbuffer[n].payload); v->ringbuffer[n].payload = NULL; } break; case LWS_CALLBACK_SERVER_WRITEABLE: while (pss->ringbuffer_tail != v->ringbuffer_head) { m = v->ringbuffer[pss->ringbuffer_tail].len; n = lws_write(wsi, (unsigned char *) v->ringbuffer[pss->ringbuffer_tail].payload + LWS_PRE, m, LWS_WRITE_TEXT); if (n < 0) { lwsl_err("ERROR %d writing to mirror socket\n", n); return -1; } if (n < m) lwsl_err("mirror partial write %d vs %d\n", n, m); if (pss->ringbuffer_tail == (MAX_MESSAGE_QUEUE - 1)) pss->ringbuffer_tail = 0; else pss->ringbuffer_tail++; if (((v->ringbuffer_head - pss->ringbuffer_tail) & (MAX_MESSAGE_QUEUE - 1)) == (MAX_MESSAGE_QUEUE - 15)) lws_rx_flow_allow_all_protocol(lws_get_context(wsi), lws_get_protocol(wsi)); if (lws_send_pipe_choked(wsi)) { lws_callback_on_writable(wsi); break; } } break; case LWS_CALLBACK_RECEIVE: if (((v->ringbuffer_head - pss->ringbuffer_tail) & (MAX_MESSAGE_QUEUE - 1)) == (MAX_MESSAGE_QUEUE - 1)) { lwsl_err("dropping!\n"); goto choke; } if (v->ringbuffer[v->ringbuffer_head].payload) free(v->ringbuffer[v->ringbuffer_head].payload); v->ringbuffer[v->ringbuffer_head].payload = malloc(LWS_PRE + len); v->ringbuffer[v->ringbuffer_head].len = len; memcpy((char *)v->ringbuffer[v->ringbuffer_head].payload + LWS_PRE, in, len); if (v->ringbuffer_head == (MAX_MESSAGE_QUEUE - 1)) v->ringbuffer_head = 0; else v->ringbuffer_head++; if (((v->ringbuffer_head - pss->ringbuffer_tail) & (MAX_MESSAGE_QUEUE - 1)) != (MAX_MESSAGE_QUEUE - 2)) goto done; choke: lwsl_debug("LWS_CALLBACK_RECEIVE: throttling %p\n", wsi); lws_rx_flow_control(wsi, 0); done: lws_callback_on_writable_all_protocol(lws_get_context(wsi), lws_get_protocol(wsi)); break; 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_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[LWS_PRE + 512]; unsigned char *p, *start, *end; int n; switch (reason) { case LWS_CALLBACK_HTTP_BODY: lwsl_debug("LWS_CALLBACK_HTTP_BODY: len %d\n", (int)len); strncpy(pss->post_string, in, sizeof (pss->post_string) -1); pss->post_string[sizeof(pss->post_string) - 1] = '\0'; if (len < sizeof(pss->post_string) - 1) pss->post_string[len] = '\0'; break; case LWS_CALLBACK_HTTP_WRITEABLE: 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_BODY_COMPLETION: lwsl_debug("LWS_CALLBACK_HTTP_BODY_COMPLETION\n"); /* * the whole of the sent body arrived, * respond to the client with a redirect to show the * results */ pss->result_len = sprintf((char *)pss->result + LWS_PRE, "<html><body><h1>Form results</h1>'%s'<br>" "</body></html>", pss->post_string); p = buffer + LWS_PRE; start = p; end = p + sizeof(buffer) - LWS_PRE; if (lws_add_http_header_status(wsi, 200, &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; if (lws_add_http_header_content_length(wsi, pss->result_len, &p, end)) return 1; if (lws_finalize_http_header(wsi, &p, end)) return 1; n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS); if (n < 0) return 1; /* * send the payload next time, in case would block after * headers */ lws_callback_on_writable(wsi); break; default: break; } return 0; try_to_reuse: if (lws_http_transaction_completed(wsi)) return -1; return 0; }
static int callback_lws_mirror(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + 4096]; unsigned int rands[4]; int l = 0; int n; switch (reason) { case LWS_CALLBACK_CLIENT_ESTABLISHED: lwsl_notice("mirror: LWS_CALLBACK_CLIENT_ESTABLISHED\n"); lws_get_random(lws_get_context(wsi), rands, sizeof(rands[0])); mirror_lifetime = 16384 + (rands[0] & 65535); /* useful to test single connection stability */ if (longlived) mirror_lifetime += 500000; lwsl_info("opened mirror connection with " "%d lifetime\n", mirror_lifetime); /* * mirror_lifetime is decremented each send, when it reaches * zero the connection is closed in the send callback. * When the close callback comes, wsi_mirror is set to NULL * so a new connection will be opened * * start the ball rolling, * LWS_CALLBACK_CLIENT_WRITEABLE will come next service */ lws_callback_on_writable(wsi); break; case LWS_CALLBACK_CLOSED: lwsl_notice("mirror: LWS_CALLBACK_CLOSED mirror_lifetime=%d\n", mirror_lifetime); wsi_mirror = NULL; break; case LWS_CALLBACK_CLIENT_WRITEABLE: for (n = 0; n < 1; n++) { lws_get_random(lws_get_context(wsi), rands, sizeof(rands)); l += sprintf((char *)&buf[LWS_SEND_BUFFER_PRE_PADDING + l], "c #%06X %d %d %d;", (int)rands[0] & 0xffffff, (int)rands[1] % 500, (int)rands[2] % 250, (int)rands[3] % 24); } n = lws_write(wsi, &buf[LWS_SEND_BUFFER_PRE_PADDING], l, opts | LWS_WRITE_TEXT); if (n < 0) return -1; if (n < l) { lwsl_err("Partial write LWS_CALLBACK_CLIENT_WRITEABLE\n"); return -1; } mirror_lifetime--; if (!mirror_lifetime) { lwsl_info("closing mirror session\n"); return -1; } /* get notified as soon as we can write again */ lws_callback_on_writable(wsi); break; default: break; } return 0; }
// This static function handles all callbacks coming in and when context is services via lws_service // return value of -1, closes the connection. int FNetworkFileServerHttp::CallBack_HTTP( struct lws *Wsi, enum lws_callback_reasons Reason, void *User, void *In, size_t Len) { struct lws_context *Context = lws_get_context(Wsi); PerSessionData* BufferInfo = (PerSessionData*)User; FNetworkFileServerHttp* Server = (FNetworkFileServerHttp*)lws_context_user(Context); switch (Reason) { case LWS_CALLBACK_HTTP: // hang on to socket even if there's no data for atleast 60 secs. lws_set_timeout(Wsi, NO_PENDING_TIMEOUT, 60); /* if it was not legal POST URL, let it continue and accept data */ if (!lws_hdr_total_length(Wsi, WSI_TOKEN_POST_URI)) { char *requested_uri = (char *) In; // client request the base page. e.g http://unrealfileserver:port/ // just return a banner, probably add some more information, e,g Version, Config, Game. etc. if ( FCString::Strcmp(ANSI_TO_TCHAR(requested_uri), TEXT("/")) == 0 ) { TCHAR Buffer[1024]; TCHAR ServerBanner[] = TEXT("<HTML>This is Unreal File Server</HTML>"); int x = FCString::Sprintf( Buffer, TEXT("HTTP/1.0 200 OK\x0d\x0a") TEXT("Server: Unreal File Server\x0d\x0a") TEXT("Connection: close\x0d\x0a") TEXT("Content-Type: text/html; charset=utf-8\x0d\x0a") TEXT("Content-Length: %u\x0d\x0a\x0d\x0a%s"), FCString::Strlen(ServerBanner), ServerBanner ); // very small data being sent, its fine to just send. lws_write(Wsi,(unsigned char*)TCHAR_TO_ANSI(Buffer),FCStringAnsi::Strlen(TCHAR_TO_ANSI(Buffer)), LWS_WRITE_HTTP); } else { // client has asked for a file. ( only html/js files are served.) // what type is being served. FString FilePath = FPaths::GameDir() / TEXT("Binaries/HTML5") + FString((ANSICHAR*)In); TCHAR Mime[512]; if ( FilePath.Contains(".js")) { FCStringWide::Strcpy(Mime,TEXT("application/javascript;charset=UTF-8")); } else { FCStringWide::Strcpy(Mime,TEXT("text/html;charset=UTF-8")); } UE_LOG(LogFileServer, Warning, TEXT("HTTP Serving file %s with mime %s "), *FilePath, (Mime)); FString AbsoluteFilePath = FPaths::ConvertRelativePathToFull(FilePath); AbsoluteFilePath.ReplaceInline(TEXT("/"),TEXT("\\")); // we are going to read the complete file in memory and then serve it in batches. // rather than reading and sending in batches because Unreal NFS servers are not running in memory // constrained env and the added complexity is not worth it. TArray<uint8> FileData; FFileHelper::LoadFileToArray(FileData, *AbsoluteFilePath, FILEREAD_Silent); if (FileData.Num() == 0) { // umm. we didn't find file, we should tell the client that we couldn't find it. // send 404. char Header[]= "HTTP/1.1 404 Not Found\x0d\x0a" "Server: Unreal File Server\x0d\x0a" "Connection: close\x0d\x0a"; lws_write(Wsi,(unsigned char*)Header,FCStringAnsi::Strlen(Header), LWS_WRITE_HTTP); // chug along, client will close the connection. break; } // file up the header. TCHAR Header[1024]; int Length = 0; if (FilePath.Contains("gz")) { Length = FCString::Sprintf(Header, TEXT("HTTP/1.1 200 OK\x0d\x0a") TEXT("Server: Unreal File Server\x0d\x0a") TEXT("Connection: close\x0d\x0a") TEXT("Content-Type: %s \x0d\x0a") TEXT("Content-Encoding: gzip\x0d\x0a") TEXT("Content-Length: %u\x0d\x0a\x0d\x0a"), Mime, FileData.Num()); } else { Length = FCString::Sprintf(Header, TEXT("HTTP/1.1 200 OK\x0d\x0a") TEXT("Server: Unreal File Server\x0d\x0a") TEXT("Connection: close\x0d\x0a") TEXT("Content-Type: %s \x0d\x0a") TEXT("Content-Length: %u\x0d\x0a\x0d\x0a"), Mime, FileData.Num()); } // make space for the whole file in our out buffer. BufferInfo->Out.Append((uint8*)TCHAR_TO_ANSI(Header),Length); BufferInfo->Out.Append(FileData); // we need to write back to the client, queue up a write callback. lws_callback_on_writable(Wsi); } } else { // we got a post request!, queue up a write callback. lws_callback_on_writable(Wsi); } break; case LWS_CALLBACK_HTTP_BODY: { // post data is coming in, push it on to our incoming buffer. UE_LOG(LogFileServer, Log, TEXT("Incoming HTTP Partial Body Size %d, total size %d"),Len, Len+ BufferInfo->In.Num()); BufferInfo->In.Append((uint8*)In,Len); // we received some data - update time out. lws_set_timeout(Wsi, NO_PENDING_TIMEOUT, 60); } break; case LWS_CALLBACK_HTTP_BODY_COMPLETION: { // we have all the post data from the client. // create archives and process them. UE_LOG(LogFileServer, Log, TEXT("Incoming HTTP total size %d"), BufferInfo->In.Num()); FMemoryReader Reader(BufferInfo->In); TArray<uint8> Writer; FNetworkFileServerHttp::Process(Reader,Writer,Server); // even if we have 0 data to push, tell the client that we don't any data. ANSICHAR Header[1024]; int Length = FCStringAnsi::Sprintf( (ANSICHAR*)Header, "HTTP/1.1 200 OK\x0d\x0a" "Server: Unreal File Server\x0d\x0a" "Connection: close\x0d\x0a" "Content-Type: application/octet-stream \x0d\x0a" "Content-Length: %u\x0d\x0a\x0d\x0a", Writer.Num() ); // Add Http Header BufferInfo->Out.Append((uint8*)Header,Length); // Add Binary Data. BufferInfo->Out.Append(Writer); // we have enqueued data increase timeout and push a writable callback. lws_set_timeout(Wsi, NO_PENDING_TIMEOUT, 60); lws_callback_on_writable(Wsi); } break; case LWS_CALLBACK_CLOSED_HTTP: // client went away or //clean up. BufferInfo->In.Empty(); BufferInfo->Out.Empty(); break; case LWS_CALLBACK_PROTOCOL_DESTROY: // we are going away. break; case LWS_CALLBACK_HTTP_WRITEABLE: // get rid of superfluous write callbacks. if ( BufferInfo == NULL ) break; // we have data o send out. if (BufferInfo->Out.Num()) { int SentSize = lws_write(Wsi,(unsigned char*)BufferInfo->Out.GetData(),BufferInfo->Out.Num(), LWS_WRITE_HTTP); // get rid of the data that has been sent. BufferInfo->Out.RemoveAt(0,SentSize); } break; default: break; } return 0; }