/* * json-rpc-test-server.c: JSON-RPC 2.0 demo server * * Copyright (c) 2015 - 2022, PÅ™emysl Eric Janouch * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #define print_fatal_data ((void *) LOG_ERR) #define print_error_data ((void *) LOG_ERR) #define print_warning_data ((void *) LOG_WARNING) #define print_status_data ((void *) LOG_INFO) #define print_debug_data ((void *) LOG_DEBUG) #define LIBERTY_WANT_SSL #define LIBERTY_WANT_PROTO_HTTP #define LIBERTY_WANT_PROTO_WS #define LIBERTY_WANT_PROTO_SCGI #define LIBERTY_WANT_PROTO_FASTCGI #include "config.h" #undef PROGRAM_NAME #define PROGRAM_NAME "json-rpc-test-server" #include "liberty/liberty.c" #include #include #include #include #include #include #include #include "http-parser/http_parser.h" enum { PIPE_READ, PIPE_WRITE }; #define FIND_CONTAINER(name, pointer, type, member) \ type *name = CONTAINER_OF (pointer, type, member) // --- Utilities --------------------------------------------------------------- static bool flush_queue (struct write_queue *queue, int fd) { struct iovec vec[queue->len], *vec_iter = vec; LIST_FOR_EACH (struct write_req, iter, queue->head) *vec_iter++ = iter->data; ssize_t written; again: if ((written = writev (fd, vec, N_ELEMENTS (vec))) >= 0) { write_queue_processed (queue, written); return true; } if (errno == EINTR) goto again; if (errno == EAGAIN) return true; return false; } // --- Logging ----------------------------------------------------------------- static void log_message_syslog (void *user_data, const char *quote, const char *fmt, va_list ap) { int prio = (int) (intptr_t) user_data; va_list va; va_copy (va, ap); int size = vsnprintf (NULL, 0, fmt, va); va_end (va); if (size < 0) return; char buf[size + 1]; if (vsnprintf (buf, sizeof buf, fmt, ap) >= 0) syslog (prio, "%s%s", quote, buf); } // --- FastCGI ----------------------------------------------------------------- /// @defgroup FastCGI /// @{ enum fcgi_request_state { FCGI_REQUEST_PARAMS, ///< Reading headers FCGI_REQUEST_STDIN ///< Reading input }; struct fcgi_request { struct fcgi_muxer *muxer; ///< The parent muxer uint16_t request_id; ///< The ID of this request uint8_t flags; ///< Request flags enum fcgi_request_state state; ///< Parsing state struct str_map headers; ///< Headers struct fcgi_nv_parser hdr_parser; ///< Header parser struct str output_buffer; ///< Output buffer void *handler_data; ///< Handler data }; /// Handles a single FastCGI connection, de/multiplexing requests and responses struct fcgi_muxer { struct fcgi_parser parser; ///< FastCGI message parser uint32_t active_requests; ///< The number of active requests bool in_shutdown; ///< Rejecting new requests // Virtual method callbacks: /// Write data to the underlying transport. Assumes ownership of data. void (*write_cb) (struct fcgi_muxer *, void *data, size_t len); /// Close the underlying transport. You are allowed to destroy the muxer /// directly from within the callback. void (*close_cb) (struct fcgi_muxer *); /// Start processing a request. Return false if no further action is /// to be done and the request should be finished. bool (*request_start_cb) (struct fcgi_request *); /// Handle incoming data. "len == 0" means EOF. Returns false if /// the underlying transport should be closed, this being the last request. bool (*request_push_cb) (struct fcgi_request *, const void *data, size_t len); /// Destroy the handler's data stored in the request object void (*request_finalize_cb) (struct fcgi_request *); /// Requests assigned to request IDs (may not be FCGI_NULL_REQUEST_ID) struct fcgi_request *requests[1 << 8]; }; static void fcgi_muxer_send (struct fcgi_muxer *self, enum fcgi_type type, uint16_t request_id, const void *data, size_t len) { hard_assert (len <= UINT16_MAX); struct str message = str_make (); static char zeroes[8]; size_t padding = -len & 7; str_pack_u8 (&message, FCGI_VERSION_1); str_pack_u8 (&message, type); str_pack_u16 (&message, request_id); str_pack_u16 (&message, len); // content length str_pack_u8 (&message, padding); // padding length str_pack_u8 (&message, 0); // reserved str_append_data (&message, data, len); str_append_data (&message, zeroes, padding); self->write_cb (self, message.str, message.len); } static void fcgi_muxer_send_end_request (struct fcgi_muxer *self, uint16_t request_id, uint32_t app_status, enum fcgi_protocol_status protocol_status) { uint8_t content[8] = { app_status >> 24, app_status >> 16, app_status << 8, app_status, protocol_status }; fcgi_muxer_send (self, FCGI_END_REQUEST, request_id, content, sizeof content); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static struct fcgi_request * fcgi_request_new (void) { struct fcgi_request *self = xcalloc (1, sizeof *self); self->headers = str_map_make (free); self->hdr_parser = fcgi_nv_parser_make (); self->hdr_parser.output = &self->headers; self->output_buffer = str_make (); return self; } static void fcgi_request_destroy (struct fcgi_request *self) { // TODO: consider the case where it hasn't been started yet self->muxer->request_finalize_cb (self); str_map_free (&self->headers); fcgi_nv_parser_free (&self->hdr_parser); free (self); } static void fcgi_request_flush (struct fcgi_request *self) { if (!self->output_buffer.len) return; fcgi_muxer_send (self->muxer, FCGI_STDOUT, self->request_id, self->output_buffer.str, self->output_buffer.len); str_reset (&self->output_buffer); } static void fcgi_request_write (struct fcgi_request *self, const void *data, size_t len) { // We're buffering the output and splitting it into messages bool need_flush = true; while (len) { size_t to_write = UINT16_MAX - self->output_buffer.len; if (to_write > len) { to_write = len; need_flush = false; } str_append_data (&self->output_buffer, data, to_write); data = (uint8_t *) data + to_write; len -= to_write; if (need_flush) fcgi_request_flush (self); } } /// Mark the request as done. Returns false if the underlying transport /// should be closed, this being the last request. static bool fcgi_request_finish (struct fcgi_request *self, int32_t status_code) { fcgi_request_flush (self); fcgi_muxer_send (self->muxer, FCGI_STDOUT, self->request_id, NULL, 0); // The appStatus is mostly ignored by web servers and it's not even clear // whether it should be a signed value like it is on Unix, or not fcgi_muxer_send_end_request (self->muxer, self->request_id, status_code, FCGI_REQUEST_COMPLETE); bool should_close = !(self->flags & FCGI_KEEP_CONN); self->muxer->active_requests--; self->muxer->requests[self->request_id] = NULL; fcgi_request_destroy (self); return !should_close; } static bool fcgi_request_push_params (struct fcgi_request *self, const void *data, size_t len) { if (self->state != FCGI_REQUEST_PARAMS) { print_debug ("FastCGI: expected %s, got %s", STRINGIFY (FCGI_STDIN), STRINGIFY (FCGI_PARAMS)); return false; } if (len) fcgi_nv_parser_push (&self->hdr_parser, data, len); else { if (self->hdr_parser.state != FCGI_NV_PARSER_NAME_LEN) print_debug ("FastCGI: request headers seem to be cut off"); self->state = FCGI_REQUEST_STDIN; if (!self->muxer->request_start_cb (self)) return fcgi_request_finish (self, EXIT_SUCCESS); } return true; } static bool fcgi_request_push_stdin (struct fcgi_request *self, const void *data, size_t len) { if (self->state != FCGI_REQUEST_STDIN) { print_debug ("FastCGI: expected %s, got %s", STRINGIFY (FCGI_PARAMS), STRINGIFY (FCGI_STDIN)); return false; } return self->muxer->request_push_cb (self, data, len); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - typedef bool (*fcgi_muxer_handler_fn) (struct fcgi_muxer *, const struct fcgi_parser *); static bool fcgi_muxer_on_get_values (struct fcgi_muxer *self, const struct fcgi_parser *parser) { if (parser->request_id != FCGI_NULL_REQUEST_ID) { print_debug ("FastCGI: invalid %s message", STRINGIFY (FCGI_GET_VALUES)); return false; } struct str_map values = str_map_make (free); struct str_map response = str_map_make (free); struct fcgi_nv_parser nv_parser = fcgi_nv_parser_make (); nv_parser.output = &values; fcgi_nv_parser_push (&nv_parser, parser->content.str, parser->content.len); fcgi_nv_parser_free (&nv_parser); const char *key = NULL; // No real-world servers seem to actually use multiplexing // or even issue this request, but we will implement it anyway if (str_map_find (&values, (key = FCGI_MPXS_CONNS))) str_map_set (&response, key, xstrdup ("1")); // It's not clear whether FCGI_MAX_REQS means concurrently over all // connections or over just a single connection (multiplexed), though // supposedly it's actually per /web server/. Supply the strictest limit. if (str_map_find (&values, (key = FCGI_MAX_REQS))) str_map_set (&response, key, xstrdup_printf ("%zu", N_ELEMENTS (self->requests) - 1)); // FCGI_MAX_CONNS would be basically infinity. We don't limit connections. struct str content = str_make (); fcgi_nv_convert (&response, &content); fcgi_muxer_send (self, FCGI_GET_VALUES_RESULT, parser->request_id, content.str, content.len); str_free (&content); str_map_free (&values); str_map_free (&response); return true; } static bool fcgi_muxer_on_begin_request (struct fcgi_muxer *self, const struct fcgi_parser *parser) { struct msg_unpacker unpacker = msg_unpacker_make (parser->content.str, parser->content.len); uint16_t role; uint8_t flags; bool success = true; success &= msg_unpacker_u16 (&unpacker, &role); success &= msg_unpacker_u8 (&unpacker, &flags); // Ignoring 5 reserved bytes if (!success) { print_debug ("FastCGI: invalid %s message", STRINGIFY (FCGI_BEGIN_REQUEST)); return false; } struct fcgi_request *request = self->requests[parser->request_id]; if (parser->request_id == FCGI_NULL_REQUEST_ID || request) { print_debug ("FastCGI: unusable request ID in %s message", STRINGIFY (FCGI_BEGIN_REQUEST)); return false; } // We can only act as a responder, reject everything else up front if (role != FCGI_RESPONDER) { fcgi_muxer_send_end_request (self, parser->request_id, 0, FCGI_UNKNOWN_ROLE); return true; } if (parser->request_id >= N_ELEMENTS (self->requests) || self->in_shutdown) { fcgi_muxer_send_end_request (self, parser->request_id, 0, FCGI_OVERLOADED); return true; } request = fcgi_request_new (); request->muxer = self; request->request_id = parser->request_id; request->flags = flags; self->requests[parser->request_id] = request; self->active_requests++; return true; } static bool fcgi_muxer_on_abort_request (struct fcgi_muxer *self, const struct fcgi_parser *parser) { struct fcgi_request *request = self->requests[parser->request_id]; if (parser->request_id == FCGI_NULL_REQUEST_ID || !request) { print_debug ("FastCGI: received %s for an unknown request", STRINGIFY (FCGI_ABORT_REQUEST)); return true; // We might have just rejected it } return fcgi_request_finish (request, EXIT_FAILURE); } static bool fcgi_muxer_on_params (struct fcgi_muxer *self, const struct fcgi_parser *parser) { struct fcgi_request *request = self->requests[parser->request_id]; if (parser->request_id == FCGI_NULL_REQUEST_ID || !request) { print_debug ("FastCGI: received %s for an unknown request", STRINGIFY (FCGI_PARAMS)); return true; // We might have just rejected it } // This may immediately finish and delete the request, but that's fine return fcgi_request_push_params (request, parser->content.str, parser->content.len); } static bool fcgi_muxer_on_stdin (struct fcgi_muxer *self, const struct fcgi_parser *parser) { struct fcgi_request *request = self->requests[parser->request_id]; if (parser->request_id == FCGI_NULL_REQUEST_ID || !request) { print_debug ("FastCGI: received %s for an unknown request", STRINGIFY (FCGI_STDIN)); return true; // We might have just rejected it } // At the end of the stream, a zero-length record is received return fcgi_request_push_stdin (request, parser->content.str, parser->content.len); } static bool fcgi_muxer_on_message (const struct fcgi_parser *parser, void *user_data) { struct fcgi_muxer *self = user_data; if (parser->version != FCGI_VERSION_1) { print_debug ("FastCGI: unsupported version %d", parser->version); return false; } static const fcgi_muxer_handler_fn handlers[] = { [FCGI_GET_VALUES] = fcgi_muxer_on_get_values, [FCGI_BEGIN_REQUEST] = fcgi_muxer_on_begin_request, [FCGI_ABORT_REQUEST] = fcgi_muxer_on_abort_request, [FCGI_PARAMS] = fcgi_muxer_on_params, [FCGI_STDIN] = fcgi_muxer_on_stdin, }; fcgi_muxer_handler_fn handler; if (parser->type >= N_ELEMENTS (handlers) || !(handler = handlers[parser->type])) { // Responding in this way even to application records, unspecified uint8_t content[8] = { parser->type }; fcgi_muxer_send (self, FCGI_UNKNOWN_TYPE, parser->request_id, content, sizeof content); return true; } return handler (self, parser); } static void fcgi_muxer_init (struct fcgi_muxer *self) { self->parser = fcgi_parser_make (); self->parser.on_message = fcgi_muxer_on_message; self->parser.user_data = self; } static void fcgi_muxer_free (struct fcgi_muxer *self) { for (size_t i = 0; i < N_ELEMENTS (self->requests); i++) { if (!self->active_requests) break; if (self->requests[i]) { fcgi_request_destroy (self->requests[i]); self->active_requests--; } } fcgi_parser_free (&self->parser); } static bool fcgi_muxer_push (struct fcgi_muxer *self, const void *data, size_t len) { return fcgi_parser_push (&self->parser, data, len); } /// @} // --- WebSocket --------------------------------------------------------------- /// @defgroup WebSocket /// @{ // WebSocket isn't CGI-compatible, therefore we must handle the initial HTTP // handshake ourselves. Luckily it's not too much of a bother with http-parser. // Typically there will be a normal HTTP server in front of us, proxying the // requests based on the URI. enum ws_handler_state { WS_HANDLER_CONNECTING, ///< Parsing HTTP WS_HANDLER_OPEN, ///< Parsing WebSocket frames WS_HANDLER_CLOSING, ///< Partial closure by us WS_HANDLER_FLUSHING, ///< Just waiting for client EOF WS_HANDLER_CLOSED ///< Dead, both sides closed }; struct ws_handler { enum ws_handler_state state; ///< State // HTTP handshake: http_parser hp; ///< HTTP parser bool have_header_value; ///< Parsing header value or field? struct str field; ///< Field part buffer struct str value; ///< Value part buffer struct str_map headers; ///< HTTP Headers struct str url; ///< Request URL ev_timer handshake_timeout_watcher; ///< Handshake timeout watcher // WebSocket frame protocol: struct ws_parser parser; ///< Protocol frame parser bool expecting_continuation; ///< For non-control traffic enum ws_opcode message_opcode; ///< Opcode for the current message struct str message_data; ///< Concatenated message data ev_timer ping_timer; ///< Ping timer bool received_pong; ///< Received PONG since the last PING ev_timer close_timeout_watcher; ///< Close timeout watcher // Configuration: unsigned handshake_timeout; ///< How long to wait for the handshake unsigned close_timeout; ///< How long to wait for TCP close unsigned ping_interval; ///< Ping interval in seconds uint64_t max_payload_len; ///< Maximum length of any message // Event callbacks: // TODO: void (*on_handshake) (protocols) that will allow the user // to choose any sub-protocol, if the client has provided any. // This may render "on_connected" unnecessary. // Should also enable failing the handshake. /// Called after successfuly connecting (handshake complete) bool (*on_connected) (struct ws_handler *); /// Called upon reception of a single full message bool (*on_message) (struct ws_handler *, enum ws_opcode type, const void *data, size_t len); /// The connection is about to close. @a close_code may, or may not, be one /// of enum ws_status. The @a reason is never NULL. void (*on_close) (struct ws_handler *, int close_code, const char *reason); // Virtual method callbacks: /// Write a chunk of data to the stream void (*write_cb) (struct ws_handler *, const void *data, size_t len); /// Close the connection. If @a half_close is false, you are allowed to /// destroy the handler directly from within the callback. void (*close_cb) (struct ws_handler *, bool half_close); }; static void ws_handler_send_control (struct ws_handler *self, enum ws_opcode opcode, const void *data, size_t len) { if (len > WS_MAX_CONTROL_PAYLOAD_LEN) { print_debug ("truncating output control frame payload" " from %zu to %zu bytes", len, (size_t) WS_MAX_CONTROL_PAYLOAD_LEN); len = WS_MAX_CONTROL_PAYLOAD_LEN; } uint8_t header[2] = { 0x80 | (opcode & 0x0F), len }; self->write_cb (self, header, sizeof header); self->write_cb (self, data, len); } static void ws_handler_close (struct ws_handler *self, enum ws_status close_code, const char *reason, size_t len) { hard_assert (self->state == WS_HANDLER_OPEN); struct str payload = str_make (); str_pack_u16 (&payload, close_code); // XXX: maybe accept a null-terminated string on input? Has to be UTF-8 a/w str_append_data (&payload, reason, len); ws_handler_send_control (self, WS_OPCODE_CLOSE, payload.str, payload.len); self->close_cb (self, true /* half_close */); self->state = WS_HANDLER_CLOSING; str_free (&payload); } static bool ws_handler_fail_connection (struct ws_handler *self, enum ws_status close_code) { hard_assert (self->state == WS_HANDLER_OPEN || self->state == WS_HANDLER_CLOSING); if (self->state == WS_HANDLER_OPEN) ws_handler_close (self, close_code, NULL, 0); self->state = WS_HANDLER_FLUSHING; if (self->on_close) self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, ""); ev_timer_stop (EV_DEFAULT_ &self->ping_timer); ev_timer_set (&self->close_timeout_watcher, self->close_timeout, 0.); ev_timer_start (EV_DEFAULT_ &self->close_timeout_watcher); return false; } // TODO: add support for fragmented responses static void ws_handler_send_frame (struct ws_handler *self, enum ws_opcode opcode, const void *data, size_t len) { if (!soft_assert (self->state == WS_HANDLER_OPEN)) return; struct str header = str_make (); str_pack_u8 (&header, 0x80 | (opcode & 0x0F)); if (len > UINT16_MAX) { str_pack_u8 (&header, 127); str_pack_u64 (&header, len); } else if (len > 125) { str_pack_u8 (&header, 126); str_pack_u16 (&header, len); } else str_pack_u8 (&header, len); self->write_cb (self, header.str, header.len); self->write_cb (self, data, len); str_free (&header); } static bool ws_handler_on_frame_header (void *user_data, const struct ws_parser *parser) { struct ws_handler *self = user_data; // Note that we aren't expected to send any close frame before closing the // connection when the frame is unmasked if (parser->reserved_1 || parser->reserved_2 || parser->reserved_3 || !parser->is_masked // client -> server payload must be masked || (ws_is_control_frame (parser->opcode) && (!parser->is_fin || parser->payload_len > WS_MAX_CONTROL_PAYLOAD_LEN)) || (!ws_is_control_frame (parser->opcode) && (self->expecting_continuation && parser->opcode != WS_OPCODE_CONT)) || parser->payload_len >= 0x8000000000000000ULL) return ws_handler_fail_connection (self, WS_STATUS_PROTOCOL_ERROR); if (parser->payload_len > self->max_payload_len || (self->expecting_continuation && self->message_data.len + parser->payload_len > self->max_payload_len)) return ws_handler_fail_connection (self, WS_STATUS_MESSAGE_TOO_BIG); return true; } static bool ws_handler_on_control_close (struct ws_handler *self, const struct ws_parser *parser) { hard_assert (self->state == WS_HANDLER_OPEN || self->state == WS_HANDLER_CLOSING); struct msg_unpacker unpacker = msg_unpacker_make (parser->input.str, parser->payload_len); char *reason = NULL; uint16_t close_code = WS_STATUS_NO_STATUS_RECEIVED; if (parser->payload_len >= 2) { (void) msg_unpacker_u16 (&unpacker, &close_code); reason = xstrndup (parser->input.str + 2, parser->payload_len - 2); } else reason = xstrdup (""); if (close_code < 1000 || close_code > 4999) // XXX: invalid close code: maybe we should fail the connection instead, // although the specification doesn't say anything about this case close_code = WS_STATUS_PROTOCOL_ERROR; // Update the now potentially different close_code (lol const) if (parser->payload_len >= 2) { parser->input.str[0] = close_code >> 8; parser->input.str[1] = close_code; } if (self->state == WS_HANDLER_OPEN) { ws_handler_send_control (self, WS_OPCODE_CLOSE, parser->input.str, parser->payload_len); self->state = WS_HANDLER_FLUSHING; if (self->on_close) self->on_close (self, close_code, reason); } else // Close initiated by us, flush the write queue and close the transport self->state = WS_HANDLER_FLUSHING; free (reason); ev_timer_stop (EV_DEFAULT_ &self->ping_timer); ev_timer_set (&self->close_timeout_watcher, self->close_timeout, 0.); ev_timer_start (EV_DEFAULT_ &self->close_timeout_watcher); return true; } static bool ws_handler_on_control_frame (struct ws_handler *self, const struct ws_parser *parser) { switch (parser->opcode) { case WS_OPCODE_CLOSE: return ws_handler_on_control_close (self, parser); case WS_OPCODE_PING: ws_handler_send_control (self, WS_OPCODE_PONG, parser->input.str, parser->payload_len); break; case WS_OPCODE_PONG: // TODO: check the payload self->received_pong = true; break; default: // Unknown control frame return ws_handler_fail_connection (self, WS_STATUS_PROTOCOL_ERROR); } return true; } static bool ws_handler_on_frame (void *user_data, const struct ws_parser *parser) { struct ws_handler *self = user_data; if (ws_is_control_frame (parser->opcode)) return ws_handler_on_control_frame (self, parser); if (!self->expecting_continuation) self->message_opcode = parser->opcode; str_append_data (&self->message_data, parser->input.str, parser->payload_len); if ((self->expecting_continuation = !parser->is_fin)) return true; if (self->message_opcode == WS_OPCODE_TEXT && !utf8_validate (self->message_data.str, self->message_data.len)) { return ws_handler_fail_connection (self, WS_STATUS_INVALID_PAYLOAD_DATA); } bool result = true; if (self->on_message) result = self->on_message (self, self->message_opcode, self->message_data.str, self->message_data.len); str_reset (&self->message_data); // TODO: if (!result), either replace this with a state check, // or make sure to change the state return result; } static void ws_handler_on_ping_timer (EV_P_ ev_timer *watcher, int revents) { (void) loop; (void) revents; struct ws_handler *self = watcher->data; if (!self->received_pong) ws_handler_fail_connection (self, 4000 /* private use code */); else { // TODO: be an annoying server and send a nonce in the data ws_handler_send_control (self, WS_OPCODE_PING, NULL, 0); ev_timer_again (EV_A_ watcher); } } static void ws_handler_on_close_timeout (EV_P_ ev_timer *watcher, int revents) { (void) loop; (void) revents; struct ws_handler *self = watcher->data; hard_assert (self->state == WS_HANDLER_OPEN || self->state == WS_HANDLER_CLOSING); if (self->state == WS_HANDLER_CLOSING && self->on_close) self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "close timeout"); self->state = WS_HANDLER_CLOSED; self->close_cb (self, false /* half_close */); } static bool ws_handler_fail_handshake (struct ws_handler *self, const char *status, ...) ATTRIBUTE_SENTINEL; #define HTTP_101_SWITCHING_PROTOCOLS "101 Switching Protocols" #define HTTP_400_BAD_REQUEST "400 Bad Request" #define HTTP_405_METHOD_NOT_ALLOWED "405 Method Not Allowed" #define HTTP_408_REQUEST_TIMEOUT "408 Request Timeout" #define HTTP_417_EXPECTATION_FAILED "407 Expectation Failed" #define HTTP_426_UPGRADE_REQUIRED "426 Upgrade Required" #define HTTP_505_VERSION_NOT_SUPPORTED "505 HTTP Version Not Supported" static void ws_handler_on_handshake_timeout (EV_P_ ev_timer *watcher, int revents) { (void) loop; (void) revents; struct ws_handler *self = watcher->data; ws_handler_fail_handshake (self, HTTP_408_REQUEST_TIMEOUT, NULL); self->state = WS_HANDLER_CLOSED; self->close_cb (self, false /* half_close */); } static void ws_handler_init (struct ws_handler *self) { memset (self, 0, sizeof *self); self->state = WS_HANDLER_CONNECTING; http_parser_init (&self->hp, HTTP_REQUEST); self->hp.data = self; self->field = str_make (); self->value = str_make (); self->headers = str_map_make (free); self->headers.key_xfrm = tolower_ascii_strxfrm; self->url = str_make (); ev_timer_init (&self->handshake_timeout_watcher, ws_handler_on_handshake_timeout, 0., 0.); self->handshake_timeout_watcher.data = self; self->parser = ws_parser_make (); self->parser.on_frame_header = ws_handler_on_frame_header; self->parser.on_frame = ws_handler_on_frame; self->parser.user_data = self; self->message_data = str_make (); ev_timer_init (&self->ping_timer, ws_handler_on_ping_timer, 0., 0.); self->ping_timer.data = self; ev_timer_init (&self->close_timeout_watcher, ws_handler_on_close_timeout, 0., 0.); self->ping_timer.data = self; // So that the first ping timer doesn't timeout the connection self->received_pong = true; self->handshake_timeout = self->close_timeout = self->ping_interval = 60; // This is still ridiculously high. Note that the most significant bit // must always be zero, i.e. the protocol maximum is 0x7FFF FFFF FFFF FFFF. self->max_payload_len = UINT32_MAX; } /// Stop all timers, not going to use the handler anymore static void ws_handler_stop (struct ws_handler *self) { ev_timer_stop (EV_DEFAULT_ &self->handshake_timeout_watcher); ev_timer_stop (EV_DEFAULT_ &self->ping_timer); ev_timer_stop (EV_DEFAULT_ &self->close_timeout_watcher); } static void ws_handler_free (struct ws_handler *self) { ws_handler_stop (self); str_free (&self->field); str_free (&self->value); str_map_free (&self->headers); str_free (&self->url); ws_parser_free (&self->parser); str_free (&self->message_data); } static bool ws_handler_header_field_is_a_list (const char *name) { // This must contain all header fields we use for anything static const char *concatenable[] = { SEC_WS_PROTOCOL, SEC_WS_EXTENSIONS, "Connection", "Upgrade" }; for (size_t i = 0; i < N_ELEMENTS (concatenable); i++) if (!strcasecmp_ascii (name, concatenable[i])) return true; return false; } static void ws_handler_on_header_read (struct ws_handler *self) { // The HTTP parser unfolds values and removes preceding whitespace, but // otherwise doesn't touch the values or the following whitespace. // RFC 7230 states that trailing whitespace is not part of a field value char *value = self->field.str; size_t len = self->field.len; while (len--) if (value[len] == '\t' || value[len] == ' ') value[len] = '\0'; else break; self->field.len = len; const char *field = self->field.str; const char *current = str_map_find (&self->headers, field); if (ws_handler_header_field_is_a_list (field) && current) str_map_set (&self->headers, field, xstrdup_printf ("%s, %s", current, self->value.str)); else // If the field cannot be concatenated, just overwrite the last value. // Maybe we should issue a warning or something. str_map_set (&self->headers, field, xstrdup (self->value.str)); } static int ws_handler_on_header_field (http_parser *parser, const char *at, size_t len) { struct ws_handler *self = parser->data; if (self->have_header_value) { ws_handler_on_header_read (self); str_reset (&self->field); str_reset (&self->value); } str_append_data (&self->field, at, len); self->have_header_value = false; return 0; } static int ws_handler_on_header_value (http_parser *parser, const char *at, size_t len) { struct ws_handler *self = parser->data; str_append_data (&self->value, at, len); self->have_header_value = true; return 0; } static int ws_handler_on_headers_complete (http_parser *parser) { struct ws_handler *self = parser->data; if (self->have_header_value) ws_handler_on_header_read (self); // We require a protocol upgrade. 1 is for "skip body", 2 is the same // + "stop processing", return another number to indicate a problem here. if (!parser->upgrade) return 3; return 0; } static int ws_handler_on_url (http_parser *parser, const char *at, size_t len) { struct ws_handler *self = parser->data; str_append_data (&self->url, at, len); return 0; } static struct str ws_handler_build_http_responsev (const char *status, char *const *fields) { hard_assert (status != NULL); struct str response = str_make (); str_append_printf (&response, "HTTP/1.1 %s\r\n", status); while (*fields) str_append_printf (&response, "%s\r\n", *fields++); time_t now = time (NULL); struct tm ts; gmtime_r (&now, &ts); // See RFC 7231, 7.1.1.2. Date const char *dow[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; const char *moy[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; str_append_printf (&response, "Date: %s, %02d %s %04d %02d:%02d:%02d GMT\r\n", dow[ts.tm_wday], ts.tm_mday, moy[ts.tm_mon], ts.tm_year + 1900, ts.tm_hour, ts.tm_min, ts.tm_sec); str_append (&response, "Server: " PROGRAM_NAME "/" PROGRAM_VERSION "\r\n\r\n"); return response; } static void ws_handler_http_responsev (struct ws_handler *self, const char *status, char *const *fields) { struct str response = ws_handler_build_http_responsev (status, fields); self->write_cb (self, response.str, response.len); str_free (&response); } static bool ws_handler_fail_handshake (struct ws_handler *self, const char *status, ...) { va_list ap; va_start (ap, status); const char *s; struct strv v = strv_make (); while ((s = va_arg (ap, const char *))) strv_append (&v, s); strv_append (&v, "Connection: close"); va_end (ap); ws_handler_http_responsev (self, status, v.vector); strv_free (&v); self->close_cb (self, true /* half_close */); self->state = WS_HANDLER_FLUSHING; if (self->on_close) self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, status); return false; } #define FAIL_HANDSHAKE(...) \ return ws_handler_fail_handshake (self, __VA_ARGS__, NULL) static bool ws_handler_finish_handshake (struct ws_handler *self) { if (self->hp.method != HTTP_GET) FAIL_HANDSHAKE (HTTP_405_METHOD_NOT_ALLOWED, "Allow: GET"); // Technically, it must be /at least/ 1.1 but no other 1.x version of HTTP // is going to happen and 2.x is entirely incompatible // XXX: we probably shouldn't use 505 to reject the minor version but w/e if (self->hp.http_major != 1 || self->hp.http_minor != 1) FAIL_HANDSHAKE (HTTP_505_VERSION_NOT_SUPPORTED); // Your expectations are way too high if (str_map_find (&self->headers, "Expect")) FAIL_HANDSHAKE (HTTP_417_EXPECTATION_FAILED); // Reject URLs specifying the schema and host; we're not parsing that // TODO: actually do parse this and let our user decide if it matches struct http_parser_url url; if (http_parser_parse_url (self->url.str, self->url.len, false, &url) || (url.field_set & (1 << UF_SCHEMA | 1 << UF_HOST | 1 << UF_PORT)) || !str_map_find (&self->headers, "Host")) FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST); const char *connection = str_map_find (&self->headers, "Connection"); if (!connection || strcasecmp_ascii (connection, "Upgrade")) FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST); // Check if we can actually upgrade the protocol to WebSocket const char *upgrade = str_map_find (&self->headers, "Upgrade"); struct http_protocol *offered_upgrades = NULL; bool can_upgrade = false; if (upgrade && http_parse_upgrade (upgrade, &offered_upgrades)) // Case-insensitive according to RFC 6455; neither RFC 2616 nor 7230 // say anything at all about case-sensitivity for this field LIST_FOR_EACH (struct http_protocol, iter, offered_upgrades) { if (!iter->version && !strcasecmp_ascii (iter->name, "websocket")) can_upgrade = true; http_protocol_destroy (iter); } if (!can_upgrade) FAIL_HANDSHAKE (HTTP_426_UPGRADE_REQUIRED, "Upgrade: websocket", SEC_WS_VERSION ": 13"); // Okay, we're finally past the basic HTTP/1.1 stuff const char *key = str_map_find (&self->headers, SEC_WS_KEY); const char *version = str_map_find (&self->headers, SEC_WS_VERSION); /* const char *protocol = str_map_find (&self->headers, SEC_WS_PROTOCOL); const char *extensions = str_map_find (&self->headers, SEC_WS_EXTENSIONS); */ if (!version) FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST); if (strcmp (version, "13")) FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST, SEC_WS_VERSION ": 13"); struct str tmp = str_make (); bool key_is_valid = key && base64_decode (key, false, &tmp) && tmp.len == 16; str_free (&tmp); if (!key_is_valid) FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST); struct strv fields = strv_make (); strv_append_args (&fields, "Upgrade: websocket", "Connection: Upgrade", NULL); char *response_key = ws_encode_response_key (key); strv_append_owned (&fields, xstrdup_printf (SEC_WS_ACCEPT ": %s", response_key)); free (response_key); // TODO: make it possible to choose Sec-Websocket-{Extensions,Protocol} ws_handler_http_responsev (self, HTTP_101_SWITCHING_PROTOCOLS, fields.vector); strv_free (&fields); self->state = WS_HANDLER_OPEN; ev_timer_init (&self->ping_timer, ws_handler_on_ping_timer, self->ping_interval, 0); ev_timer_start (EV_DEFAULT_ &self->ping_timer); return true; } /// Tells the handler that the TCP connection has been established so it can /// timeout when the client handshake doesn't arrive soon enough static void ws_handler_start (struct ws_handler *self) { hard_assert (self->state == WS_HANDLER_CONNECTING); ev_timer_set (&self->handshake_timeout_watcher, self->handshake_timeout, 0.); ev_timer_start (EV_DEFAULT_ &self->handshake_timeout_watcher); } // The client should normally never close the connection, assume that it's // either received an EOF from our side, or that it doesn't care about our data // anymore, having called close() already static bool ws_handler_push_eof (struct ws_handler *self) { switch (self->state) { case WS_HANDLER_CONNECTING: ev_timer_stop (EV_DEFAULT_ &self->handshake_timeout_watcher); self->state = WS_HANDLER_FLUSHING; if (self->on_close) self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, "unexpected EOF"); break; case WS_HANDLER_OPEN: ev_timer_stop (EV_DEFAULT_ &self->ping_timer); // Fall-through case WS_HANDLER_CLOSING: self->state = WS_HANDLER_CLOSED; if (self->on_close) self->on_close (self, WS_STATUS_ABNORMAL_CLOSURE, ""); // Fall-through case WS_HANDLER_FLUSHING: ev_timer_stop (EV_DEFAULT_ &self->close_timeout_watcher); break; default: soft_assert(self->state != WS_HANDLER_CLOSED); } self->state = WS_HANDLER_CLOSED; return false; } /// Push data to the WebSocket handler. "len == 0" means EOF. /// You are expected to close the connection and dispose of the handler /// when the function returns false. static bool ws_handler_push (struct ws_handler *self, const void *data, size_t len) { if (!len) return ws_handler_push_eof (self); if (self->state == WS_HANDLER_FLUSHING) // We're waiting for an EOF from the client, must not process data return true; if (self->state != WS_HANDLER_CONNECTING) return soft_assert (self->state != WS_HANDLER_CLOSED) && ws_parser_push (&self->parser, data, len); // The handshake hasn't been done yet, process HTTP headers static const http_parser_settings http_settings = { .on_header_field = ws_handler_on_header_field, .on_header_value = ws_handler_on_header_value, .on_headers_complete = ws_handler_on_headers_complete, .on_url = ws_handler_on_url, }; size_t n_parsed = http_parser_execute (&self->hp, &http_settings, data, len); if (self->hp.upgrade) { ev_timer_stop (EV_DEFAULT_ &self->handshake_timeout_watcher); // The handshake hasn't been finished, yet there is more data // to be processed after the headers already if (len - n_parsed) FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST); if (!ws_handler_finish_handshake (self)) return false; if (self->on_connected) return self->on_connected (self); return true; } enum http_errno err = HTTP_PARSER_ERRNO (&self->hp); if (n_parsed != len || err != HPE_OK) { ev_timer_stop (EV_DEFAULT_ &self->handshake_timeout_watcher); if (err == HPE_CB_headers_complete) { print_debug ("WS handshake failed: %s", "missing `Upgrade' field"); FAIL_HANDSHAKE (HTTP_426_UPGRADE_REQUIRED, "Upgrade: websocket", SEC_WS_VERSION ": 13"); } print_debug ("WS handshake failed: %s", http_errno_description (err)); FAIL_HANDSHAKE (HTTP_400_BAD_REQUEST); } return true; } /// @} // --- Server ------------------------------------------------------------------ static struct simple_config_item g_config_table[] = { { "bind_host", NULL, "Address of the server" }, { "port_fastcgi", "9000", "Port to bind for FastCGI" }, { "port_scgi", NULL, "Port to bind for SCGI" }, { "port_ws", NULL, "Port to bind for WebSocket" }, { "port_http", NULL, "Port to bind for HTTP" }, { "pid_file", NULL, "Full path for the PID file" }, // XXX: here belongs something like a web SPA that interfaces with us { "static_root", NULL, "The root for static content" }, { NULL, NULL, NULL } }; struct server_context { ev_signal sigterm_watcher; ///< Got SIGTERM ev_signal sigint_watcher; ///< Got SIGINT ev_timer quit_timeout_watcher; ///< Quit timeout watcher bool quitting; ///< User requested quitting struct listener *listeners; ///< Listeners size_t n_listeners; ///< Number of listening sockets struct client *clients; ///< Clients unsigned n_clients; ///< Current number of connections struct request_handler *handlers; ///< Request handlers struct str_map config; ///< Server configuration }; static void initiate_quit (struct server_context *self); static void try_finish_quit (struct server_context *self); static void on_quit_timeout (EV_P_ ev_timer *watcher, int revents); static void close_listeners (struct server_context *self); static void server_context_init (struct server_context *self) { memset (self, 0, sizeof *self); self->config = str_map_make (NULL); simple_config_load_defaults (&self->config, g_config_table); ev_timer_init (&self->quit_timeout_watcher, on_quit_timeout, 3., 0.); self->quit_timeout_watcher.data = self; } static void server_context_free (struct server_context *self) { // We really shouldn't attempt a quit without closing the clients first soft_assert (!self->clients); close_listeners (self); free (self->listeners); str_map_free (&self->config); } // --- JSON-RPC ---------------------------------------------------------------- /// @defgroup JSON-RPC /// @{ #define JSON_RPC_ERROR_TABLE(XX) \ XX (-32700, PARSE_ERROR, "Parse error") \ XX (-32600, INVALID_REQUEST, "Invalid Request") \ XX (-32601, METHOD_NOT_FOUND, "Method not found") \ XX (-32602, INVALID_PARAMS, "Invalid params") \ XX (-32603, INTERNAL_ERROR, "Internal error") enum json_rpc_error { #define XX(code, name, message) JSON_RPC_ERROR_ ## name, JSON_RPC_ERROR_TABLE (XX) #undef XX JSON_RPC_ERROR_COUNT }; static json_t * json_rpc_error (enum json_rpc_error id, json_t *data) { #define XX(code, name, message) { code, message }, static const struct json_rpc_error { int code; const char *message; } errors[JSON_RPC_ERROR_COUNT] = { JSON_RPC_ERROR_TABLE (XX) }; #undef XX json_t *error = json_object (); json_object_set_new (error, "code", json_integer (errors[id].code)); json_object_set_new (error, "message", json_string (errors[id].message)); if (data) json_object_set_new (error, "data", data); return error; } static json_t * json_rpc_response (json_t *id, json_t *result, json_t *error) { json_t *x = json_object (); json_object_set_new (x, "jsonrpc", json_string ("2.0")); json_object_set_new (x, "id", id ? id : json_null ()); if (result) json_object_set_new (x, "result", result); if (error) json_object_set_new (x, "error", error); return x; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static bool validate_json_rpc_content_type (const char *content_type) { char *type = NULL; char *subtype = NULL; struct str_map parameters = str_map_make (free); parameters.key_xfrm = tolower_ascii_strxfrm; bool result = http_parse_media_type (content_type, &type, &subtype, ¶meters); if (!result) goto end; if (strcasecmp_ascii (type, "application") || (strcasecmp_ascii (subtype, "json") && strcasecmp_ascii (subtype, "json-rpc" /* obsolete */))) result = false; const char *charset = str_map_find (¶meters, "charset"); if (charset && strcasecmp_ascii (charset, "UTF-8")) result = false; // Currently ignoring all unknown parametrs end: free (type); free (subtype); str_map_free (¶meters); return result; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /// Handlers must not set the `id` field in their responses, that will be filled /// in automatically according to whether the request is a notification or not. typedef json_t *(*json_rpc_handler_fn) (struct server_context *, json_t *); struct json_rpc_handler_info { const char *method_name; ///< JSON-RPC method name json_rpc_handler_fn handler; ///< Method handler }; static int json_rpc_handler_info_cmp (const void *first, const void *second) { return strcmp (((struct json_rpc_handler_info *) first)->method_name, ((struct json_rpc_handler_info *) second)->method_name); } static json_t * open_rpc_describe (const char *method, json_t *result) { return json_pack ("{sssoso}", "name", method, "params", json_pack ("[]"), "result", json_pack ("{ssso}", "name", method, "schema", result)); } // This server rarely sees changes and we can afford to hardcode the schema static json_t * json_rpc_discover (struct server_context *ctx, json_t *params) { (void) ctx; (void) params; json_t *info = json_pack ("{ssss}", "title", PROGRAM_NAME, "version", PROGRAM_VERSION); json_t *methods = json_pack ("[oooo]", open_rpc_describe ("date", json_pack ("{ssso}", "type", "object", "properties", json_pack ("{s{ss}s{ss}s{ss}s{ss}s{ss}s{ss}}", "year", "type", "number", "month", "type", "number", "day", "type", "number", "hours", "type", "number", "minutes", "type", "number", "seconds", "type", "number"))), open_rpc_describe ("ping", json_pack ("{ss}", "type", "string")), open_rpc_describe ("rpc.discover", json_pack ("{ss}", "$ref", "https://github.com/open-rpc/meta-schema/raw/master/schema.json")), open_rpc_describe ("wait", json_pack ("{ss}", "type", "null"))); return json_rpc_response (NULL, json_pack ("{sssoso}", "openrpc", "1.2.6", "info", info, "methods", methods), NULL); } static json_t * json_rpc_ping (struct server_context *ctx, json_t *params) { (void) ctx; if (params && !json_is_null (params)) return json_rpc_response (NULL, NULL, json_rpc_error (JSON_RPC_ERROR_INVALID_PARAMS, NULL)); return json_rpc_response (NULL, json_string ("pong"), NULL); } static json_t * json_rpc_wait (struct server_context *ctx, json_t *params) { (void) ctx; (void) params; sleep (1); return json_rpc_response (NULL, json_null (), NULL); } static json_t * json_rpc_date (struct server_context *ctx, json_t *params) { (void) ctx; if (params && !json_is_null (params)) return json_rpc_response (NULL, NULL, json_rpc_error (JSON_RPC_ERROR_INVALID_PARAMS, NULL)); time_t now = time (NULL); const struct tm *tm = localtime (&now); json_t *x = json_object (); json_object_set_new (x, "year", json_integer (tm->tm_year + 1900)); json_object_set_new (x, "month", json_integer (tm->tm_mon + 1)); json_object_set_new (x, "day", json_integer (tm->tm_mday)); json_object_set_new (x, "hours", json_integer (tm->tm_hour)); json_object_set_new (x, "minutes", json_integer (tm->tm_min)); json_object_set_new (x, "seconds", json_integer (tm->tm_sec)); return json_rpc_response (NULL, x, NULL); } static json_t * process_json_rpc_request (struct server_context *ctx, json_t *request) { // A list of all available methods; this list has to be ordered. // Eventually it might be better to move this into a map in the context. static struct json_rpc_handler_info handlers[] = { { "date", json_rpc_date }, { "ping", json_rpc_ping }, { "rpc.discover", json_rpc_discover }, { "wait", json_rpc_wait }, }; if (!json_is_object (request)) return json_rpc_response (NULL, NULL, json_rpc_error (JSON_RPC_ERROR_INVALID_REQUEST, NULL)); json_t *v = json_object_get (request, "jsonrpc"); json_t *m = json_object_get (request, "method"); json_t *params = json_object_get (request, "params"); json_t *id = json_object_get (request, "id"); const char *version; const char *method; bool ok = true; ok &= v && (version = json_string_value (v)) && !strcmp (version, "2.0"); ok &= m && (method = json_string_value (m)); ok &= !params || json_is_array (params) || json_is_object (params); ok &= !id || json_is_null (id) || json_is_string (id) || json_is_number (id); if (!ok) return json_rpc_response (id, NULL, json_rpc_error (JSON_RPC_ERROR_INVALID_REQUEST, NULL)); struct json_rpc_handler_info key = { .method_name = method }; struct json_rpc_handler_info *handler = bsearch (&key, handlers, N_ELEMENTS (handlers), sizeof key, json_rpc_handler_info_cmp); if (!handler) return json_rpc_response (id, NULL, json_rpc_error (JSON_RPC_ERROR_METHOD_NOT_FOUND, NULL)); json_t *response = handler->handler (ctx, params); if (id) { (void) json_object_set (response, "id", id); return response; } // Notifications don't get responses json_decref (response); return NULL; } static void flush_json (json_t *json, struct str *output) { char *utf8 = json_dumps (json, JSON_ENCODE_ANY); str_append (output, utf8); free (utf8); json_decref (json); } static void process_json_rpc (struct server_context *ctx, const void *data, size_t len, struct str *output) { json_error_t e; json_t *request; if (!(request = json_loadb (data, len, JSON_DECODE_ANY, &e))) { flush_json (json_rpc_response (NULL, NULL, json_rpc_error (JSON_RPC_ERROR_PARSE_ERROR, NULL)), output); return; } if (json_is_array (request)) { if (!json_array_size (request)) { flush_json (json_rpc_response (NULL, NULL, json_rpc_error (JSON_RPC_ERROR_INVALID_REQUEST, NULL)), output); return; } json_t *response = json_array (); json_t *iter; size_t i; json_array_foreach (request, i, iter) { json_t *result = process_json_rpc_request (ctx, iter); if (result) json_array_append_new (response, result); } if (json_array_size (response)) flush_json (response, output); else json_decref (response); } else { json_t *result = process_json_rpc_request (ctx, request); if (result) flush_json (result, output); } } /// @} // --- Requests ---------------------------------------------------------------- /// @defgroup Requests /// @{ /// A generic CGI request abstraction, writing data indirectly through callbacks struct request { struct server_context *ctx; ///< Server context struct request_handler *handler; ///< Assigned request handler void *handler_data; ///< User data for the handler /// Callback to write some CGI response data to the output void (*write_cb) (struct request *, const void *data, size_t len); /// Callback to close the CGI response, simulates end of program execution. /// CALLING THIS MAY CAUSE THE REQUEST TO BE DESTROYED. void (*finish_cb) (struct request *); }; /// An interface to detect and handle specific kinds of CGI requests. /// The server walks through a list of them until it finds one that can serve /// a particular request. If unsuccessful, the remote client gets a 404 /// (the default handling). struct request_handler { LIST_HEADER (struct request_handler) /// Install ourselves as the handler for the request, if applicable. /// If the request contains data, check it against CONTENT_LENGTH. /// ("Transfer-Encoding: chunked" should be dechunked by the HTTP server, /// however it is possible that it mishandles this situation.) /// Sets @a continue_ to false if further processing should be stopped, /// meaning the request has already been handled. /// Note that starting the response before receiving all data denies you /// the option of returning error status codes based on the data. bool (*try_handle) (struct request *request, struct str_map *headers, bool *continue_); /// Handle incoming data. "len == 0" means EOF. /// Returns false if there is no more processing to be done. /// EOF is never delivered on a network error (see client_read_loop()). // XXX: the EOF may or may not be delivered when the request is cut short: // - client_scgi delivers an EOF when it itself receives an EOF without // considering any mismatch, and it can deliver another one earlier // when the counter just goes down to 0... depends on what we return // from here upon the first occasion (whether we want to close). // - FCGI_ABORT_REQUEST /might/ not close the stdin and it /might/ cover // a CONTENT_LENGTH mismatch, since this callback wouldn't get invoked. // The FastCGI specification explicitly says to compare CONTENT_LENGTH // against the number of received bytes, which may only be smaller. // // We might want to adjust client_scgi and client_fcgi to not invoke // request_push(EOF) when CONTENT_LENGTH hasn't been reached and remove // the extra EOF generation from client_scgi (why is it there, does the // server keep the connection open, or is it just a precaution?) // // The finalization callback takes care of any needs to destruct data. // If we handle this reliably in all clients, try_handle won't have to, // as it will run in a stricter-than-CGI scenario. bool (*push_cb) (struct request *request, const void *data, size_t len); /// Destroy the handler's data stored in the request object void (*finalize_cb) (struct request *request); }; static void request_init (struct request *self) { memset (self, 0, sizeof *self); } static void request_free (struct request *self) { if (self->handler) self->handler->finalize_cb (self); } /// Write request CGI response data, intended for use by request handlers static void request_write (struct request *self, const void *data, size_t len) { self->write_cb (self, data, len); } /// This function is only intended to be run from asynchronous event handlers /// such as timers, not as a direct result of starting the request or receiving /// request data. CALLING THIS MAY CAUSE THE REQUEST TO BE DESTROYED. static void request_finish (struct request *self) { self->finish_cb (self); } /// Starts processing a request. Returns false if no further action is to be /// done and the request should be finished. static bool request_start (struct request *self, struct str_map *headers) { // XXX: it feels like this should rather be two steps: // bool (*can_handle) (request *, headers) // ... install the handler ... // bool (*handle) (request *) // // However that might cause some stuff to be done twice. // // Another way we could get rid of the continue_ argument is via adding // some way of marking the request as finished from within the handler. if (g_debug_mode) { struct str_map_iter iter = str_map_iter_make (headers); const char *value; while ((value = str_map_iter_next (&iter))) print_debug ("%s: %s", iter.link->key, value); print_debug ("--"); } bool continue_ = true; LIST_FOR_EACH (struct request_handler, handler, self->ctx->handlers) if (handler->try_handle (self, headers, &continue_)) { self->handler = handler; return continue_; } // Unable to serve the request struct str response = str_make (); str_append (&response, "Status: 404 Not Found\n"); str_append (&response, "Content-Type: text/plain\n\n"); request_write (self, response.str, response.len); str_free (&response); return false; } static bool request_push (struct request *self, const void *data, size_t len) { if (!soft_assert (self->handler)) // No handler, nothing to do with any data return false; return self->handler->push_cb (self, data, len); } /// @} // --- Requests handlers ------------------------------------------------------- static bool request_handler_json_rpc_try_handle (struct request *request, struct str_map *headers, bool *continue_) { const char *content_type = str_map_find (headers, "CONTENT_TYPE"); const char *method = str_map_find (headers, "REQUEST_METHOD"); if (!method || strcmp (method, "POST") || !content_type || !validate_json_rpc_content_type (content_type)) return false; struct str *buf = xcalloc (1, sizeof *buf); *buf = str_make (); request->handler_data = buf; *continue_ = true; return true; } static bool request_handler_json_rpc_push (struct request *request, const void *data, size_t len) { struct str *buf = request->handler_data; if (len) { str_append_data (buf, data, len); return true; } // TODO: check buf.len against CONTENT_LENGTH; if it's less, then the // client hasn't been successful in transferring all of its data. // See also comment on request_handler::push_cb. For JSON-RPC, though, // it shouldn't matter as an incomplete request will be invalid and // clients have no reason to append unnecessary trailing bytes. struct str response = str_make (); str_append (&response, "Status: 200 OK\n"); str_append_printf (&response, "Content-Type: %s\n\n", "application/json"); process_json_rpc (request->ctx, buf->str, buf->len, &response); request_write (request, response.str, response.len); str_free (&response); return false; } static void request_handler_json_rpc_finalize (struct request *request) { struct str *buf = request->handler_data; str_free (buf); free (buf); request->handler_data = NULL; } struct request_handler g_request_handler_json_rpc = { .try_handle = request_handler_json_rpc_try_handle, .push_cb = request_handler_json_rpc_push, .finalize_cb = request_handler_json_rpc_finalize, }; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /// Make a URL path canonical. The resulting path always begins with a slash, /// and any trailing slashes are lost. static char * canonicalize_url_path (const char *path) { struct strv v = strv_make (); cstr_split (path, "/", true, &v); struct strv canonical = strv_make (); strv_append (&canonical, ""); for (size_t i = 0; i < v.len; i++) { const char *dir = v.vector[i]; if (!strcmp (dir, ".")) continue; if (strcmp (dir, "..")) strv_append (&canonical, dir); else if (canonical.len > 1) // ".." never goes above the root strv_remove (&canonical, canonical.len - 1); } strv_free (&v); char *joined = strv_join (&canonical, "/"); strv_free (&canonical); return joined; } static char * detect_magic (const void *data, size_t len) { magic_t cookie; char *mime_type = NULL; if (!(cookie = magic_open (MAGIC_MIME))) return NULL; const char *magic = NULL; if (!magic_load (cookie, NULL) && (magic = magic_buffer (cookie, data, len))) mime_type = xstrdup (magic); else print_debug ("MIME type detection failed: %s", magic_error (cookie)); magic_close (cookie); return mime_type; } static bool request_handler_static_try_handle (struct request *request, struct str_map *headers, bool *continue_) { // Serving static files is actually quite complicated as it turns out; // but this is only meant to serve a few tiny text files struct server_context *ctx = request->ctx; const char *root = str_map_find (&ctx->config, "static_root"); if (!root) { print_debug ("static document root not configured"); return false; } // TODO: implement HEAD, we don't get that for free; // probably implies adding Content-Length const char *method = str_map_find (headers, "REQUEST_METHOD"); if (!method || strcmp (method, "GET")) return false; // TODO: look at , REQUEST_URI in the headers const char *path_info = str_map_find (headers, "PATH_INFO"); if (!path_info) path_info = str_map_find (headers, "REQUEST_URI"); if (!path_info) { print_debug ("neither PATH_INFO nor REQUEST_URI was defined"); return false; } // We need to filter the path to stay in our root // Being able to read /etc/passwd would be rather embarrasing char *suffix = canonicalize_url_path (path_info); char *path = xstrdup_printf ("%s%s", root, suffix); print_debug ("trying to statically serve %s", path); FILE *fp = fopen (path, "rb"); struct stat st = {}; if (fp && !fstat (fileno (fp), &st) && !S_ISREG (st.st_mode)) { fclose (fp); fp = NULL; } if (!fp) { struct str response = str_make (); str_append (&response, "Status: 404 Not Found\n"); str_append (&response, "Content-Type: text/plain\n\n"); str_append_printf (&response, "File %s was not found on this server\n", suffix); request_write (request, response.str, response.len); str_free (&response); free (suffix); free (path); return false; } free (suffix); free (path); uint8_t buf[8192]; size_t len; // Try to detect the Content-Type from the actual contents char *mime_type = NULL; if ((len = fread (buf, 1, sizeof buf, fp))) mime_type = detect_magic (buf, len); if (!mime_type) mime_type = xstrdup ("application/octet_stream"); struct str response = str_make (); str_append (&response, "Status: 200 OK\n"); str_append_printf (&response, "Content-Type: %s\n\n", mime_type); request_write (request, response.str, response.len); str_free (&response); free (mime_type); // Write the chunk we've used to help us with magic detection; // obviously we have to do it after we've written the headers if (len) request_write (request, buf, len); while ((len = fread (buf, 1, sizeof buf, fp))) request_write (request, buf, len); fclose (fp); // TODO: this should rather not be returned all at once but in chunks // (consider Transfer-Encoding); file read requests never return EAGAIN // TODO: actual file data should really be returned by a callback when // the socket is writable with nothing to be sent (pumping the entire // file all at once won't really work if it's huge). *continue_ = false; return true; } static bool request_handler_static_push (struct request *request, const void *data, size_t len) { (void) request; (void) data; if (len == 0) return true; // Aborting on content; we shouldn't receive any (GET). // In fact, we will only get here once try_handle stops dumping everything // into the write queue at once. print_debug ("the static file handler received data but shouldn't have"); return false; } static void request_handler_static_finalize (struct request *request) { (void) request; // Nothing to dispose of this far } struct request_handler g_request_handler_static = { .try_handle = request_handler_static_try_handle, .push_cb = request_handler_static_push, .finalize_cb = request_handler_static_finalize, }; // --- Client communication handlers ------------------------------------------- /// A virtual class for client connections coming either from the web server /// or directly from the end-client, depending on the protocol in use struct client { LIST_HEADER (struct client) struct client_vtable *vtable; ///< Client behaviour int socket_fd; ///< The network socket bool received_eof; ///< Whether EOF has been received yet bool flushing; ///< No more data to write, send FIN bool closing; ///< No more data to read or write bool half_closed; ///< Conn. half-closed while flushing struct write_queue write_queue; ///< Write queue ev_timer close_timeout_watcher; ///< Write queue flush timer ev_io read_watcher; ///< The socket can be read from ev_io write_watcher; ///< The socket can be written to }; /// The concrete behaviour to serve a particular client's requests struct client_vtable { /// Process incoming data; "len == 0" means EOF. /// If the method returns false, client_close() is called by the caller. bool (*push) (struct client *client, const void *data, size_t len); // TODO: optional push_error() to inform about network I/O errors /// Attempt a graceful shutdown: make any appropriate steps before /// the client connection times out and gets torn down by force. /// The client is allowed to destroy itself immediately. void (*shutdown) (struct client *client); /// Do any additional cleanup for the concrete class before destruction void (*finalize) (struct client *client); }; static void client_destroy (struct client *self) { // XXX: this codebase halfway pretends there could be other contexts struct server_context *ctx = ev_userdata (EV_DEFAULT); LIST_UNLINK (ctx->clients, self); ctx->n_clients--; // First uninitialize the higher-level implementation self->vtable->finalize (self); ev_io_stop (EV_DEFAULT_ &self->read_watcher); ev_io_stop (EV_DEFAULT_ &self->write_watcher); xclose (self->socket_fd); write_queue_free (&self->write_queue); ev_timer_stop (EV_DEFAULT_ &self->close_timeout_watcher); free (self); try_finish_quit (ctx); } static void client_write_unsafe (struct client *self, void *data, size_t len) { struct write_req *req = xcalloc (1, sizeof *req); req->data.iov_base = data; req->data.iov_len = len; write_queue_add (&self->write_queue, req); ev_io_start (EV_DEFAULT_ &self->write_watcher); } static void client_write_owned (struct client *self, void *data, size_t len) { if (soft_assert (!self->flushing) && len != 0) client_write_unsafe (self, data, len); else free (data); } static void client_write (struct client *self, const void *data, size_t len) { if (soft_assert (!self->flushing) && len != 0) client_write_unsafe (self, memcpy (xmalloc (len), data, len), len); } /// Half-close the connection from our side once the write_queue is flushed. /// It is the caller's responsibility to destroy the connection upon EOF. // XXX: or we might change on_client_readable to do it anyway, seems safe static void client_shutdown (struct client *self) { self->flushing = true; // In case this shutdown is immediately followed by a destroy (client_ws // calls client_destroy() rather than client_close() in case of a request // timeout), try our best (void) flush_queue (&self->write_queue, self->socket_fd); ev_feed_event (EV_DEFAULT_ &self->write_watcher, EV_WRITE); } /// Try to cleanly close the connection, waiting for the remote client to close /// its own side of the connection as a sign that it has processed all the data /// it wanted to. The client implementation will not receive any further data. /// May directly call client_destroy(). static void client_close (struct client *self) { if (self->closing) return; self->closing = true; ev_timer_start (EV_DEFAULT_ &self->close_timeout_watcher); client_shutdown (self); // We assume the remote client doesn't want our data if it half-closes if (self->received_eof) client_destroy (self); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static bool client_read_loop (EV_P_ struct client *client, ev_io *watcher) { char buf[8192]; ssize_t n_read; again: while ((n_read = recv (watcher->fd, buf, sizeof buf, 0)) >= 0) { if (!n_read) { // Don't deliver the EOF condition repeatedly ev_io_stop (EV_A_ watcher); client->received_eof = true; } if (!client->closing && !client->vtable->push (client, buf, n_read)) { client_close (client); return false; } if (!n_read) return true; } if (errno == EINTR) goto again; if (errno == EAGAIN) return true; client_destroy (client); return false; } static void on_client_readable (EV_P_ ev_io *watcher, int revents) { struct client *client = watcher->data; (void) revents; if (client_read_loop (EV_A_ client, watcher) && client->closing && client->received_eof) client_destroy (client); } static void on_client_writable (EV_P_ ev_io *watcher, int revents) { struct client *client = watcher->data; (void) loop; (void) revents; // TODO: some sort of "on_buffers_flushed" callback for streaming huge // chunks of external (or generated) data. That will need to be // forwarded to "struct request_handler". if (!flush_queue (&client->write_queue, watcher->fd)) { client_destroy (client); return; } if (!write_queue_is_empty (&client->write_queue)) return; ev_io_stop (EV_A_ watcher); if (client->flushing && !client->half_closed) { if (!shutdown (client->socket_fd, SHUT_WR)) client->half_closed = true; else client_destroy (client); } } static void on_client_timeout (EV_P_ ev_timer *watcher, int revents) { (void) loop; (void) revents; client_destroy (watcher->data); } /// Create a new instance of a subclass with the given size. /// The superclass is assumed to be the first member of the structure. static void * client_new (EV_P_ size_t size, int sock_fd) { struct server_context *ctx = ev_userdata (loop); struct client *self = xcalloc (1, size); self->write_queue = write_queue_make (); ev_timer_init (&self->close_timeout_watcher, on_client_timeout, 5., 0.); self->close_timeout_watcher.data = self; set_blocking (sock_fd, false); self->socket_fd = sock_fd; ev_io_init (&self->read_watcher, on_client_readable, sock_fd, EV_READ); ev_io_init (&self->write_watcher, on_client_writable, sock_fd, EV_WRITE); self->read_watcher.data = self; self->write_watcher.data = self; // We're only interested in reading as the write queue is empty now ev_io_start (EV_A_ &self->read_watcher); LIST_PREPEND (ctx->clients, self); ctx->n_clients++; return self; } // --- FastCGI client handler -------------------------------------------------- struct client_fcgi { struct client client; ///< Parent class struct fcgi_muxer muxer; ///< FastCGI de/multiplexer }; struct client_fcgi_request { struct fcgi_request *fcgi_request; ///< FastCGI request struct request request; ///< Request }; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static void client_fcgi_request_write_cb (struct request *req, const void *data, size_t len) { FIND_CONTAINER (self, req, struct client_fcgi_request, request); fcgi_request_write (self->fcgi_request, data, len); } // XXX: it should be possible to pass a specific status code but we'd have to // allow it in multiple places over this code base, notably request_push() static void client_fcgi_request_finish_cb (struct request *req) { FIND_CONTAINER (self, req, struct client_fcgi_request, request); struct fcgi_muxer *muxer = self->fcgi_request->muxer; // No more data to send, terminate the substream/request, // and also the transport if the client didn't specifically ask to keep it if (!fcgi_request_finish (self->fcgi_request, EXIT_SUCCESS)) muxer->close_cb (muxer); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static bool client_fcgi_request_start (struct fcgi_request *fcgi_request) { struct client_fcgi_request *request = fcgi_request->handler_data = xcalloc (1, sizeof *request); request->fcgi_request = fcgi_request; request_init (&request->request); request->request.ctx = ev_userdata (EV_DEFAULT); request->request.write_cb = client_fcgi_request_write_cb; request->request.finish_cb = client_fcgi_request_finish_cb; return request_start (&request->request, &fcgi_request->headers); } static bool client_fcgi_request_push (struct fcgi_request *req, const void *data, size_t len) { struct client_fcgi_request *request = req->handler_data; return request_push (&request->request, data, len) || fcgi_request_finish (req, EXIT_SUCCESS); } static void client_fcgi_request_finalize (struct fcgi_request *req) { struct client_fcgi_request *request = req->handler_data; request_free (&request->request); free (request); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static void client_fcgi_write_cb (struct fcgi_muxer *mux, void *data, size_t len) { FIND_CONTAINER (self, mux, struct client_fcgi, muxer); client_write_owned (&self->client, data, len); } static void client_fcgi_close_cb (struct fcgi_muxer *mux) { FIND_CONTAINER (self, mux, struct client_fcgi, muxer); client_close (&self->client); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static bool client_fcgi_push (struct client *client, const void *data, size_t len) { FIND_CONTAINER (self, client, struct client_fcgi, client); return fcgi_muxer_push (&self->muxer, data, len); } static void client_fcgi_shutdown (struct client *client) { FIND_CONTAINER (self, client, struct client_fcgi, client); self->muxer.in_shutdown = true; // TODO: respond with FCGI_END_REQUEST: FCGI_REQUEST_COMPLETE to everything? // The FastCGI specification isn't very clear about what we should do. } static void client_fcgi_finalize (struct client *client) { FIND_CONTAINER (self, client, struct client_fcgi, client); fcgi_muxer_free (&self->muxer); } static struct client_vtable client_fcgi_vtable = { .push = client_fcgi_push, .shutdown = client_fcgi_shutdown, .finalize = client_fcgi_finalize, }; static struct client * client_fcgi_create (EV_P_ const char *host, int sock_fd) { (void) host; struct client_fcgi *self = client_new (EV_A_ sizeof *self, sock_fd); self->client.vtable = &client_fcgi_vtable; fcgi_muxer_init (&self->muxer); self->muxer.write_cb = client_fcgi_write_cb; self->muxer.close_cb = client_fcgi_close_cb; self->muxer.request_start_cb = client_fcgi_request_start; self->muxer.request_push_cb = client_fcgi_request_push; self->muxer.request_finalize_cb = client_fcgi_request_finalize; return &self->client; } // --- SCGI client handler ----------------------------------------------------- struct client_scgi { struct client client; ///< Parent class struct scgi_parser parser; ///< SCGI stream parser struct request request; ///< Request (only one per connection) unsigned long remaining_content; ///< Length of input data to be seen }; static void client_scgi_write_cb (struct request *req, const void *data, size_t len) { FIND_CONTAINER (self, req, struct client_scgi, request); client_write (&self->client, data, len); } static void client_scgi_finish_cb (struct request *req) { FIND_CONTAINER (self, req, struct client_scgi, request); client_close (&self->client); } static bool client_scgi_on_headers_read (void *user_data) { struct client_scgi *self = user_data; const char *cl = str_map_find (&self->parser.headers, "CONTENT_LENGTH"); if (!cl || !xstrtoul (&self->remaining_content, cl, 10)) { print_debug ("SCGI request with invalid or missing CONTENT_LENGTH"); return false; } return request_start (&self->request, &self->parser.headers); } static bool client_scgi_on_content (void *user_data, const void *data, size_t len) { struct client_scgi *self = user_data; if (len > self->remaining_content) { print_debug ("SCGI request got more data than CONTENT_LENGTH"); return false; } // We're in a slight disagreement with the SCGI specification since // this tries to write output before it has read all the input if (!request_push (&self->request, data, len)) return false; if ((self->remaining_content -= len)) return true; // Signalise end of input to the request handler return request_push (&self->request, NULL, 0); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static bool client_scgi_push (struct client *client, const void *data, size_t len) { struct client_scgi *self = (struct client_scgi *) client; struct error *e = NULL; if (scgi_parser_push (&self->parser, data, len, &e)) return true; if (e != NULL) { print_debug ("SCGI parser failed: %s", e->message); error_free (e); } return false; } static void client_scgi_finalize (struct client *client) { struct client_scgi *self = (struct client_scgi *) client; request_free (&self->request); scgi_parser_free (&self->parser); } static struct client_vtable client_scgi_vtable = { .push = client_scgi_push, .finalize = client_scgi_finalize, }; static struct client * client_scgi_create (EV_P_ const char *host, int sock_fd) { (void) host; struct client_scgi *self = client_new (EV_A_ sizeof *self, sock_fd); self->client.vtable = &client_scgi_vtable; request_init (&self->request); self->request.ctx = ev_userdata (EV_DEFAULT); self->request.write_cb = client_scgi_write_cb; self->request.finish_cb = client_scgi_finish_cb; self->parser = scgi_parser_make (); self->parser.on_headers_read = client_scgi_on_headers_read; self->parser.on_content = client_scgi_on_content; self->parser.user_data = self; return &self->client; } // --- WebSocket client handler ------------------------------------------------ struct client_ws { struct client client; ///< Parent class struct ws_handler handler; ///< WebSocket connection handler }; static bool client_ws_on_message (struct ws_handler *handler, enum ws_opcode type, const void *data, size_t len) { FIND_CONTAINER (self, handler, struct client_ws, handler); if (type != WS_OPCODE_TEXT) { return ws_handler_fail_connection (&self->handler, WS_STATUS_UNSUPPORTED_DATA); } struct server_context *ctx = ev_userdata (EV_DEFAULT); struct str response = str_make (); process_json_rpc (ctx, data, len, &response); if (response.len) ws_handler_send_frame (&self->handler, WS_OPCODE_TEXT, response.str, response.len); str_free (&response); return true; } static void client_ws_write_cb (struct ws_handler *handler, const void *data, size_t len) { FIND_CONTAINER (self, handler, struct client_ws, handler); client_write (&self->client, data, len); } static void client_ws_close_cb (struct ws_handler *handler, bool half_close) { FIND_CONTAINER (self, handler, struct client_ws, handler); (half_close ? client_shutdown : client_destroy) (&self->client); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static bool client_ws_push (struct client *client, const void *data, size_t len) { FIND_CONTAINER (self, client, struct client_ws, client); // client_close() will correctly destroy the client on EOF return ws_handler_push (&self->handler, data, len); } static void client_ws_shutdown (struct client *client) { FIND_CONTAINER (self, client, struct client_ws, client); if (self->handler.state == WS_HANDLER_CONNECTING) // No on_close, no problem client_destroy (&self->client); else if (self->handler.state == WS_HANDLER_OPEN) ws_handler_close (&self->handler, WS_STATUS_GOING_AWAY, NULL, 0); } static void client_ws_finalize (struct client *client) { FIND_CONTAINER (self, client, struct client_ws, client); ws_handler_free (&self->handler); } static struct client_vtable client_ws_vtable = { .push = client_ws_push, .shutdown = client_ws_shutdown, .finalize = client_ws_finalize, }; static struct client * client_ws_create (EV_P_ const char *host, int sock_fd) { (void) host; struct client_ws *self = client_new (EV_A_ sizeof *self, sock_fd); self->client.vtable = &client_ws_vtable; ws_handler_init (&self->handler); self->handler.on_message = client_ws_on_message; self->handler.write_cb = client_ws_write_cb; self->handler.close_cb = client_ws_close_cb; // One mebibyte seems to be a reasonable value self->handler.max_payload_len = 1 << 10; ws_handler_start (&self->handler); return &self->client; } // --- HTTP client handler ----------------------------------------------------- struct client_http { struct client client; ///< Parent class struct ws_handler handler; ///< WebSocket connection handler struct request request; ///< Request (only one per connection) char *listening_host; ///< Listening hostname or IP int listening_port; ///< Listening port http_parser hp; ///< HTTP parser bool have_header_value; ///< Parsing header value or field? struct str field; ///< Field part buffer struct str value; ///< Value part buffer struct str_map headers; ///< HTTP Headers struct str url; ///< Request URL struct str message; ///< Message data struct str response; ///< CGI response ev_timer request_timeout_watcher; ///< Request timeout watcher unsigned request_timeout; ///< How long to wait for the handshake }; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // TODO: refactor this part as much as possible, see also http_parserpp static bool client_http_header_field_is_a_list (const char *name) { // This must contain all header fields we use for anything static const char *concatenable[] = { SEC_WS_PROTOCOL, SEC_WS_EXTENSIONS, "Connection", "Upgrade" }; for (size_t i = 0; i < N_ELEMENTS (concatenable); i++) if (!strcasecmp_ascii (name, concatenable[i])) return true; return false; } static void client_http_on_header_read (struct client_http *self) { // The HTTP parser unfolds values and removes preceding whitespace, but // otherwise doesn't touch the values or the following whitespace. // RFC 7230 states that trailing whitespace is not part of a field value char *value = self->field.str; size_t len = self->field.len; while (len--) if (value[len] == '\t' || value[len] == ' ') value[len] = '\0'; else break; self->field.len = len; const char *field = self->field.str; const char *current = str_map_find (&self->headers, field); if (client_http_header_field_is_a_list (field) && current) str_map_set (&self->headers, field, xstrdup_printf ("%s, %s", current, self->value.str)); else // If the field cannot be concatenated, just overwrite the last value. // Maybe we should issue a warning or something. str_map_set (&self->headers, field, xstrdup (self->value.str)); } static int client_http_on_header_field (http_parser *parser, const char *at, size_t len) { struct client_http *self = parser->data; if (self->have_header_value) { client_http_on_header_read (self); str_reset (&self->field); str_reset (&self->value); } str_append_data (&self->field, at, len); self->have_header_value = false; return 0; } static int client_http_on_header_value (http_parser *parser, const char *at, size_t len) { struct client_http *self = parser->data; str_append_data (&self->value, at, len); self->have_header_value = true; return 0; } static bool client_http_request_start (struct client_http *self) { struct str_map cgi_headers = str_map_make (free); cgi_headers.key_xfrm = tolower_ascii_strxfrm; struct str_map_iter iter = str_map_iter_make (&self->headers); const char *value; while ((value = str_map_iter_next (&iter))) { char *protocol_key = xstrdup_printf ("HTTP_%s", iter.link->key); for (char *p = protocol_key; *p; p++) *p = (*p == '-') ? '_' : toupper_ascii (*p); str_map_set (&cgi_headers, protocol_key, xstrdup (value)); free (protocol_key); } // This so far is enough for request_handler_json_rpc_try_handle() // TODO: set all the required ones https://tools.ietf.org/html/rfc3875 #define HEADER(name, value) str_map_set (&cgi_headers, (name), (value)) // TODO: AUTH_TYPE: parse from any Authentication header // Trust the client and http-parser that it is set when it should be const char *content_length = str_map_find (&self->headers, "Content-Length"); if (content_length) HEADER ("CONTENT_LENGTH", xstrdup (content_length)); const char *content_type = str_map_find (&self->headers, "Content-Type"); if (content_type) HEADER ("CONTENT_TYPE", xstrdup (content_type)); HEADER ("GATEWAY_INTERFACE", xstrdup ("CGI/1.1")); // TODO: ?PATH_INFO: probably empty or even ${UF_PATH} // TODO: ?PATH_TRANSLATED: can be relative to "static_root", or just empty // TODO: QUERY_STRING: ${UF_QUERY} // TODO: ?REMOTE_ADDR: we get this from accept(), save it somewhere // or even re-query it, although it's getpeername() getnameinfo(); // on_client_available() can store it in the returned object (unused) // TODO: ?REMOTE_HOST: we'd have to be passed this, can be empty // TODO: ?REMOTE_IDENT: skip // TODO: ?REMOTE_USER: skip HEADER ("REQUEST_METHOD", xstrdup (http_method_str (self->hp.method))); // TODO: SCRIPT_NAME: empty, especially if PATH_INFO is ${UF_PATH} // TODO: SERVER_NAME: parse Host, if any, fall back to self->listening_host HEADER ("SERVER_PORT", xstrdup_printf ("%d", self->listening_port)); HEADER ("SERVER_PROTOCOL", xstrdup ("HTTP/1.1")); HEADER ("SERVER_SOFTWARE", xstrdup_printf ("%s/%s", PROGRAM_NAME, PROGRAM_VERSION)); #undef HEADER // NOTE: this doesn't necessarily affect HTTP keep-alive, // so long as the output buffer isn't reprocessed str_reset (&self->response); bool keep_alive = request_start (&self->request, &cgi_headers); str_map_free (&cgi_headers); return keep_alive; } static size_t parse_cgi_response_headers (const struct str *response, struct str_map *out) { // This is simplified because we're only parsing local, sane responses char *p = response->str, *end = p + response->len, *eol = NULL; while ((eol = memchr (p, '\n', end - p))) { char *name = p, *value = memchr (p, ':', eol - p); p = eol + 1; // "UNIX: [...] servers should also accept CR LF as a newline" if (name < eol && eol[-1] == '\r') eol--; if (name == eol) return p - response->str; if (!value) return 0; // missing colon *value++ = '\0'; // terminate the name to avoid having to copy it value += strspn (value, " \t"); // skip OWS at the beginning str_map_set (out, name, xstrndup (value, eol - value)); } return 0; // missing or unterminated headers } static int client_http_request_parse_cgi (struct client_http *self, struct str_map *headers) { size_t advance = parse_cgi_response_headers (&self->response, headers); if (!advance) return 0; int code = 200; // We do not support local redirects, so use an appropriate status code if (str_map_find (headers, "Location")) code = 302; const char *status = str_map_find (headers, "Status"); if (status && (code = atoi (status)) < 100) return 0; str_map_set (headers, "Status", NULL); if (!str_map_find (headers, "Content-Type")) print_warning ("missing Content-Type header"); // Enforce correctness; we have no use for Transfer-Encoding chunked now str_map_set (headers, "Content-Length", xstrdup_printf ("%zu", self->response.len - advance)); // In the name of simplicity and despite wastefulness str_remove_slice (&self->response, 0, advance); return code; } static void client_http_request_finish (struct client_http *self) { struct str_map headers = str_map_make (free); headers.key_xfrm = tolower_ascii_strxfrm; request_free (&self->request); int code = client_http_request_parse_cgi (self, &headers); if (!code) { str_reset (&self->response); str_append (&self->response, "Invalid CGI response."); code = 500; } str_map_set (&headers, "Connection", xstrdup (http_should_keep_alive (&self->hp) ? "keep-alive" : "close")); struct strv fields = strv_make (); struct str_map_iter iter = str_map_iter_make (&headers); const char *value = NULL; while ((value = str_map_iter_next (&iter))) strv_append_owned (&fields, xstrdup_printf ("%s: %s", iter.link->key, value)); // XXX: this is still for from optimal (possible conflicts, too complex) char *status = xstrdup_printf ("%03d %s", code, http_status_str (code)); struct str response = ws_handler_build_http_responsev (status, fields.vector); free (status); strv_free (&fields); str_map_free (&headers); client_write (&self->client, response.str, response.len); client_write (&self->client, self->response.str, self->response.len); str_free (&response); } static bool client_http_request_fail (struct client_http *self, const char *status, ...) { va_list ap; va_start (ap, status); const char *s; struct strv fields = strv_make (); while ((s = va_arg (ap, const char *))) strv_append (&fields, s); strv_append (&fields, "Connection: close"); va_end (ap); struct str response = ws_handler_build_http_responsev (status, fields.vector); strv_free (&fields); client_write (&self->client, response.str, response.len); str_free (&response); // TODO: consider client_shutdown() to put this to a flushing state // - that would mean no other request can be answered return false; } static void client_http_write_cb (struct request *req, const void *data, size_t len) { FIND_CONTAINER (self, req, struct client_http, request); str_append_data (&self->response, data, len); } static int client_http_on_headers_complete (http_parser *parser) { struct client_http *self = parser->data; if (self->have_header_value) client_http_on_header_read (self); if (str_map_find (&self->headers, "Transfer-Encoding")) { // TODO: see what this would mean to implement (should support chunked) // - probably that we'd have to pre-buffer the whole message // before starting the request (for chunked) client_http_request_fail (self, "501 Not Implemented", NULL); return 3; } // TODO: it should normally abort when it sees an upgrade but when // the Content-Length is non-zero or the message is chunked, // it reads the body first... see if it allows that for GET, // and if it lets non-GET requests make an upgrade. // If we return 1 from here on "upgrade", we can ensure an abort. // TODO: parse the URI (self->url) // - seems like this should go in client_http_request_start() // TODO: reject URIs not containing UF_PATH that aren't "*" if (!client_http_request_start (self)) { // FIXME: we want to enable keeping the connection alive in this case, // though we (might) have a body to skip now client_http_request_finish (self); return 3; } return 0; } static int client_http_on_url (http_parser *parser, const char *at, size_t len) { struct client_http *self = parser->data; str_append_data (&self->url, at, len); return 0; } static int client_http_on_message_begin (http_parser *parser) { struct client_http *self = parser->data; ev_timer_start (EV_DEFAULT_ &self->request_timeout_watcher); str_reset (&self->message); return 0; } static int client_http_on_body (http_parser *parser, const char *at, size_t len) { struct client_http *self = parser->data; str_append_data (&self->message, at, len); // TODO: consider pushing data to the request in here. // We may not be able to do that when there's a Content-Encoding // or a Transfer-Encoding other than chunked. For the chunked encoding, // we'll probably want to keep filling the message and process it in // on_message_complete. There may also be other Content/Transfer-Encoding // filters, in particular gzip. And beware that this means we need to // delay starting the request as well. if (!request_push (&self->request, at, len)) { // FIXME: we want to enable keeping the connection alive in this case, // though we (might) have a body to skip now client_http_request_finish (self); return 1; } return 0; } static int client_http_on_message_complete (http_parser *parser) { struct client_http *self = parser->data; str_reset (&self->message); // TODO: here we could start a keep-alive timeout, so far we let // the current timer run and only reset it on new messages // XXX: if it decides to stay alive, we forcibly interpret the result, // we might want to throw it away instead bool continue_ = request_push (&self->request, NULL, 0); client_http_request_finish (self); return continue_; } static void client_http_on_request_timeout (EV_P_ ev_timer *watcher, int revents) { (void) loop; (void) revents; struct client_http *self = watcher->data; client_http_request_fail (self, HTTP_408_REQUEST_TIMEOUT, NULL); client_destroy (&self->client); } #define FAIL_REQUEST(...) \ return client_http_request_fail (self, __VA_ARGS__, NULL) static bool client_http_push (struct client *client, const void *data, size_t len) { FIND_CONTAINER (self, client, struct client_http, client); // client_close() will correctly destroy the client on EOF if (self->handler.state != WS_HANDLER_CONNECTING) return ws_handler_push (&self->handler, data, len); static const http_parser_settings http_settings = { .on_header_field = client_http_on_header_field, .on_header_value = client_http_on_header_value, .on_headers_complete = client_http_on_headers_complete, .on_url = client_http_on_url, .on_message_begin = client_http_on_message_begin, .on_body = client_http_on_body, .on_message_complete = client_http_on_message_complete, }; size_t n_parsed = http_parser_execute (&self->hp, &http_settings, data, len); // TODO: in all code, we can use http_status_str() and numerics from // the respective constants in `enum http_status` if (self->hp.upgrade) { ev_timer_stop (EV_DEFAULT_ &self->request_timeout_watcher); // The handshake hasn't been finished, yet there is more data // to be processed after the headers already if (len - n_parsed) FAIL_REQUEST (HTTP_400_BAD_REQUEST); // TODO: make it use our url and headers (http_parserpp *?) // TODO: move the HTTP version check in here return ws_handler_finish_handshake (&self->handler); } enum http_errno err = HTTP_PARSER_ERRNO (&self->hp); if (n_parsed != len || err != HPE_OK) { ev_timer_stop (EV_DEFAULT_ &self->request_timeout_watcher); if (err == HPE_CB_headers_complete || err == HPE_CB_body || err == HPE_CB_message_complete) return false; print_debug ("HTTP handshake failed: %s", http_errno_description (err)); FAIL_REQUEST (HTTP_400_BAD_REQUEST); } // TODO: investigate this: // - if len == 0, we /certainly/ want to call it quits here // - but double check check flushing logic // - we enforce some "Connection: close" cases above // - maybe an internal bool: // - start with keep_alive = true (nothing received yet) // - http_parser will error out on inappropriate EOF // - then in on_headers_complete reset it to http_should_keep_alive() // - use this in client_http_request_finish() // - still lost... // - maybe client_http_request_finish() should always abort the parser // when the connection has been closed? // - go have a look at connection closure in the HTTP specification return len != 0; } static void client_http_shutdown (struct client *client) { FIND_CONTAINER (self, client, struct client_http, client); if (self->handler.state == WS_HANDLER_CONNECTING) // No on_close, no problem client_destroy (&self->client); else if (self->handler.state == WS_HANDLER_OPEN) ws_handler_close (&self->handler, WS_STATUS_GOING_AWAY, NULL, 0); } static void client_http_finalize (struct client *client) { FIND_CONTAINER (self, client, struct client_http, client); ws_handler_free (&self->handler); request_free (&self->request); cstr_set (&self->listening_host, NULL); str_free (&self->field); str_free (&self->value); str_map_free (&self->headers); str_free (&self->url); str_free (&self->message); str_free (&self->response); ev_timer_stop (EV_DEFAULT_ &self->request_timeout_watcher); } static struct client_vtable client_http_vtable = { .push = client_http_push, .shutdown = client_http_shutdown, .finalize = client_http_finalize, }; static struct client * client_http_create (EV_P_ const char *local_host, int sock_fd) { struct client_http *self = client_new (EV_A_ sizeof *self, sock_fd); self->client.vtable = &client_http_vtable; // XXX: `struct sockaddr_storage' is not the most portable thing struct sockaddr_storage sock; socklen_t sock_len = sizeof sock; // When we're bound to the any address, at least try to fetch an IP string char num_host[NI_MAXHOST] = "", num_serv[NI_MAXSERV] = ""; if (!getsockname (sock_fd, (struct sockaddr *) &sock, &sock_len) && !getnameinfo ((struct sockaddr *) &sock, sock_len, num_host, sizeof num_host, num_serv, sizeof num_serv, NI_NUMERICHOST | NI_NUMERICSERV) && !*local_host) local_host = num_host; self->listening_host = xstrdup (local_host); self->listening_port = atoi (num_serv); // We may or may not upgrade to WebSocket (keep WS_HANDLER_CONNECTING) ws_handler_init (&self->handler); self->handler.on_message = client_ws_on_message; self->handler.write_cb = client_ws_write_cb; self->handler.close_cb = client_ws_close_cb; // One mebibyte seems to be a reasonable value self->handler.max_payload_len = 1 << 10; request_init (&self->request); self->request.ctx = ev_userdata (EV_DEFAULT); self->request.write_cb = client_http_write_cb; // TODO: finish_cb: call client_http_request_finish(); // make sure we don't free ourselves and crash // - this is actually never called because nothing is asynchronous http_parser_init (&self->hp, HTTP_REQUEST); self->hp.data = self; self->field = str_make (); self->value = str_make (); self->headers = str_map_make (free); self->headers.key_xfrm = tolower_ascii_strxfrm; self->url = str_make (); self->message = str_make (); self->response = str_make (); ev_timer_init (&self->request_timeout_watcher, client_http_on_request_timeout, 0., 0.); self->request_timeout_watcher.data = self; ev_timer_set (&self->request_timeout_watcher, (self->request_timeout = 60), 0.); ev_timer_start (EV_DEFAULT_ &self->request_timeout_watcher); return &self->client; } // --- Co-process client ------------------------------------------------------- // This is mostly copied over from json-rpc-shell.c, only a bit simplified. // We're giving up on header parsing in order to keep this small. struct co_context { struct server_context *ctx; ///< Server context struct str message; ///< Message data struct http_parser parser; ///< HTTP parser bool pending_fake_starter; ///< Start of message? }; static int client_co_on_message_begin (http_parser *parser) { struct co_context *self = parser->data; str_reset (&self->message); return 0; } static int client_co_on_body (http_parser *parser, const char *at, size_t len) { struct co_context *self = parser->data; str_append_data (&self->message, at, len); return 0; } static int client_co_on_message_complete (http_parser *parser) { struct co_context *self = parser->data; http_parser_pause (&self->parser, true); return 0; } // The LSP incorporates a very thin subset of RFC 822, and it so happens // that we may simply reuse the full HTTP parser here, with a small hack. static const http_parser_settings client_co_http_settings = { .on_message_begin = client_co_on_message_begin, .on_body = client_co_on_body, .on_message_complete = client_co_on_message_complete, }; static void client_co_respond (const struct str *buf) { struct str wrapped = str_make(); str_append_printf (&wrapped, "Content-Length: %zu\r\n" "Content-Type: application/json; charset=utf-8\r\n" "\r\n", buf->len); str_append_data (&wrapped, buf->str, buf->len); if (write (STDOUT_FILENO, wrapped.str, wrapped.len) != (ssize_t) wrapped.len) exit_fatal ("write: %s", strerror (errno)); str_free (&wrapped); } static void client_co_inject_starter (struct co_context *self) { // The default "Connection: keep-alive" maps well here. // We cannot feed this line into the parser from within callbacks. static const char starter[] = "POST / HTTP/1.1\r\n"; http_parser_pause (&self->parser, false); size_t n_parsed = http_parser_execute (&self->parser, &client_co_http_settings, starter, sizeof starter - 1); enum http_errno err = HTTP_PARSER_ERRNO (&self->parser); if (n_parsed != sizeof starter - 1 || err != HPE_OK) exit_fatal ("protocol failure: %s", http_errno_description (err)); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static void client_co_process (struct co_context *self) { struct str *message = &self->message; struct str response = str_make (); process_json_rpc (self->ctx, message->str, message->len, &response); if (response.len) client_co_respond (&response); str_free (&response); } static void client_co_parse (struct co_context *self, const char *data, size_t len, size_t *n_parsed) { if (self->pending_fake_starter) { self->pending_fake_starter = false; client_co_inject_starter (self); } *n_parsed = http_parser_execute (&self->parser, &client_co_http_settings, data, len); if (self->parser.upgrade) exit_fatal ("protocol failure: %s", "unsupported upgrade attempt"); enum http_errno err = HTTP_PARSER_ERRNO (&self->parser); if (err == HPE_PAUSED) { self->pending_fake_starter = true; client_co_process (self); } else if (err != HPE_OK) exit_fatal ("protocol failure: %s", http_errno_description (err)); } static void client_co_on_data (struct co_context *self, const char *data, size_t len) { size_t n_parsed = 0; do { client_co_parse (self, data, len, &n_parsed); data += n_parsed; } while ((len -= n_parsed)); } static void client_co_run (struct server_context *ctx) { struct co_context self = {}; self.ctx = ctx; self.message = str_make (); http_parser_init (&self.parser, HTTP_REQUEST); self.parser.data = &self; self.pending_fake_starter = true; hard_assert (set_blocking (STDIN_FILENO, false)); struct str buf = str_make (); struct pollfd pfd = { .fd = STDIN_FILENO, .events = POLLIN }; while (true) { if (poll (&pfd, 1, -1) <= 0) exit_fatal ("poll: %s", strerror (errno)); str_remove_slice (&buf, 0, buf.len); enum socket_io_result result = socket_io_try_read (pfd.fd, &buf); int errno_saved = errno; if (buf.len) client_co_on_data (&self, buf.str, buf.len); if (result == SOCKET_IO_ERROR) exit_fatal ("read: %s", strerror (errno_saved)); if (result == SOCKET_IO_EOF) break; } str_free (&buf); str_free (&self.message); } // --- Basic server stuff ------------------------------------------------------ typedef struct client * (*client_create_fn) (EV_P_ const char *host, int sock_fd); struct listener { int fd; ///< Listening socket FD ev_io watcher; ///< New connection available client_create_fn create; ///< Client constructor char *host; ///< Hostname, IP or empty for any }; static void close_listeners (struct server_context *self) { for (size_t i = 0; i < self->n_listeners; i++) { struct listener *listener = &self->listeners[i]; if (listener->fd == -1) continue; ev_io_stop (EV_DEFAULT_ &listener->watcher); xclose (listener->fd); listener->fd = -1; cstr_set (&listener->host, NULL); } } static void try_finish_quit (struct server_context *self) { if (!self->quitting || self->clients) return; ev_timer_stop (EV_DEFAULT_ &self->quit_timeout_watcher); ev_break (EV_DEFAULT_ EVBREAK_ALL); } static void on_quit_timeout (EV_P_ ev_timer *watcher, int revents) { struct server_context *self = watcher->data; (void) loop; (void) revents; LIST_FOR_EACH (struct client, iter, self->clients) client_destroy (iter); } static void initiate_quit (struct server_context *self) { self->quitting = true; close_listeners (self); // Wait a little while for all clients to clean up, if necessary LIST_FOR_EACH (struct client, iter, self->clients) if (iter->vtable->shutdown) iter->vtable->shutdown (iter); ev_timer_start (EV_DEFAULT_ &self->quit_timeout_watcher); try_finish_quit (self); } static void on_client_available (EV_P_ ev_io *watcher, int revents) { struct server_context *ctx = ev_userdata (loop); struct listener *listener = watcher->data; (void) revents; while (true) { int sock_fd = accept (watcher->fd, NULL, NULL); if (sock_fd != -1) listener->create (EV_A_ listener->host, sock_fd); else if (errno == EAGAIN) return; else if (errno != EINTR && errno != EMFILE && errno != ECONNRESET && errno != ECONNABORTED) break; } // Stop accepting connections to prevent busy looping ev_io_stop (EV_A_ watcher); print_fatal ("%s: %s", "accept", strerror (errno)); initiate_quit (ctx); } // --- Application setup ------------------------------------------------------- /// This function handles values that require validation before their first use, /// or some kind of a transformation (such as conversion to an integer) needs /// to be done before they can be used directly. static bool parse_config (struct server_context *ctx, struct error **e) { (void) ctx; (void) e; return true; } static int listener_bind (struct addrinfo *gai_iter, char *host, size_t host_len, char *port, size_t port_len) { int fd = socket (gai_iter->ai_family, gai_iter->ai_socktype, gai_iter->ai_protocol); if (fd == -1) return -1; set_cloexec (fd); int yes = 1; soft_assert (setsockopt (fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof yes) != -1); soft_assert (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes) != -1); *host = *port = 0; int err = getnameinfo (gai_iter->ai_addr, gai_iter->ai_addrlen, host, host_len, port, port_len, NI_NUMERICHOST | NI_NUMERICSERV); if (err) print_debug ("%s: %s", "getnameinfo", gai_strerror (err)); // We're binding to a port but our caller prefers a hostname char *address = format_host_port_pair (host, port); if (gai_iter->ai_canonname) snprintf (host, host_len, "%s", gai_iter->ai_canonname); if (bind (fd, gai_iter->ai_addr, gai_iter->ai_addrlen)) print_error ("bind to %s failed: %s", address, strerror (errno)); else if (listen (fd, 16 /* arbitrary number */)) print_error ("listen on %s failed: %s", address, strerror (errno)); else { print_status ("listening on %s", address); free (address); return fd; } free (address); xclose (fd); return -1; } static void listener_add (struct server_context *ctx, const char *host, const char *port, const struct addrinfo *gai_hints, client_create_fn create) { struct addrinfo *gai_result, *gai_iter; int err = getaddrinfo (host, port, gai_hints, &gai_result); if (err) { char *address = format_host_port_pair (host, port); print_error ("bind to %s failed: %s: %s", address, "getaddrinfo", gai_strerror (err)); free (address); return; } int fd; char host_buf[NI_MAXHOST] = "", port_buf[NI_MAXSERV] = ""; for (gai_iter = gai_result; gai_iter; gai_iter = gai_iter->ai_next) { if ((fd = listener_bind (gai_iter, host_buf, sizeof host_buf, port_buf, sizeof port_buf)) == -1) continue; set_blocking (fd, false); struct listener *listener = &ctx->listeners[ctx->n_listeners++]; ev_io_init (&listener->watcher, on_client_available, fd, EV_READ); ev_io_start (EV_DEFAULT_ &listener->watcher); listener->watcher.data = listener; listener->create = create; listener->fd = fd; listener->host = xstrdup (host); // The client will query the obtained socket for the port anyway break; } freeaddrinfo (gai_result); } static void get_ports_from_config (struct server_context *ctx, const char *key, struct strv *out) { const char *ports; if ((ports = str_map_find (&ctx->config, key))) cstr_split (ports, ",", true, out); } static bool setup_listen_fds (struct server_context *ctx, struct error **e) { static struct addrinfo gai_hints = { .ai_socktype = SOCK_STREAM, .ai_flags = AI_PASSIVE, }; struct strv ports_fcgi = strv_make (); struct strv ports_scgi = strv_make (); struct strv ports_ws = strv_make (); struct strv ports_http = strv_make (); get_ports_from_config (ctx, "port_fastcgi", &ports_fcgi); get_ports_from_config (ctx, "port_scgi", &ports_scgi); get_ports_from_config (ctx, "port_ws", &ports_ws); get_ports_from_config (ctx, "port_http", &ports_http); const char *bind_host = str_map_find (&ctx->config, "bind_host"); size_t n_ports = ports_fcgi.len + ports_scgi.len + ports_ws.len; ctx->listeners = xcalloc (n_ports, sizeof *ctx->listeners); if (bind_host) gai_hints.ai_flags |= AI_CANONNAME; for (size_t i = 0; i < ports_fcgi.len; i++) listener_add (ctx, bind_host, ports_fcgi.vector[i], &gai_hints, client_fcgi_create); for (size_t i = 0; i < ports_scgi.len; i++) listener_add (ctx, bind_host, ports_scgi.vector[i], &gai_hints, client_scgi_create); for (size_t i = 0; i < ports_ws.len; i++) listener_add (ctx, bind_host, ports_ws.vector[i], &gai_hints, client_ws_create); for (size_t i = 0; i < ports_http.len; i++) listener_add (ctx, bind_host, ports_http.vector[i], &gai_hints, client_http_create); strv_free (&ports_fcgi); strv_free (&ports_scgi); strv_free (&ports_ws); strv_free (&ports_http); if (!ctx->n_listeners) { error_set (e, "%s: %s", "network setup failed", "no ports to listen on"); return false; } return true; } static bool app_lock_pid_file (struct server_context *ctx, struct error **e) { const char *path = str_map_find (&ctx->config, "pid_file"); if (!path) return true; char *resolved = resolve_filename (path, resolve_relative_runtime_filename); bool result = lock_pid_file (resolved, e) != -1; free (resolved); return result; } // --- Tests ------------------------------------------------------------------- static void test_misc (void) { soft_assert ( validate_json_rpc_content_type ("application/JSON; charset=\"utf-8\"")); soft_assert (!validate_json_rpc_content_type ("text/html; charset=\"utf-8\"")); char *canon = canonicalize_url_path ("///../../../etc/./passwd"); soft_assert (!strcmp (canon, "/etc/passwd")); free (canon); } int test_main (int argc, char *argv[]) { struct test test; test_init (&test, argc, argv); test_add_simple (&test, "/misc", NULL, test_misc); // TODO: write more tests // TODO: test the server handler (happy path) return test_run (&test); } // --- Main program ------------------------------------------------------------ static void on_termination_signal (EV_P_ ev_signal *handle, int revents) { struct server_context *ctx = ev_userdata (loop); (void) handle; (void) revents; if (ctx->quitting) { // Double C-c from the terminal accelerates the process LIST_FOR_EACH (struct client, iter, ctx->clients) client_destroy (iter); } else initiate_quit (ctx); } static void setup_signal_handlers (struct server_context *ctx) { ev_signal_init (&ctx->sigterm_watcher, on_termination_signal, SIGTERM); ev_signal_start (EV_DEFAULT_ &ctx->sigterm_watcher); ev_signal_init (&ctx->sigint_watcher, on_termination_signal, SIGINT); ev_signal_start (EV_DEFAULT_ &ctx->sigint_watcher); (void) signal (SIGPIPE, SIG_IGN); } static void daemonize (struct server_context *ctx) { print_status ("daemonizing..."); if (chdir ("/")) exit_fatal ("%s: %s", "chdir", strerror (errno)); // Because of systemd, we need to exit the parent process _after_ writing // a PID file, otherwise our grandchild would receive a SIGTERM int sync_pipe[2]; if (pipe (sync_pipe)) exit_fatal ("%s: %s", "pipe", strerror (errno)); pid_t pid; if ((pid = fork ()) < 0) exit_fatal ("%s: %s", "fork", strerror (errno)); else if (pid) { // Wait until all write ends of the pipe are closed, which can mean // either success or failure, we don't need to care xclose (sync_pipe[PIPE_WRITE]); char dummy; if (read (sync_pipe[PIPE_READ], &dummy, 1) < 0) exit_fatal ("%s: %s", "read", strerror (errno)); exit (EXIT_SUCCESS); } setsid (); signal (SIGHUP, SIG_IGN); if ((pid = fork ()) < 0) exit_fatal ("%s: %s", "fork", strerror (errno)); else if (pid) exit (EXIT_SUCCESS); openlog (PROGRAM_NAME, LOG_NDELAY | LOG_NOWAIT | LOG_PID, 0); g_log_message_real = log_message_syslog; // Write the PID file (if so configured) and get rid of the pipe, so that // the read() in our grandparent finally returns zero (no write ends) struct error *e = NULL; if (!app_lock_pid_file (ctx, &e)) exit_fatal ("%s", e->message); xclose (sync_pipe[PIPE_READ]); xclose (sync_pipe[PIPE_WRITE]); // XXX: we may close our own descriptors this way, crippling ourselves; // there is no real guarantee that we will start with all three // descriptors open. In theory we could try to enumerate the descriptors // at the start of main(). for (int i = 0; i < 3; i++) xclose (i); int tty = open ("/dev/null", O_RDWR); if (tty != 0 || dup (0) != 1 || dup (0) != 2) exit_fatal ("failed to reopen FD's: %s", strerror (errno)); } static void parse_program_arguments (int argc, char **argv, bool *running_as_slave) { static const struct opt opts[] = { { 't', "test", NULL, 0, "self-test" }, { 's', "slave", NULL, 0, "co-process mode" }, { 'd', "debug", NULL, 0, "run in debug mode" }, { 'h', "help", NULL, 0, "display this help and exit" }, { 'V', "version", NULL, 0, "output version information and exit" }, { 'w', "write-default-cfg", "FILENAME", OPT_OPTIONAL_ARG | OPT_LONG_ONLY, "write a default configuration file and exit" }, { 0, NULL, NULL, 0, NULL } }; struct opt_handler oh = opt_handler_make (argc, argv, opts, NULL, "JSON-RPC 2.0 demo server."); int c; while ((c = opt_handler_get (&oh)) != -1) switch (c) { case 't': test_main (argc, argv); exit (EXIT_SUCCESS); case 's': *running_as_slave = true; break; case 'd': g_debug_mode = true; break; case 'h': opt_handler_usage (&oh, stdout); exit (EXIT_SUCCESS); case 'V': printf (PROGRAM_NAME " " PROGRAM_VERSION "\n"); exit (EXIT_SUCCESS); case 'w': call_simple_config_write_default (optarg, g_config_table); exit (EXIT_SUCCESS); default: print_error ("wrong options"); opt_handler_usage (&oh, stderr); exit (EXIT_FAILURE); } argc -= optind; argv += optind; if (argc) { opt_handler_usage (&oh, stderr); exit (EXIT_FAILURE); } opt_handler_free (&oh); } int main (int argc, char *argv[]) { bool running_as_a_slave = false; parse_program_arguments (argc, argv, &running_as_a_slave); print_status (PROGRAM_NAME " " PROGRAM_VERSION " starting"); struct server_context ctx; server_context_init (&ctx); struct error *e = NULL; if (!simple_config_update_from_file (&ctx.config, &e)) { print_error ("error loading configuration: %s", e->message); error_free (e); exit (EXIT_FAILURE); } // There's a lot of unnecessary left-over scaffolding in this program, // for testing purposes assume that everything is synchronous if (running_as_a_slave) { client_co_run (&ctx); server_context_free (&ctx); return EXIT_SUCCESS; } struct ev_loop *loop; if (!(loop = EV_DEFAULT)) exit_fatal ("libev initialization failed"); ev_set_userdata (loop, &ctx); setup_signal_handlers (&ctx); LIST_PREPEND (ctx.handlers, &g_request_handler_static); LIST_PREPEND (ctx.handlers, &g_request_handler_json_rpc); if (!parse_config (&ctx, &e) || !setup_listen_fds (&ctx, &e)) { print_error ("%s", e->message); error_free (e); exit (EXIT_FAILURE); } if (!g_debug_mode) daemonize (&ctx); else if (!app_lock_pid_file (&ctx, &e)) exit_fatal ("%s", e->message); ev_run (loop, 0); ev_loop_destroy (loop); server_context_free (&ctx); return EXIT_SUCCESS; }