/* * liberty-proto.c: the ultimate C unlibrary: protocols * * Copyright (c) 2014 - 2016, 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. * */ // Mostly parsers and various utilities relating to various protocols #ifdef LIBERTY_WANT_PROTO_IRC // --- IRC utilities ----------------------------------------------------------- struct irc_message { struct str_map tags; ///< IRC 3.2 message tags char *prefix; ///< Message prefix char *command; ///< IRC command struct strv params; ///< Command parameters }; static char * irc_unescape_message_tag (const char *value) { struct str s = str_make (); bool escape = false; for (const char *p = value; *p; p++) { if (escape) { switch (*p) { case ':': str_append_c (&s, ';'); break; case 's': str_append_c (&s, ' '); break; case 'r': str_append_c (&s, '\r'); break; case 'n': str_append_c (&s, '\n'); break; default: str_append_c (&s, *p); } escape = false; } else if (*p == '\\') escape = true; else str_append_c (&s, *p); } return str_steal (&s); } static void irc_parse_message_tags (const char *tags, struct str_map *out) { struct strv v = strv_make (); cstr_split (tags, ";", true, &v); for (size_t i = 0; i < v.len; i++) { char *key = v.vector[i], *equal_sign = strchr (key, '='); if (equal_sign) { *equal_sign = '\0'; str_map_set (out, key, irc_unescape_message_tag (equal_sign + 1)); } else str_map_set (out, key, xstrdup ("")); } strv_free (&v); } static void irc_parse_message (struct irc_message *msg, const char *line) { msg->tags = str_map_make (free); msg->prefix = NULL; msg->command = NULL; msg->params = strv_make (); // IRC 3.2 message tags if (*line == '@') { size_t tags_len = strcspn (++line, " "); char *tags = xstrndup (line, tags_len); irc_parse_message_tags (tags, &msg->tags); free (tags); line += tags_len; while (*line == ' ') line++; } // Prefix if (*line == ':') { size_t prefix_len = strcspn (++line, " "); msg->prefix = xstrndup (line, prefix_len); line += prefix_len; } // Command name { while (*line == ' ') line++; size_t cmd_len = strcspn (line, " "); msg->command = xstrndup (line, cmd_len); line += cmd_len; } // Arguments while (true) { while (*line == ' ') line++; if (*line == ':') { strv_append (&msg->params, ++line); break; } size_t param_len = strcspn (line, " "); if (!param_len) break; strv_append_owned (&msg->params, xstrndup (line, param_len)); line += param_len; } } static void irc_free_message (struct irc_message *msg) { str_map_free (&msg->tags); free (msg->prefix); free (msg->command); strv_free (&msg->params); } static void irc_process_buffer (struct str *buf, void (*callback) (const struct irc_message *, const char *, void *), void *user_data) { char *start = buf->str, *end = start + buf->len; for (char *p = start; p + 1 < end; p++) { // Split the input on newlines if (p[0] != '\r' || p[1] != '\n') continue; *p = 0; struct irc_message msg; irc_parse_message (&msg, start); callback (&msg, start, user_data); irc_free_message (&msg); start = p + 2; } // XXX: we might want to just advance some kind of an offset to avoid // moving memory around unnecessarily. str_remove_slice (buf, 0, start - buf->str); } static int irc_tolower (int c) { if (c == '[') return '{'; if (c == ']') return '}'; if (c == '\\') return '|'; if (c == '~') return '^'; return c >= 'A' && c <= 'Z' ? c + ('a' - 'A') : c; } static int irc_tolower_strict (int c) { if (c == '[') return '{'; if (c == ']') return '}'; if (c == '\\') return '|'; return c >= 'A' && c <= 'Z' ? c + ('a' - 'A') : c; } TRIVIAL_STRXFRM (irc_strxfrm, irc_tolower) TRIVIAL_STRXFRM (irc_strxfrm_strict, irc_tolower_strict) static int irc_strcmp (const char *a, const char *b) { int x; while (*a || *b) if ((x = irc_tolower (*a++) - irc_tolower (*b++))) return x; return 0; } static int irc_fnmatch (const char *pattern, const char *string) { size_t pattern_size = strlen (pattern) + 1; size_t string_size = strlen (string) + 1; char x_pattern[pattern_size], x_string[string_size]; irc_strxfrm (x_pattern, pattern, pattern_size); irc_strxfrm (x_string, string, string_size); // FIXME: this supports [], which is not mentioned in RFC 2812 return fnmatch (x_pattern, x_string, 0); } #endif #ifdef LIBERTY_WANT_PROTO_HTTP // --- HTTP parsing ------------------------------------------------------------ // Basic tokenizer for HTTP header field values, to be used in various parsers. // The input should already be unwrapped. // Recommended literature: // http://tools.ietf.org/html/rfc7230#section-3.2.6 // http://tools.ietf.org/html/rfc7230#appendix-B // http://tools.ietf.org/html/rfc5234#appendix-B.1 #define HTTP_TOKENIZER_CLASS(name, definition) \ static inline bool \ http_tokenizer_is_ ## name (int c) \ { \ return (definition); \ } HTTP_TOKENIZER_CLASS (vchar, c >= 0x21 && c <= 0x7E) HTTP_TOKENIZER_CLASS (delimiter, !!strchr ("\"(),/:;<=>?@[\\]{}", c)) HTTP_TOKENIZER_CLASS (whitespace, c == '\t' || c == ' ') HTTP_TOKENIZER_CLASS (obs_text, c >= 0x80 && c <= 0xFF) HTTP_TOKENIZER_CLASS (tchar, http_tokenizer_is_vchar (c) && !http_tokenizer_is_delimiter (c)) HTTP_TOKENIZER_CLASS (qdtext, c == '\t' || c == ' ' || c == '!' || (c >= 0x23 && c <= 0x5B) || (c >= 0x5D && c <= 0x7E) || http_tokenizer_is_obs_text (c)) HTTP_TOKENIZER_CLASS (quoted_pair, c == '\t' || c == ' ' || http_tokenizer_is_vchar (c) || http_tokenizer_is_obs_text (c)) #undef HTTP_TOKENIZER_CLASS // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - enum http_tokenizer_token { HTTP_T_EOF, ///< Input error HTTP_T_ERROR, ///< End of input HTTP_T_TOKEN, ///< "token" HTTP_T_QUOTED_STRING, ///< "quoted-string" HTTP_T_DELIMITER, ///< "delimiters" HTTP_T_WHITESPACE ///< RWS/OWS/BWS }; struct http_tokenizer { const unsigned char *input; ///< The input string size_t input_len; ///< Length of the input size_t offset; ///< Position in the input char delimiter; ///< The delimiter character struct str string; ///< "token" / "quoted-string" content }; static struct http_tokenizer http_tokenizer_make (const char *input, size_t len) { return (struct http_tokenizer) { .input = (const unsigned char *) input, .input_len = len, .string = str_make (), }; } static void http_tokenizer_free (struct http_tokenizer *self) { str_free (&self->string); } static enum http_tokenizer_token http_tokenizer_quoted_string (struct http_tokenizer *self) { bool quoted_pair = false; while (self->offset < self->input_len) { int c = self->input[self->offset++]; if (quoted_pair) { if (!http_tokenizer_is_quoted_pair (c)) return HTTP_T_ERROR; str_append_c (&self->string, c); quoted_pair = false; } else if (c == '\\') quoted_pair = true; else if (c == '"') return HTTP_T_QUOTED_STRING; else if (http_tokenizer_is_qdtext (c)) str_append_c (&self->string, c); else return HTTP_T_ERROR; } // Premature end of input return HTTP_T_ERROR; } static enum http_tokenizer_token http_tokenizer_next (struct http_tokenizer *self, bool skip_ows) { str_reset (&self->string); if (self->offset >= self->input_len) return HTTP_T_EOF; int c = self->input[self->offset++]; if (skip_ows) while (http_tokenizer_is_whitespace (c)) { if (self->offset >= self->input_len) return HTTP_T_EOF; c = self->input[self->offset++]; } if (c == '"') return http_tokenizer_quoted_string (self); if (http_tokenizer_is_delimiter (c)) { self->delimiter = c; return HTTP_T_DELIMITER; } // Simple variable-length tokens enum http_tokenizer_token result; bool (*eater) (int c) = NULL; if (http_tokenizer_is_whitespace (c)) { eater = http_tokenizer_is_whitespace; result = HTTP_T_WHITESPACE; } else if (http_tokenizer_is_tchar (c)) { eater = http_tokenizer_is_tchar; result = HTTP_T_TOKEN; } else return HTTP_T_ERROR; str_append_c (&self->string, c); while (self->offset < self->input_len) { if (!eater (c = self->input[self->offset])) break; str_append_c (&self->string, c); self->offset++; } return result; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static bool http_parse_media_type_parameter (struct http_tokenizer *t, struct str_map *parameters) { bool result = false; char *attribute = NULL; if (http_tokenizer_next (t, true) != HTTP_T_TOKEN) goto end; attribute = xstrdup (t->string.str); if (http_tokenizer_next (t, false) != HTTP_T_DELIMITER || t->delimiter != '=') goto end; switch (http_tokenizer_next (t, false)) { case HTTP_T_TOKEN: case HTTP_T_QUOTED_STRING: if (parameters) str_map_set (parameters, attribute, xstrdup (t->string.str)); result = true; default: break; } end: free (attribute); return result; } /// Parser for "Content-Type". @a type and @a subtype may end up non-NULL /// even if the function fails. @a parameters should be case-insensitive, /// and may be NULL for validation only. static bool http_parse_media_type (const char *media_type, char **type, char **subtype, struct str_map *parameters) { bool result = false; struct http_tokenizer t = http_tokenizer_make (media_type, strlen (media_type)); if (http_tokenizer_next (&t, true) != HTTP_T_TOKEN) goto end; *type = xstrdup (t.string.str); if (http_tokenizer_next (&t, false) != HTTP_T_DELIMITER || t.delimiter != '/') goto end; if (http_tokenizer_next (&t, false) != HTTP_T_TOKEN) goto end; *subtype = xstrdup (t.string.str); while (true) switch (http_tokenizer_next (&t, true)) { case HTTP_T_DELIMITER: if (t.delimiter != ';') goto end; if (!http_parse_media_type_parameter (&t, parameters)) goto end; break; case HTTP_T_EOF: result = true; default: goto end; } end: http_tokenizer_free (&t); return result; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - struct http_protocol { LIST_HEADER (struct http_protocol) char *name; ///< The protocol to upgrade to char *version; ///< Version of the protocol, if any }; static void http_protocol_destroy (struct http_protocol *self) { free (self->name); free (self->version); free (self); } static bool http_parse_upgrade (const char *upgrade, struct http_protocol **out) { // HTTP grammar makes this more complicated than it should be bool result = false; struct http_protocol *list = NULL; struct http_protocol *tail = NULL; struct http_tokenizer t = http_tokenizer_make (upgrade, strlen (upgrade)); enum { STATE_PROTOCOL_NAME, STATE_SLASH, STATE_PROTOCOL_VERSION, STATE_EXPECT_COMMA } state = STATE_PROTOCOL_NAME; struct http_protocol *proto = NULL; while (true) switch (state) { case STATE_PROTOCOL_NAME: switch (http_tokenizer_next (&t, false)) { case HTTP_T_DELIMITER: if (t.delimiter != ',') goto end; case HTTP_T_WHITESPACE: break; case HTTP_T_TOKEN: proto = xcalloc (1, sizeof *proto); proto->name = xstrdup (t.string.str); LIST_APPEND_WITH_TAIL (list, tail, proto); state = STATE_SLASH; break; case HTTP_T_EOF: result = true; default: goto end; } break; case STATE_SLASH: switch (http_tokenizer_next (&t, false)) { case HTTP_T_DELIMITER: if (t.delimiter == '/') state = STATE_PROTOCOL_VERSION; else if (t.delimiter == ',') state = STATE_PROTOCOL_NAME; else goto end; break; case HTTP_T_WHITESPACE: state = STATE_EXPECT_COMMA; break; case HTTP_T_EOF: result = true; default: goto end; } break; case STATE_PROTOCOL_VERSION: switch (http_tokenizer_next (&t, false)) { case HTTP_T_TOKEN: proto->version = xstrdup (t.string.str); state = STATE_EXPECT_COMMA; break; default: goto end; } break; case STATE_EXPECT_COMMA: switch (http_tokenizer_next (&t, false)) { case HTTP_T_DELIMITER: if (t.delimiter != ',') goto end; state = STATE_PROTOCOL_NAME; case HTTP_T_WHITESPACE: break; case HTTP_T_EOF: result = true; default: goto end; } } end: if (result) *out = list; else LIST_FOR_EACH (struct http_protocol, iter, list) http_protocol_destroy (iter); http_tokenizer_free (&t); return result; } #endif #ifdef LIBERTY_WANT_PROTO_SCGI // --- SCGI -------------------------------------------------------------------- enum scgi_parser_state { SCGI_READING_NETSTRING_LENGTH, ///< The length of the header netstring SCGI_READING_NAME, ///< Header name SCGI_READING_VALUE, ///< Header value SCGI_READING_CONTENT ///< Incoming data }; struct scgi_parser { enum scgi_parser_state state; ///< Parsing state struct str input; ///< Input buffer struct str_map headers; ///< Headers parsed size_t headers_len; ///< Length of the netstring contents struct str name; ///< Header name so far struct str value; ///< Header value so far /// Finished parsing request headers. /// Return false to abort further processing of input. bool (*on_headers_read) (void *user_data); /// Content available; len == 0 means end of file. /// Return false to abort further processing of input. bool (*on_content) (void *user_data, const void *data, size_t len); void *user_data; ///< User data passed to callbacks }; static struct scgi_parser scgi_parser_make (void) { return (struct scgi_parser) { .input = str_make (), .headers = str_map_make (free), .name = str_make (), .value = str_make (), }; } static void scgi_parser_free (struct scgi_parser *self) { str_free (&self->input); str_map_free (&self->headers); str_free (&self->name); str_free (&self->value); } static bool scgi_parser_push (struct scgi_parser *self, const void *data, size_t len, struct error **e) { if (!len) { if (self->state != SCGI_READING_CONTENT) return error_set (e, "premature EOF"); // Indicate end of file return self->on_content (self->user_data, NULL, 0); } // Notice that this madness is significantly harder to parse than FastCGI; // this procedure could also be optimized significantly str_append_data (&self->input, data, len); bool keep_running = true; while (keep_running) switch (self->state) { case SCGI_READING_NETSTRING_LENGTH: { if (self->input.len < 1) return true; char digit = *self->input.str; // XXX: this allows for omitting the netstring length altogether if (digit == ':') { self->state = SCGI_READING_NAME; str_remove_slice (&self->input, 0, 1); break; } if (digit < '0' || digit > '9') return error_set (e, "invalid header netstring"); size_t new_len = self->headers_len * 10 + (digit - '0'); if (new_len < self->headers_len) return error_set (e, "header netstring is too long"); self->headers_len = new_len; str_remove_slice (&self->input, 0, 1); break; } case SCGI_READING_NAME: { if (self->input.len < 1) return true; char c = *self->input.str; if (!self->headers_len) { // The netstring is ending but we haven't finished parsing it, // or the netstring doesn't end with a comma if (self->name.len || c != ',') return error_set (e, "invalid header netstring"); self->state = SCGI_READING_CONTENT; keep_running = self->on_headers_read (self->user_data); } else if (c != '\0') str_append_c (&self->name, c); else self->state = SCGI_READING_VALUE; str_remove_slice (&self->input, 0, 1); self->headers_len--; break; } case SCGI_READING_VALUE: { if (self->input.len < 1) return true; char c = *self->input.str; if (!self->headers_len) { // The netstring is ending but we haven't finished parsing it return error_set (e, "invalid header netstring"); } else if (c != '\0') str_append_c (&self->value, c); else { // We've got a name-value pair, let's put it in the map str_map_set (&self->headers, self->name.str, str_steal (&self->value)); str_reset (&self->name); self->value = str_make (); self->state = SCGI_READING_NAME; } str_remove_slice (&self->input, 0, 1); self->headers_len--; break; } case SCGI_READING_CONTENT: keep_running = self->on_content (self->user_data, self->input.str, self->input.len); str_remove_slice (&self->input, 0, self->input.len); return keep_running; } return false; } #endif #ifdef LIBERTY_WANT_PROTO_FASTCGI // --- FastCGI ----------------------------------------------------------------- // Constants from the FastCGI specification document #define FCGI_HEADER_LEN 8 #define FCGI_VERSION_1 1 #define FCGI_NULL_REQUEST_ID 0 #define FCGI_KEEP_CONN 1 enum fcgi_type { FCGI_BEGIN_REQUEST = 1, FCGI_ABORT_REQUEST = 2, FCGI_END_REQUEST = 3, FCGI_PARAMS = 4, FCGI_STDIN = 5, FCGI_STDOUT = 6, FCGI_STDERR = 7, FCGI_DATA = 8, FCGI_GET_VALUES = 9, FCGI_GET_VALUES_RESULT = 10, FCGI_UNKNOWN_TYPE = 11, FCGI_MAXTYPE = FCGI_UNKNOWN_TYPE }; enum fcgi_role { FCGI_RESPONDER = 1, FCGI_AUTHORIZER = 2, FCGI_FILTER = 3 }; enum fcgi_protocol_status { FCGI_REQUEST_COMPLETE = 0, FCGI_CANT_MPX_CONN = 1, FCGI_OVERLOADED = 2, FCGI_UNKNOWN_ROLE = 3 }; #define FCGI_MAX_CONNS "FCGI_MAX_CONNS" #define FCGI_MAX_REQS "FCGI_MAX_REQS" #define FCGI_MPXS_CONNS "FCGI_MPXS_CONNS" // - - Message stream parser - - - - - - - - - - - - - - - - - - - - - - - - - - struct fcgi_parser; /// Message handler, returns false if further processing should be stopped typedef bool (*fcgi_message_fn) (const struct fcgi_parser *parser, void *user_data); enum fcgi_parser_state { FCGI_READING_HEADER, ///< Reading the fixed header portion FCGI_READING_CONTENT, ///< Reading the message content FCGI_READING_PADDING ///< Reading the padding }; struct fcgi_parser { enum fcgi_parser_state state; ///< Parsing state struct str input; ///< Input buffer // The next block of fields is considered public: uint8_t version; ///< FastCGI protocol version uint8_t type; ///< FastCGI record type uint16_t request_id; ///< FastCGI request ID struct str content; ///< Message data uint16_t content_length; ///< Message content length uint8_t padding_length; ///< Message padding length fcgi_message_fn on_message; ///< Callback on message void *user_data; ///< User data }; static struct fcgi_parser fcgi_parser_make (void) { return (struct fcgi_parser) { .input = str_make (), .content = str_make () }; } static void fcgi_parser_free (struct fcgi_parser *self) { str_free (&self->input); str_free (&self->content); } static void fcgi_parser_unpack_header (struct fcgi_parser *self) { struct msg_unpacker unpacker = msg_unpacker_make (self->input.str, self->input.len); bool success = true; uint8_t reserved; success &= msg_unpacker_u8 (&unpacker, &self->version); success &= msg_unpacker_u8 (&unpacker, &self->type); success &= msg_unpacker_u16 (&unpacker, &self->request_id); success &= msg_unpacker_u16 (&unpacker, &self->content_length); success &= msg_unpacker_u8 (&unpacker, &self->padding_length); success &= msg_unpacker_u8 (&unpacker, &reserved); hard_assert (success); str_remove_slice (&self->input, 0, unpacker.offset); } static bool fcgi_parser_push (struct fcgi_parser *self, const void *data, size_t len) { // This could be made considerably faster for high-throughput applications // if we use a circular buffer instead of constantly calling memmove() str_append_data (&self->input, data, len); while (true) switch (self->state) { case FCGI_READING_HEADER: if (self->input.len < FCGI_HEADER_LEN) return true; fcgi_parser_unpack_header (self); self->state = FCGI_READING_CONTENT; break; case FCGI_READING_CONTENT: if (self->input.len < self->content_length) return true; // Move an appropriate part of the input buffer to the content buffer str_reset (&self->content); str_append_data (&self->content, self->input.str, self->content_length); str_remove_slice (&self->input, 0, self->content_length); self->state = FCGI_READING_PADDING; break; case FCGI_READING_PADDING: if (self->input.len < self->padding_length) return true; // Call the callback to further process the message if (!self->on_message (self, self->user_data)) return false; // Remove the padding from the input buffer str_remove_slice (&self->input, 0, self->padding_length); self->state = FCGI_READING_HEADER; break; } } // - - Name-value pair parser - - - - - - - - - - - - - - - - - - - - - - - - - enum fcgi_nv_parser_state { FCGI_NV_PARSER_NAME_LEN, ///< The first name length octet FCGI_NV_PARSER_NAME_LEN_FULL, ///< Remaining name length octets FCGI_NV_PARSER_VALUE_LEN, ///< The first value length octet FCGI_NV_PARSER_VALUE_LEN_FULL, ///< Remaining value length octets FCGI_NV_PARSER_NAME, ///< Reading the name FCGI_NV_PARSER_VALUE ///< Reading the value }; struct fcgi_nv_parser { struct str_map *output; ///< Where the pairs will be stored enum fcgi_nv_parser_state state; ///< Parsing state struct str input; ///< Input buffer uint32_t name_len; ///< Length of the name uint32_t value_len; ///< Length of the value char *name; ///< The current name, 0-terminated char *value; ///< The current value, 0-terminated }; static struct fcgi_nv_parser fcgi_nv_parser_make (void) { return (struct fcgi_nv_parser) { .input = str_make () }; } static void fcgi_nv_parser_free (struct fcgi_nv_parser *self) { str_free (&self->input); free (self->name); free (self->value); } static void fcgi_nv_parser_push (struct fcgi_nv_parser *self, const void *data, size_t len) { // This could be optimized significantly; I'm not even trying str_append_data (&self->input, data, len); while (true) { struct msg_unpacker unpacker = msg_unpacker_make (self->input.str, self->input.len); switch (self->state) { uint8_t len; uint32_t len_full; case FCGI_NV_PARSER_NAME_LEN: if (!msg_unpacker_u8 (&unpacker, &len)) return; if (len >> 7) self->state = FCGI_NV_PARSER_NAME_LEN_FULL; else { self->name_len = len; str_remove_slice (&self->input, 0, unpacker.offset); self->state = FCGI_NV_PARSER_VALUE_LEN; } break; case FCGI_NV_PARSER_NAME_LEN_FULL: if (!msg_unpacker_u32 (&unpacker, &len_full)) return; self->name_len = len_full & ~(1U << 31); str_remove_slice (&self->input, 0, unpacker.offset); self->state = FCGI_NV_PARSER_VALUE_LEN; break; case FCGI_NV_PARSER_VALUE_LEN: if (!msg_unpacker_u8 (&unpacker, &len)) return; if (len >> 7) self->state = FCGI_NV_PARSER_VALUE_LEN_FULL; else { self->value_len = len; str_remove_slice (&self->input, 0, unpacker.offset); self->state = FCGI_NV_PARSER_NAME; } break; case FCGI_NV_PARSER_VALUE_LEN_FULL: if (!msg_unpacker_u32 (&unpacker, &len_full)) return; self->value_len = len_full & ~(1U << 31); str_remove_slice (&self->input, 0, unpacker.offset); self->state = FCGI_NV_PARSER_NAME; break; case FCGI_NV_PARSER_NAME: if (self->input.len < self->name_len) return; self->name = xmalloc (self->name_len + 1); self->name[self->name_len] = '\0'; memcpy (self->name, self->input.str, self->name_len); str_remove_slice (&self->input, 0, self->name_len); self->state = FCGI_NV_PARSER_VALUE; break; case FCGI_NV_PARSER_VALUE: if (self->input.len < self->value_len) return; self->value = xmalloc (self->value_len + 1); self->value[self->value_len] = '\0'; memcpy (self->value, self->input.str, self->value_len); str_remove_slice (&self->input, 0, self->value_len); self->state = FCGI_NV_PARSER_NAME_LEN; // The map takes ownership of the value str_map_set (self->output, self->name, self->value); free (self->name); self->name = NULL; self->value = NULL; break; } } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static void fcgi_nv_convert_len (size_t len, struct str *output) { if (len < 0x80) str_pack_u8 (output, len); else { len |= (uint32_t) 1 << 31; str_pack_u32 (output, len); } } static void fcgi_nv_convert (struct str_map *map, struct str *output) { struct str_map_iter iter = str_map_iter_make (map); while (str_map_iter_next (&iter)) { const char *name = iter.link->key; const char *value = iter.link->data; size_t name_len = iter.link->key_length; size_t value_len = strlen (value); fcgi_nv_convert_len (name_len, output); fcgi_nv_convert_len (value_len, output); str_append_data (output, name, name_len); str_append_data (output, value, value_len); } } #endif #ifdef LIBERTY_WANT_PROTO_WS // --- WebSockets -------------------------------------------------------------- #define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" #define SEC_WS_KEY "Sec-WebSocket-Key" #define SEC_WS_ACCEPT "Sec-WebSocket-Accept" #define SEC_WS_PROTOCOL "Sec-WebSocket-Protocol" #define SEC_WS_EXTENSIONS "Sec-WebSocket-Extensions" #define SEC_WS_VERSION "Sec-WebSocket-Version" #define WS_MAX_CONTROL_PAYLOAD_LEN 125 static char * ws_encode_response_key (const char *key) { char *response_key = xstrdup_printf ("%s" WS_GUID, key); unsigned char hash[SHA_DIGEST_LENGTH]; SHA1 ((unsigned char *) response_key, strlen (response_key), hash); free (response_key); struct str base64 = str_make (); base64_encode (hash, sizeof hash, &base64); return str_steal (&base64); } enum ws_status { // Named according to the meaning specified in RFC 6455, section 11.7 WS_STATUS_NORMAL_CLOSURE = 1000, WS_STATUS_GOING_AWAY = 1001, WS_STATUS_PROTOCOL_ERROR = 1002, WS_STATUS_UNSUPPORTED_DATA = 1003, WS_STATUS_INVALID_PAYLOAD_DATA = 1007, WS_STATUS_POLICY_VIOLATION = 1008, WS_STATUS_MESSAGE_TOO_BIG = 1009, WS_STATUS_MANDATORY_EXTENSION = 1010, WS_STATUS_INTERNAL_SERVER_ERROR = 1011, // Reserved for internal usage WS_STATUS_NO_STATUS_RECEIVED = 1005, WS_STATUS_ABNORMAL_CLOSURE = 1006, WS_STATUS_TLS_HANDSHAKE = 1015 }; // - - Frame parser - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - enum ws_parser_state { WS_PARSER_FIXED, ///< Parsing fixed length part WS_PARSER_PAYLOAD_LEN_16, ///< Parsing extended payload length WS_PARSER_PAYLOAD_LEN_64, ///< Parsing extended payload length WS_PARSER_MASK, ///< Parsing masking-key WS_PARSER_PAYLOAD ///< Parsing payload }; enum ws_opcode { // Non-control WS_OPCODE_CONT = 0, WS_OPCODE_TEXT = 1, WS_OPCODE_BINARY = 2, // Control WS_OPCODE_CLOSE = 8, WS_OPCODE_PING = 9, WS_OPCODE_PONG = 10 }; static bool ws_is_control_frame (int opcode) { return opcode >= WS_OPCODE_CLOSE; } struct ws_parser { struct str input; ///< External input buffer enum ws_parser_state state; ///< Parsing state unsigned is_fin : 1; ///< Final frame of a message? unsigned is_masked : 1; ///< Is the frame masked? unsigned reserved_1 : 1; ///< Reserved unsigned reserved_2 : 1; ///< Reserved unsigned reserved_3 : 1; ///< Reserved enum ws_opcode opcode; ///< Opcode uint32_t mask; ///< Frame mask uint64_t payload_len; ///< Payload length bool (*on_frame_header) (void *user_data, const struct ws_parser *self); /// Callback for when a message is successfully parsed. /// The actual payload is stored in "input", of length "payload_len". bool (*on_frame) (void *user_data, const struct ws_parser *self); void *user_data; ///< User data for callbacks }; static struct ws_parser ws_parser_make (void) { return (struct ws_parser) { .input = str_make () }; } static void ws_parser_free (struct ws_parser *self) { str_free (&self->input); } static void ws_parser_unmask (char *payload, uint64_t len, uint32_t mask) { // This could be made faster. For example by reading the mask in // native byte ordering and applying it directly here. uint64_t end = len & ~(uint64_t) 3; for (uint64_t i = 0; i < end; i += 4) { payload[i + 3] ^= mask & 0xFF; payload[i + 2] ^= (mask >> 8) & 0xFF; payload[i + 1] ^= (mask >> 16) & 0xFF; payload[i ] ^= (mask >> 24) & 0xFF; } switch (len - end) { case 3: payload[end + 2] ^= (mask >> 8) & 0xFF; // Fall-through case 2: payload[end + 1] ^= (mask >> 16) & 0xFF; // Fall-through case 1: payload[end ] ^= (mask >> 24) & 0xFF; } } static bool ws_parser_push (struct ws_parser *self, const void *data, size_t len) { bool success = false; str_append_data (&self->input, data, len); struct msg_unpacker unpacker = msg_unpacker_make (self->input.str, self->input.len); while (true) switch (self->state) { uint8_t u8; uint16_t u16; case WS_PARSER_FIXED: if (unpacker.len - unpacker.offset < 2) goto need_data; (void) msg_unpacker_u8 (&unpacker, &u8); self->is_fin = (u8 >> 7) & 1; self->reserved_1 = (u8 >> 6) & 1; self->reserved_2 = (u8 >> 5) & 1; self->reserved_3 = (u8 >> 4) & 1; self->opcode = u8 & 15; (void) msg_unpacker_u8 (&unpacker, &u8); self->is_masked = (u8 >> 7) & 1; self->payload_len = u8 & 127; if (self->payload_len == 127) self->state = WS_PARSER_PAYLOAD_LEN_64; else if (self->payload_len == 126) self->state = WS_PARSER_PAYLOAD_LEN_16; else self->state = WS_PARSER_MASK; break; case WS_PARSER_PAYLOAD_LEN_16: if (!msg_unpacker_u16 (&unpacker, &u16)) goto need_data; self->payload_len = u16; self->state = WS_PARSER_MASK; break; case WS_PARSER_PAYLOAD_LEN_64: if (!msg_unpacker_u64 (&unpacker, &self->payload_len)) goto need_data; self->state = WS_PARSER_MASK; break; case WS_PARSER_MASK: if (!self->is_masked) goto end_of_header; if (!msg_unpacker_u32 (&unpacker, &self->mask)) goto need_data; end_of_header: self->state = WS_PARSER_PAYLOAD; if (!self->on_frame_header (self->user_data, self)) goto fail; break; case WS_PARSER_PAYLOAD: // Move the buffer so that payload data is at the front str_remove_slice (&self->input, 0, unpacker.offset); unpacker = msg_unpacker_make (self->input.str, self->input.len); if (self->input.len < self->payload_len) goto need_data; if (self->is_masked) ws_parser_unmask (self->input.str, self->payload_len, self->mask); if (!self->on_frame (self->user_data, self)) goto fail; // And continue unpacking frames past the payload unpacker.offset = self->payload_len; self->state = WS_PARSER_FIXED; break; } need_data: success = true; fail: str_remove_slice (&self->input, 0, unpacker.offset); return success; } #endif #ifdef LIBERTY_WANT_PROTO_MPD #include // --- MPD client interface ---------------------------------------------------- // This is a rather thin MPD client interface intended for basic tasks #define MPD_SUBSYSTEM_TABLE(XX) \ XX (DATABASE, 0, "database") \ XX (UPDATE, 1, "update") \ XX (STORED_PLAYLIST, 2, "stored_playlist") \ XX (PLAYLIST, 3, "playlist") \ XX (PLAYER, 4, "player") \ XX (MIXER, 5, "mixer") \ XX (OUTPUT, 6, "output") \ XX (OPTIONS, 7, "options") \ XX (STICKER, 8, "sticker") \ XX (SUBSCRIPTION, 9, "subscription") \ XX (MESSAGE, 10, "message") enum mpd_subsystem { #define XX(a, b, c) MPD_SUBSYSTEM_ ## a = (1 << b), MPD_SUBSYSTEM_TABLE (XX) #undef XX }; static const char *mpd_subsystem_names[] = { #define XX(a, b, c) [b] = c, MPD_SUBSYSTEM_TABLE (XX) #undef XX }; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - enum mpd_client_state { MPD_DISCONNECTED, ///< Not connected MPD_CONNECTING, ///< Currently connecting MPD_CONNECTED ///< Connected }; struct mpd_response { bool success; ///< OK or ACK // ACK-only fields: int error; ///< Numeric error value (ack.h) int list_offset; ///< Offset of command in list char *current_command; ///< Name of the erroring command char *message_text; ///< Error message }; /// Task completion callback; on connection abortion most fields are 0 typedef void (*mpd_client_task_cb) (const struct mpd_response *response, const struct strv *data, void *user_data); struct mpd_client_task { LIST_HEADER (struct mpd_client_task) mpd_client_task_cb callback; ///< Callback on completion void *user_data; ///< User data }; struct mpd_client { struct poller *poller; ///< Poller // Connection: enum mpd_client_state state; ///< Connection state struct connector *connector; ///< Connection establisher int socket; ///< MPD socket struct str read_buffer; ///< Input yet to be processed struct str write_buffer; ///< Outut yet to be be sent out struct poller_fd socket_event; ///< We can read from the socket struct poller_timer timeout_timer; ///< Connection seems to be dead // Protocol: bool got_hello; ///< Got the OK MPD hello message bool idling; ///< Sent idle as the last command unsigned idling_subsystems; ///< Subsystems we're idling for bool in_list; ///< We're inside a command list struct mpd_client_task *tasks; ///< Task queue struct mpd_client_task *tasks_tail; ///< Tail of task queue struct strv data; ///< Data from last command // User configuration: void *user_data; ///< User data for callbacks /// Callback after connection has been successfully established void (*on_connected) (void *user_data); /// Callback for general failures or even normal disconnection; /// the interface is reinitialized void (*on_failure) (void *user_data); /// Callback to receive "idle" updates. /// Remember to restart the idle if needed. void (*on_event) (unsigned subsystems, void *user_data); /// Callback to trace protocol I/O void (*on_io_hook) (void *user_data, bool outgoing, const char *line); }; static void mpd_client_reset (struct mpd_client *self); static void mpd_client_destroy_connector (struct mpd_client *self); static struct mpd_client mpd_client_make (struct poller *poller) { return (struct mpd_client) { .poller = poller, .socket = -1, .read_buffer = str_make (), .write_buffer = str_make (), .data = strv_make (), .socket_event = poller_fd_make (poller, -1), .timeout_timer = poller_timer_make (poller), }; } static void mpd_client_free (struct mpd_client *self) { // So that we don't have to repeat most of the stuff mpd_client_reset (self); str_free (&self->read_buffer); str_free (&self->write_buffer); strv_free (&self->data); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static void mpd_client_dispatch (struct mpd_client *self, struct mpd_response *response) { struct mpd_client_task *task; if (!(task = self->tasks)) return; if (task->callback) task->callback (response, &self->data, task->user_data); strv_reset (&self->data); LIST_UNLINK_WITH_TAIL (self->tasks, self->tasks_tail, task); free (task); } /// Reinitialize the interface so that you can reconnect anew static void mpd_client_reset (struct mpd_client *self) { // Get rid of all pending tasks to release resources etc. strv_reset (&self->data); struct mpd_response aborted = { .message_text = "Disconnected" }; while (self->tasks) mpd_client_dispatch (self, &aborted); if (self->state == MPD_CONNECTING) mpd_client_destroy_connector (self); if (self->socket != -1) xclose (self->socket); self->socket = -1; // FIXME: this is not robust wrt. forking self->socket_event.closed = true; poller_fd_reset (&self->socket_event); poller_timer_reset (&self->timeout_timer); str_reset (&self->read_buffer); str_reset (&self->write_buffer); self->got_hello = false; self->idling = false; self->idling_subsystems = 0; self->in_list = false; self->state = MPD_DISCONNECTED; } static void mpd_client_fail (struct mpd_client *self) { mpd_client_reset (self); if (self->on_failure) self->on_failure (self->user_data); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static bool mpd_client_parse_response (const char *p, struct mpd_response *response) { if (!strcmp (p, "OK")) return response->success = true; char *end = NULL; if (*p++ != 'A' || *p++ != 'C' || *p++ != 'K' || *p++ != ' ' || *p++ != '[') return false; errno = 0; response->error = strtoul (p, &end, 10); if (errno != 0 || end == p) return false; p = end; if (*p++ != '@') return false; errno = 0; response->list_offset = strtoul (p, &end, 10); if (errno != 0 || end == p) return false; p = end; if (*p++ != ']' || *p++ != ' ' || *p++ != '{' || !(end = strchr (p, '}'))) return false; response->current_command = xstrndup (p, end - p); p = end + 1; if (*p++ != ' ') return false; response->message_text = xstrdup (p); response->success = false; return true; } static bool mpd_client_parse_hello (struct mpd_client *self, const char *line) { const char hello[] = "OK MPD "; if (strncmp (line, hello, sizeof hello - 1)) { print_debug ("invalid MPD hello message"); return false; } // TODO: call "on_connected" now. We should however also set up a timer // so that we don't wait on this message forever. return self->got_hello = true; } static bool mpd_client_parse_line (struct mpd_client *self, const char *line) { if (self->on_io_hook) self->on_io_hook (self->user_data, false, line); if (!self->got_hello) return mpd_client_parse_hello (self, line); struct mpd_response response; memset (&response, 0, sizeof response); if (!strcmp (line, "list_OK")) strv_append_owned (&self->data, NULL); else if (mpd_client_parse_response (line, &response)) mpd_client_dispatch (self, &response); else strv_append (&self->data, line); free (response.current_command); free (response.message_text); return true; } /// All output from MPD commands seems to be in a trivial "key: value" format static char * mpd_client_parse_kv (char *line, char **value) { char *sep; if (!(sep = strstr (line, ": "))) return NULL; *sep = 0; *value = sep + 2; return line; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static void mpd_client_update_poller (struct mpd_client *self) { poller_fd_set (&self->socket_event, self->write_buffer.len ? (POLLIN | POLLOUT) : POLLIN); } static bool mpd_client_process_input (struct mpd_client *self) { // Split socket input at newlines and process them separately struct str *rb = &self->read_buffer; char *start = rb->str, *end = start + rb->len; for (char *p = start; p < end; p++) { if (*p != '\n') continue; *p = 0; if (!mpd_client_parse_line (self, start)) return false; start = p + 1; } str_remove_slice (rb, 0, start - rb->str); return true; } static void mpd_client_on_ready (const struct pollfd *pfd, void *user_data) { (void) pfd; struct mpd_client *self = user_data; if (socket_io_try_read (self->socket, &self->read_buffer) != SOCKET_IO_OK || !mpd_client_process_input (self) || socket_io_try_write (self->socket, &self->write_buffer) != SOCKET_IO_OK) mpd_client_fail (self); else mpd_client_update_poller (self); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static bool mpd_client_must_quote_char (char c) { return (unsigned char) c <= ' ' || c == '"' || c == '\''; } static bool mpd_client_must_quote (const char *s) { if (!*s) return true; for (; *s; s++) if (mpd_client_must_quote_char (*s)) return true; return false; } static void mpd_client_quote (const char *s, struct str *output) { str_append_c (output, '"'); for (; *s; s++) { if (mpd_client_must_quote_char (*s)) str_append_c (output, '\\'); str_append_c (output, *s); } str_append_c (output, '"'); } /// Beware that delivery of the event isn't deferred and you musn't make /// changes to the interface while processing the event! static void mpd_client_add_task (struct mpd_client *self, mpd_client_task_cb cb, void *user_data) { // This only has meaning with command_list_ok_begin, and then it requires // special handling (all in-list tasks need to be specially marked and // later flushed if an early ACK or OK arrives). hard_assert (!self->in_list); struct mpd_client_task *task = xcalloc (1, sizeof *self); task->callback = cb; task->user_data = user_data; LIST_APPEND_WITH_TAIL (self->tasks, self->tasks_tail, task); } /// Send a command. Remember to call mpd_client_add_task() to handle responses, /// unless the command is being sent in a list. static void mpd_client_send_command (struct mpd_client *self, const char *command, ...) ATTRIBUTE_SENTINEL; /// Avoid calling this method directly if you don't want things to explode static void mpd_client_send_command_raw (struct mpd_client *self, const char *raw) { // Automatically interrupt idle mode if (self->idling) { poller_timer_reset (&self->timeout_timer); self->idling = false; self->idling_subsystems = 0; mpd_client_send_command (self, "noidle", NULL); } if (self->on_io_hook) self->on_io_hook (self->user_data, true, raw); str_append (&self->write_buffer, raw); str_append_c (&self->write_buffer, '\n'); mpd_client_update_poller (self); } static void mpd_client_send_commandv (struct mpd_client *self, char **fields) { struct str line = str_make (); for (; *fields; fields++) { if (line.len) str_append_c (&line, ' '); if (mpd_client_must_quote (*fields)) mpd_client_quote (*fields, &line); else str_append (&line, *fields); } mpd_client_send_command_raw (self, line.str); str_free (&line); } static void mpd_client_send_command (struct mpd_client *self, const char *command, ...) { struct strv v = strv_make (); va_list ap; va_start (ap, command); for (; command; command = va_arg (ap, const char *)) strv_append (&v, command); va_end (ap); mpd_client_send_commandv (self, v.vector); strv_free (&v); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - /// "On success for all commands, OK is returned. If a command fails, no more /// commands are executed and the appropriate ACK error is returned" static void mpd_client_list_begin (struct mpd_client *self) { hard_assert (!self->in_list); mpd_client_send_command (self, "command_list_begin", NULL); self->in_list = true; } /// Beware that "list_OK" turns into NULL values in the output vector static void mpd_client_list_ok_begin (struct mpd_client *self) { hard_assert (!self->in_list); mpd_client_send_command (self, "command_list_ok_begin", NULL); self->in_list = true; } /// End a list of commands. Remember to call mpd_client_add_task() /// to handle the summary response. static void mpd_client_list_end (struct mpd_client *self) { hard_assert (self->in_list); mpd_client_send_command (self, "command_list_end", NULL); self->in_list = false; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static bool mpd_resolve_subsystem (const char *name, unsigned *output) { for (size_t i = 0; i < N_ELEMENTS (mpd_subsystem_names); i++) if (!strcasecmp_ascii (name, mpd_subsystem_names[i])) { *output |= 1 << i; return true; } return false; } static void mpd_client_on_idle_return (const struct mpd_response *response, const struct strv *data, void *user_data) { (void) response; struct mpd_client *self = user_data; unsigned subsystems = 0; for (size_t i = 0; i < data->len; i++) { char *value, *key; if (!(key = mpd_client_parse_kv (data->vector[i], &value))) print_debug ("%s: %s", "erroneous MPD output", data->vector[i]); else if (strcasecmp_ascii (key, "changed")) print_debug ("%s: %s", "unexpected idle key", key); else if (!mpd_resolve_subsystem (value, &subsystems)) print_debug ("%s: %s", "unknown subsystem", value); } // Not resetting "idling" here, we may send an extra "noidle" no problem if (self->on_event && subsystems) self->on_event (subsystems, self->user_data); } static void mpd_client_idle (struct mpd_client *self, unsigned subsystems); static void mpd_client_on_timeout (void *user_data) { struct mpd_client *self = user_data; // Abort and immediately restore the current idle so that MPD doesn't // disconnect us, even though the documentation says this won't happen. // Just sending this out should bring a dead connection down over TCP. // TODO: set another timer to make sure we get a reply mpd_client_idle (self, self->idling_subsystems); } /// When not expecting to send any further commands, you should call this /// in order to keep the connection alive. Or to receive updates. static void mpd_client_idle (struct mpd_client *self, unsigned subsystems) { hard_assert (!self->in_list); struct strv v = strv_make (); strv_append (&v, "idle"); for (size_t i = 0; i < N_ELEMENTS (mpd_subsystem_names); i++) if (subsystems & (1 << i)) strv_append (&v, mpd_subsystem_names[i]); mpd_client_send_commandv (self, v.vector); strv_free (&v); self->timeout_timer.dispatcher = mpd_client_on_timeout; self->timeout_timer.user_data = self; poller_timer_set (&self->timeout_timer, 5 * 60 * 1000); mpd_client_add_task (self, mpd_client_on_idle_return, self); self->idling = true; self->idling_subsystems = subsystems; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static void mpd_client_finish_connection (struct mpd_client *self, int socket) { set_blocking (socket, false); self->socket = socket; self->state = MPD_CONNECTED; self->socket_event = poller_fd_make (self->poller, self->socket); self->socket_event.dispatcher = mpd_client_on_ready; self->socket_event.user_data = self; mpd_client_update_poller (self); if (self->on_connected) self->on_connected (self->user_data); } static void mpd_client_destroy_connector (struct mpd_client *self) { if (self->connector) connector_free (self->connector); free (self->connector); self->connector = NULL; // Not connecting anymore self->state = MPD_DISCONNECTED; } static void mpd_client_on_connector_failure (void *user_data) { struct mpd_client *self = user_data; mpd_client_destroy_connector (self); mpd_client_fail (self); } static void mpd_client_on_connector_connected (void *user_data, int socket, const char *host) { (void) host; struct mpd_client *self = user_data; mpd_client_destroy_connector (self); mpd_client_finish_connection (self, socket); } static bool mpd_client_connect_unix (struct mpd_client *self, const char *address, struct error **e) { int fd = socket (AF_UNIX, SOCK_STREAM, 0); if (fd == -1) return error_set (e, "%s: %s", "socket", strerror (errno)); // Expand tilde if needed char *expanded = resolve_filename (address, xstrdup); struct sockaddr_un sau; sau.sun_family = AF_UNIX; strncpy (sau.sun_path, expanded, sizeof sau.sun_path); sau.sun_path[sizeof sau.sun_path - 1] = 0; free (expanded); if (connect (fd, (struct sockaddr *) &sau, sizeof sau)) { error_set (e, "%s: %s", "connect", strerror (errno)); xclose (fd); return false; } mpd_client_finish_connection (self, fd); return true; } static bool mpd_client_connect (struct mpd_client *self, const char *address, const char *service, struct error **e) { hard_assert (self->state == MPD_DISCONNECTED); // If it looks like a path, assume it's a UNIX socket if (strchr (address, '/')) return mpd_client_connect_unix (self, address, e); struct connector *connector = xmalloc (sizeof *connector); connector_init (connector, self->poller); self->connector = connector; connector->user_data = self; connector->on_connected = mpd_client_on_connector_connected; connector->on_failure = mpd_client_on_connector_failure; connector_add_target (connector, address, service); self->state = MPD_CONNECTING; return true; } #endif