| /** |
| * @file evhtp.c |
| * |
| * @brief implementation file for libevhtp. |
| */ |
| |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdint.h> |
| #include <errno.h> |
| #include <signal.h> |
| #include <strings.h> |
| #include <inttypes.h> |
| #ifndef WIN32 |
| #include <sys/socket.h> |
| #include <netinet/in.h> |
| #include <netinet/tcp.h> |
| #include <arpa/inet.h> |
| #else |
| #define WINVER 0x0501 |
| #include <winsock2.h> |
| #include <ws2tcpip.h> |
| #endif |
| #ifndef NO_SYS_UN |
| #include <sys/un.h> |
| #endif |
| |
| #include <limits.h> |
| #include <event2/dns.h> |
| |
| #include "internal.h" |
| #include "numtoa.h" |
| #include "evhtp/evhtp.h" |
| |
| #include "log.h" |
| |
| /** |
| * @brief structure containing a single callback and configuration |
| * |
| * The definition structure which is used within the evhtp_callbacks_t |
| * structure. This holds information about what should execute for either |
| * a single or regex path. |
| * |
| * For example, if you registered a callback to be executed on a request |
| * for "/herp/derp", your defined callback will be executed. |
| * |
| * Optionally you can set callback-specific hooks just like per-connection |
| * hooks using the same rules. |
| * |
| */ |
| struct evhtp_callback_s { |
| evhtp_callback_type type; /**< the type of callback (regex|path) */ |
| evhtp_callback_cb cb; /**< the actual callback function */ |
| void * cbarg; /**< user-defind arguments passed to the cb */ |
| evhtp_hooks_t * hooks; /**< per-callback hooks */ |
| size_t len; |
| |
| union { |
| char * path; |
| char * glob; |
| #ifndef EVHTP_DISABLE_REGEX |
| regex_t * regex; |
| #endif |
| } val; |
| |
| TAILQ_ENTRY(evhtp_callback_s) next; |
| }; |
| |
| TAILQ_HEAD(evhtp_callbacks_s, evhtp_callback_s); |
| |
| #define SET_BIT(VAR, FLAG) VAR |= FLAG |
| #define UNSET_BIT(VAR, FLAG) VAR &= ~FLAG |
| |
| #define HTP_FLAG_ON(PRE, FLAG) SET_BIT(PRE->flags, FLAG) |
| #define HTP_FLAG_OFF(PRE, FLAG) UNSET_BIT(PRE->flags, FLAG) |
| |
| #define HOOK_AVAIL(var, hook_name) (var->hooks && var->hooks->hook_name) |
| #define HOOK_FUNC(var, hook_name) (var->hooks->hook_name) |
| #define HOOK_ARGS(var, hook_name) var->hooks->hook_name ## _arg |
| |
| #define HOOK_REQUEST_RUN(request, hook_name, ...) do { \ |
| if (HOOK_AVAIL(request, hook_name)) \ |
| { \ |
| return HOOK_FUNC(request, hook_name) (request, __VA_ARGS__, \ |
| HOOK_ARGS(request, hook_name)); \ |
| } \ |
| \ |
| if (request->conn && HOOK_AVAIL(request->conn, hook_name)) \ |
| { \ |
| return HOOK_FUNC(request->conn, hook_name) (request, __VA_ARGS__, \ |
| HOOK_ARGS(request->conn, hook_name)); \ |
| } \ |
| } while (0) |
| |
| #define HOOK_REQUEST_RUN_NARGS(__request, hook_name) do { \ |
| if (HOOK_AVAIL(__request, hook_name)) \ |
| { \ |
| return HOOK_FUNC(__request, hook_name) (__request, \ |
| HOOK_ARGS(__request, hook_name)); \ |
| } \ |
| \ |
| if (__request->conn && HOOK_AVAIL(__request->conn, hook_name)) \ |
| { \ |
| return HOOK_FUNC(__request->conn, hook_name) (request, \ |
| HOOK_ARGS(__request->conn, hook_name)); \ |
| } \ |
| } while (0); |
| |
| #ifndef EVHTP_DISABLE_EVTHR |
| /** |
| * @brief Helper macro to lock htp structure |
| * |
| * @param h htp structure |
| */ |
| #define htp__lock_(h) do { \ |
| if (h->lock) \ |
| { \ |
| pthread_mutex_lock(h->lock); \ |
| } \ |
| } while (0) |
| |
| /** |
| * @brief Helper macro to unlock htp lock |
| * |
| * @param h htp structure |
| */ |
| #define htp__unlock_(h) do { \ |
| if (h->lock) \ |
| { \ |
| pthread_mutex_unlock(h->lock); \ |
| } \ |
| } while (0) |
| #else |
| #define htp__lock_(h) do { \ |
| } while (0) |
| #define htp__unlock_(h) do { \ |
| } while (0) |
| #endif |
| |
| #ifndef TAILQ_FOREACH_SAFE |
| #define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ |
| for ((var) = TAILQ_FIRST((head)); \ |
| (var) && ((tvar) = TAILQ_NEXT((var), field), 1); \ |
| (var) = (tvar)) |
| #endif |
| |
| /* rc == request->conn. Just little things to make life easier */ |
| #define rc_scratch conn->scratch_buf |
| #define rc_parser conn->parser |
| |
| /* ch_ == conn->hooks->on_... */ |
| #define ch_fini_arg hooks->on_connection_fini_arg |
| #define ch_fini hooks->on_connection_fini |
| |
| /* cr_ == conn->request */ |
| #define cr_status request->status |
| |
| /* rh_ == request->hooks->on_ */ |
| #define rh_err hooks->on_error |
| #define rh_err_arg hooks->on_error_arg |
| |
| #ifndef EVHTP_DISABLE_MEMFUNCTIONS |
| |
| static void * (*malloc_)(size_t sz) = malloc; |
| static void * (* realloc_)(void * d, size_t sz) = realloc; |
| static void (* free_)(void * d) = free; |
| |
| /** |
| * @brief Wrapper for malloc so that a different malloc can be used |
| * if desired. |
| * |
| * @see evhtp_set_mem_functions |
| * |
| * @param size size_t of memory to be allocated |
| * |
| * @return void * to malloc'd memory or NULL if fail |
| */ |
| static void * |
| htp__malloc_(size_t size) |
| { |
| return malloc_(size); |
| } |
| |
| /** |
| * @brief Wrapper for realloc so that a different realloc can be used |
| * if desired. |
| * |
| * @see evhtp_set_mem_functions |
| * |
| * @param ptr current memory ptr |
| * @param size size_t of memory to be allocated |
| * |
| * @return void * to newly realloc'd memory or NULL if fail |
| */ |
| static void * |
| htp__realloc_(void * ptr, size_t size) |
| { |
| return realloc_(ptr, size); |
| } |
| |
| /** |
| * @brief Wrapper for free so that a different free can be used |
| * if desired. |
| * |
| * @see evhtp_set_mem_functions |
| * |
| * @param ptr pointer to memory to be freed. |
| * |
| */ |
| static void |
| htp__free_(void * ptr) |
| { |
| return free_(ptr); |
| } |
| |
| /** |
| * @brief Wrapper for calloc so that a different calloc can be used |
| * if desired. |
| * |
| * @see evhtp_set_mem_functions |
| * |
| * @param nmemb number of members (as a size_t) |
| * @param size size of member blocks (as a size_t) |
| * |
| * @return void * to new memory block |
| */ |
| static void * |
| htp__calloc_(size_t nmemb, size_t size) |
| { |
| if (malloc_ != malloc) |
| { |
| size_t len = nmemb * size; |
| void * p; |
| |
| if ((p = malloc_(len)) == NULL) |
| { |
| return NULL; |
| } |
| |
| memset(p, 0, len); |
| |
| return p; |
| } |
| |
| return calloc(nmemb, size); |
| } |
| |
| /** |
| * @brief implementation of strdup function. |
| * |
| * @param str - null terminated string. |
| * |
| * @return duplicate of string or NULL if fail |
| * |
| */ |
| static char * |
| htp__strdup_(const char * str) |
| { |
| if (malloc_ != malloc) |
| { |
| size_t len; |
| void * p; |
| |
| len = strlen(str); |
| |
| if ((p = malloc_(len + 1)) == NULL) |
| { |
| return NULL; |
| } |
| |
| memcpy(p, str, len + 1); |
| |
| return p; |
| } |
| |
| return strdup(str); |
| } |
| |
| /** |
| * @brief implementation of strndup function. |
| * |
| * @param str - null terminated string. |
| * @param len - size_t length off string |
| * |
| * @return duplicate of string or NULL if fail |
| * |
| */ |
| static char * |
| htp__strndup_(const char * str, size_t len) |
| { |
| if (malloc_ != malloc) |
| { |
| char * p; |
| |
| if ((p = malloc_(len + 1)) != NULL) |
| { |
| memcpy(p, str, len + 1); |
| } else { |
| return NULL; |
| } |
| |
| p[len] = '\0'; |
| |
| return p; |
| } |
| |
| return strndup(str, len); |
| } |
| |
| #else |
| #define htp__malloc_(sz) malloc(sz) |
| #define htp__calloc_(n, sz) calloc(n, sz) |
| #define htp__strdup_(s) strdup(s) |
| #define htp__strndup_(n, sz) strndup(n, sz) |
| #define htp__realloc_(p, sz) realloc(p, sz) |
| #define htp__free_(p) free(p) |
| #endif |
| |
| |
| void |
| evhtp_set_mem_functions(void *(*mallocfn_)(size_t len), |
| void *(*reallocfn_)(void * p, size_t sz), |
| void (* freefn_)(void * p)) |
| { |
| #ifndef EVHTP_DISABLE_MEMFUNCTIONS |
| malloc_ = mallocfn_; |
| realloc_ = reallocfn_; |
| free_ = freefn_; |
| |
| return event_set_mem_functions(malloc_, realloc_, free_); |
| #endif |
| } |
| |
| /** |
| * @brief returns string status code from enum code |
| * |
| * @param code as evhtp_res enum |
| * |
| * @return string corresponding to code, else UNKNOWN |
| */ |
| static const char * |
| status_code_to_str(evhtp_res code) |
| { |
| switch (code) { |
| case EVHTP_RES_200: |
| return "OK"; |
| case EVHTP_RES_300: |
| return "Redirect"; |
| case EVHTP_RES_400: |
| return "Bad Request"; |
| case EVHTP_RES_NOTFOUND: |
| return "Not Found"; |
| case EVHTP_RES_SERVERR: |
| return "Internal Server Error"; |
| case EVHTP_RES_CONTINUE: |
| return "Continue"; |
| case EVHTP_RES_FORBIDDEN: |
| return "Forbidden"; |
| case EVHTP_RES_SWITCH_PROTO: |
| return "Switching Protocols"; |
| case EVHTP_RES_MOVEDPERM: |
| return "Moved Permanently"; |
| case EVHTP_RES_PROCESSING: |
| return "Processing"; |
| case EVHTP_RES_URI_TOOLONG: |
| return "URI Too Long"; |
| case EVHTP_RES_CREATED: |
| return "Created"; |
| case EVHTP_RES_ACCEPTED: |
| return "Accepted"; |
| case EVHTP_RES_NAUTHINFO: |
| return "No Auth Info"; |
| case EVHTP_RES_NOCONTENT: |
| return "No Content"; |
| case EVHTP_RES_RSTCONTENT: |
| return "Reset Content"; |
| case EVHTP_RES_PARTIAL: |
| return "Partial Content"; |
| case EVHTP_RES_MSTATUS: |
| return "Multi-Status"; |
| case EVHTP_RES_IMUSED: |
| return "IM Used"; |
| case EVHTP_RES_FOUND: |
| return "Found"; |
| case EVHTP_RES_SEEOTHER: |
| return "See Other"; |
| case EVHTP_RES_NOTMOD: |
| return "Not Modified"; |
| case EVHTP_RES_USEPROXY: |
| return "Use Proxy"; |
| case EVHTP_RES_SWITCHPROXY: |
| return "Switch Proxy"; |
| case EVHTP_RES_TMPREDIR: |
| return "Temporary Redirect"; |
| case EVHTP_RES_UNAUTH: |
| return "Unauthorized"; |
| case EVHTP_RES_PAYREQ: |
| return "Payment Required"; |
| case EVHTP_RES_METHNALLOWED: |
| return "Not Allowed"; |
| case EVHTP_RES_NACCEPTABLE: |
| return "Not Acceptable"; |
| case EVHTP_RES_PROXYAUTHREQ: |
| return "Proxy Authentication Required"; |
| case EVHTP_RES_TIMEOUT: |
| return "Request Timeout"; |
| case EVHTP_RES_CONFLICT: |
| return "Conflict"; |
| case EVHTP_RES_GONE: |
| return "Gone"; |
| case EVHTP_RES_LENREQ: |
| return "Length Required"; |
| case EVHTP_RES_PRECONDFAIL: |
| return "Precondition Failed"; |
| case EVHTP_RES_ENTOOLARGE: |
| return "Entity Too Large"; |
| case EVHTP_RES_URITOOLARGE: |
| return "Request-URI Too Long"; |
| case EVHTP_RES_UNSUPPORTED: |
| return "Unsupported Media Type"; |
| case EVHTP_RES_RANGENOTSC: |
| return "Requested Range Not Satisfiable"; |
| case EVHTP_RES_EXPECTFAIL: |
| return "Expectation Failed"; |
| case EVHTP_RES_IAMATEAPOT: |
| return "I'm a teapot"; |
| case EVHTP_RES_NOTIMPL: |
| return "Not Implemented"; |
| case EVHTP_RES_BADGATEWAY: |
| return "Bad Gateway"; |
| case EVHTP_RES_SERVUNAVAIL: |
| return "Service Unavailable"; |
| case EVHTP_RES_GWTIMEOUT: |
| return "Gateway Timeout"; |
| case EVHTP_RES_VERNSUPPORT: |
| return "HTTP Version Not Supported"; |
| case EVHTP_RES_BWEXEED: |
| return "Bandwidth Limit Exceeded"; |
| } /* switch */ |
| |
| return "UNKNOWN"; |
| } /* status_code_to_str */ |
| |
| #ifndef EVHTP_DISABLE_SSL |
| static int session_id_context = 1; |
| #ifndef EVHTP_DISABLE_EVTHR |
| static int ssl_num_locks; |
| static evhtp_mutex_t * ssl_locks; |
| static int ssl_locks_initialized = 0; |
| #endif |
| #endif |
| |
| /* |
| * COMPAT FUNCTIONS |
| */ |
| |
| #ifdef NO_STRNLEN |
| /** |
| * @brief Implementation of strnlen function if none exists. |
| * |
| * @param s - null terminated character string |
| * @param maxlen - maximum length of string |
| * |
| * @return length of string |
| * |
| */ |
| static size_t |
| strnlen(const char * s, size_t maxlen) |
| { |
| const char * e; |
| size_t n; |
| |
| for (e = s, n = 0; *e && n < maxlen; e++, n++) |
| { |
| ; |
| } |
| |
| return n; |
| } |
| |
| #endif |
| |
| #ifdef NO_STRNDUP |
| /** |
| * @brief Implementation of strndup if none exists. |
| * |
| * @param s - const char * to null terminated string |
| * @param n - size_t maximum legnth of string |
| * |
| * @return length limited string duplicate or NULL if fail |
| * |
| */ |
| static char * |
| strndup(const char * s, size_t n) |
| { |
| size_t len = strnlen(s, n); |
| char * ret; |
| |
| if (len < n) |
| { |
| return htp__strdup_(s); |
| } |
| |
| if ((ret = htp__malloc_(n + 1)) == NULL) |
| { |
| return NULL; |
| } |
| |
| ret[n] = '\0'; |
| |
| memcpy(ret, s, n); |
| |
| return ret; |
| } |
| |
| #endif |
| |
| /* |
| * PRIVATE FUNCTIONS |
| */ |
| |
| /** |
| * |
| * @brief helper macro to determine if http version is HTTP/1.0 |
| * |
| * @param major the major version number |
| * @param minor the minor version number |
| * |
| * @return 1 if HTTP/1.0, else 0 |
| */ |
| |
| #define htp__is_http_11_(_major, _minor) \ |
| (_major >= 1 && _minor >= 1) |
| |
| /** |
| * @brief helper function to determine if http version is HTTP/1.1 |
| * |
| * @param major the major version number |
| * @param minor the minor version number |
| * |
| * @return 1 if HTTP/1.1, else 0 |
| */ |
| |
| #define htp__is_http_10_(_major, _minor) \ |
| (_major >= 1 && _minor <= 0) |
| |
| |
| /** |
| * @brief returns the HTTP protocol version |
| * |
| * @param major the major version number |
| * @param minor the minor version number |
| * |
| * @return EVHTP_PROTO_10 if HTTP/1.0, EVHTP_PROTO_11 if HTTP/1.1, otherwise |
| * EVHTP_PROTO_INVALID |
| */ |
| static inline evhtp_proto |
| htp__protocol_(const char major, const char minor) |
| { |
| if (htp__is_http_10_(major, minor)) |
| { |
| return EVHTP_PROTO_10; |
| } |
| |
| if (htp__is_http_11_(major, minor)) |
| { |
| return EVHTP_PROTO_11; |
| } |
| |
| return EVHTP_PROTO_INVALID; |
| } |
| |
| /** |
| * @brief runs the user-defined on_path hook for a request |
| * |
| * @param request the request structure |
| * @param path the path structure |
| * |
| * @return EVHTP_RES_OK on success, otherwise something else. |
| */ |
| static inline evhtp_res |
| htp__hook_path_(evhtp_request_t * request, evhtp_path_t * path) |
| { |
| HOOK_REQUEST_RUN(request, on_path, path); |
| |
| return EVHTP_RES_OK; |
| } |
| |
| /** |
| * @brief runs the user-defined on_header hook for a request |
| * |
| * once a full key: value header has been parsed, this will call the hook |
| * |
| * @param request the request strucutre |
| * @param header the header structure |
| * |
| * @return EVHTP_RES_OK on success, otherwise something else. |
| */ |
| static inline evhtp_res |
| htp__hook_header_(evhtp_request_t * request, evhtp_header_t * header) |
| { |
| HOOK_REQUEST_RUN(request, on_header, header); |
| |
| return EVHTP_RES_OK; |
| } |
| |
| /** |
| * @brief runs the user-defined on_Headers hook for a request after all headers |
| * have been parsed. |
| * |
| * @param request the request structure |
| * @param headers the headers tailq structure |
| * |
| * @return EVHTP_RES_OK on success, otherwise something else. |
| */ |
| static inline evhtp_res |
| htp__hook_headers_(evhtp_request_t * request, evhtp_headers_t * headers) |
| { |
| HOOK_REQUEST_RUN(request, on_headers, headers); |
| |
| return EVHTP_RES_OK; |
| } |
| |
| /** |
| * @brief runs the user-defined on_body hook for requests containing a body. |
| * the data is stored in the request->buffer_in so the user may either |
| * leave it, or drain upon being called. |
| * |
| * @param request the request strucutre |
| * @param buf a evbuffer containing body data |
| * |
| * @return EVHTP_RES_OK on success, otherwise something else. |
| */ |
| static inline evhtp_res |
| htp__hook_body_(evhtp_request_t * request, struct evbuffer * buf) |
| { |
| if (request == NULL) |
| { |
| return 500; |
| } |
| |
| HOOK_REQUEST_RUN(request, on_read, buf); |
| |
| return EVHTP_RES_OK; |
| } |
| |
| /** |
| * @brief runs the user-defined hook called just prior to a request been |
| * free()'d |
| * |
| * @param request therequest structure |
| * |
| * @return EVHTP_RES_OK on success, otherwise treated as an error |
| */ |
| static inline evhtp_res |
| htp__hook_request_fini_(evhtp_request_t * request) |
| { |
| if (request == NULL) |
| { |
| return 500; |
| } |
| |
| HOOK_REQUEST_RUN_NARGS(request, on_request_fini); |
| |
| return EVHTP_RES_OK; |
| } |
| |
| /** |
| * @brief Runs the user defined request hook |
| * |
| * @param request |
| * @param len |
| * @return |
| */ |
| static inline evhtp_res |
| htp__hook_chunk_new_(evhtp_request_t * request, uint64_t len) |
| { |
| HOOK_REQUEST_RUN(request, on_new_chunk, len); |
| |
| return EVHTP_RES_OK; |
| } |
| |
| /** |
| * @brief Runs the user defined on_chunk_fini hook |
| * |
| * @param request |
| * @return |
| */ |
| static inline evhtp_res |
| htp__hook_chunk_fini_(evhtp_request_t * request) |
| { |
| HOOK_REQUEST_RUN_NARGS(request, on_chunk_fini); |
| |
| return EVHTP_RES_OK; |
| } |
| |
| /** |
| * @brief Runs the user defined on chunk_finis hook |
| * |
| * @param request |
| * @return |
| */ |
| static inline evhtp_res |
| htp__hook_chunks_fini_(evhtp_request_t * request) |
| { |
| HOOK_REQUEST_RUN_NARGS(request, on_chunks_fini); |
| |
| return EVHTP_RES_OK; |
| } |
| |
| /** |
| * @brief Runs the user defined on_headers_start hook |
| * |
| * @param request |
| * @return |
| */ |
| static inline evhtp_res |
| htp__hook_headers_start_(evhtp_request_t * request) |
| { |
| HOOK_REQUEST_RUN_NARGS(request, on_headers_start); |
| |
| return EVHTP_RES_OK; |
| } |
| |
| /** |
| * @brief runs the user-definedhook called just prior to a connection being |
| * closed |
| * |
| * @param connection the connection structure |
| * |
| * @return EVHTP_RES_OK on success, but pretty much ignored in any case. |
| */ |
| static inline evhtp_res |
| htp__hook_connection_fini_(evhtp_connection_t * connection) |
| { |
| if (evhtp_unlikely(connection == NULL)) |
| { |
| return 500; |
| } |
| |
| if (connection->hooks != NULL && connection->ch_fini != NULL) |
| { |
| return (connection->ch_fini)(connection, connection->ch_fini_arg); |
| } |
| |
| return EVHTP_RES_OK; |
| } |
| |
| /** |
| * @brief runs the user-defined hook when a connection error occurs |
| * |
| * @param request the request structure |
| * @param errtype the error that ocurred |
| */ |
| static inline void |
| htp__hook_error_(evhtp_request_t * request, evhtp_error_flags errtype) |
| { |
| if (request && request->hooks && request->rh_err) |
| { |
| (*request->rh_err)(request, errtype, request->rh_err_arg); |
| } |
| } |
| |
| /** |
| * @brief runs the user-defined hook when a connection error occurs |
| * |
| * @param connection the connection structure |
| * @param errtype the error that ocurred |
| */ |
| static inline evhtp_res |
| htp__hook_connection_error_(evhtp_connection_t * connection, evhtp_error_flags errtype) |
| { |
| if (connection == NULL) |
| { |
| return EVHTP_RES_FATAL; |
| } |
| |
| if (connection->request != NULL) |
| { |
| htp__hook_error_(connection->request, errtype); |
| } |
| |
| return EVHTP_RES_OK; |
| } |
| |
| /** |
| * @brief Runs the user defined hostname processing hook |
| * |
| * @param r |
| * @param hostname |
| * @return |
| */ |
| static inline evhtp_res |
| htp__hook_hostname_(evhtp_request_t * r, const char * hostname) |
| { |
| HOOK_REQUEST_RUN(r, on_hostname, hostname); |
| |
| return EVHTP_RES_OK; |
| } |
| |
| /** |
| * @brief Runs the user defined on_write hook |
| * |
| * @param connection |
| * @return |
| */ |
| static inline evhtp_res |
| htp__hook_connection_write_(evhtp_connection_t * connection) |
| { |
| if (connection->hooks && connection->hooks->on_write) |
| { |
| return (connection->hooks->on_write)(connection, |
| connection->hooks->on_write_arg); |
| } |
| |
| return EVHTP_RES_OK; |
| } |
| |
| /** |
| * @brief glob/wildcard type pattern matching. |
| * |
| * Note: This code was derived from redis's (v2.6) stringmatchlen() function. |
| * |
| * @param pattern |
| * @param string |
| * |
| * @return |
| */ |
| static int |
| htp__glob_match_(const char * pattern, size_t plen, |
| const char * string, size_t str_len) |
| { |
| while (plen) |
| { |
| switch (pattern[0]) { |
| case '*': |
| while (pattern[1] == '*') |
| { |
| pattern++; |
| plen--; |
| } |
| |
| if (plen == 1) |
| { |
| return 1; /* match */ |
| } |
| |
| while (str_len) |
| { |
| if (htp__glob_match_(pattern + 1, plen - 1, |
| string, str_len)) |
| { |
| return 1; /* match */ |
| } |
| |
| string++; |
| str_len--; |
| } |
| |
| return 0; /* no match */ |
| default: |
| if (pattern[0] != string[0]) |
| { |
| return 0; /* no match */ |
| } |
| |
| string++; |
| str_len--; |
| break; |
| } /* switch */ |
| |
| pattern++; |
| plen--; |
| |
| if (str_len == 0) |
| { |
| while (*pattern == '*') |
| { |
| pattern++; |
| plen--; |
| } |
| |
| break; |
| } |
| } |
| |
| if (plen == 0 && str_len == 0) |
| { |
| return 1; |
| } |
| |
| return 0; |
| } /* htp__glob_match_ */ |
| |
| /** |
| * @brief Locates a given callback offsets performs a regex pattern match |
| * |
| * @param [IN] cbs ptr to evhtp_callbacks_t structure |
| * @param [IN] path |
| * @param [OUT] start_offset |
| * @param [OUT] end_offset |
| * @return |
| */ |
| static evhtp_callback_t * |
| htp__callback_find_(evhtp_callbacks_t * cbs, |
| const char * path, |
| unsigned int * start_offset, |
| unsigned int * end_offset) |
| { |
| size_t path_len; |
| |
| #ifndef EVHTP_DISABLE_REGEX |
| regmatch_t pmatch[28]; |
| #endif |
| evhtp_callback_t * callback; |
| |
| if (evhtp_unlikely(cbs == NULL)) |
| { |
| return NULL; |
| } |
| |
| path_len = strlen(path); |
| |
| TAILQ_FOREACH(callback, cbs, next) { |
| switch (callback->type) { |
| case evhtp_callback_type_hash: |
| if (strncmp(path, callback->val.path, callback->len) == 0) |
| { |
| *start_offset = 0; |
| *end_offset = path_len; |
| |
| return callback; |
| } |
| break; |
| #ifndef EVHTP_DISABLE_REGEX |
| case evhtp_callback_type_regex: |
| if (regexec(callback->val.regex, |
| path, |
| callback->val.regex->re_nsub + 1, |
| pmatch, 0) == 0) |
| { |
| *start_offset = pmatch[callback->val.regex->re_nsub].rm_so; |
| *end_offset = pmatch[callback->val.regex->re_nsub].rm_eo; |
| |
| return callback; |
| } |
| |
| break; |
| #endif |
| case evhtp_callback_type_glob: |
| { |
| size_t glob_len = strlen(callback->val.glob); |
| |
| if (htp__glob_match_(callback->val.glob, |
| glob_len, |
| path, |
| path_len) == 1) |
| { |
| *start_offset = 0; |
| *end_offset = path_len; |
| |
| return callback; |
| } |
| } |
| default: |
| break; |
| } /* switch */ |
| } |
| |
| return NULL; |
| } /* htp__callback_find_ */ |
| |
| /** |
| * @brief parses the path and file from an input buffer |
| * |
| * @details in order to properly create a structure that can match |
| * both a path and a file, this will parse a string into |
| * what it considers a path, and a file. |
| * |
| * @details if for example the input was "/a/b/c", the parser will |
| * consider "/a/b/" as the path, and "c" as the file. |
| * |
| * @param the unallocated destination buffer. |
| * @param data raw input data (assumes a /path/[file] structure) |
| * @param len length of the input data |
| * |
| * @return 0 on success, -1 on error. |
| */ |
| static int |
| htp__path_new_(evhtp_path_t ** out, const char * data, size_t len) |
| { |
| evhtp_path_t * req_path; |
| const char * data_end = (const char *)(data + len); |
| char * path = NULL; |
| char * file = NULL; |
| |
| |
| req_path = htp__calloc_(1, sizeof(*req_path)); |
| evhtp_alloc_assert(req_path); |
| |
| *out = NULL; |
| |
| if (evhtp_unlikely(len == 0)) |
| { |
| /* |
| * odd situation here, no preceding "/", so just assume the path is "/" |
| */ |
| path = htp__strdup_("/"); |
| evhtp_alloc_assert(path); |
| } else if (*data != '/') |
| { |
| /* request like GET stupid HTTP/1.0, treat stupid as the file, and |
| * assume the path is "/" |
| */ |
| path = htp__strdup_("/"); |
| evhtp_alloc_assert(path); |
| |
| file = htp__strndup_(data, len); |
| evhtp_alloc_assert(file); |
| } else { |
| if (data[len - 1] != '/') |
| { |
| /* |
| * the last character in data is assumed to be a file, not the end of path |
| * loop through the input data backwards until we find a "/" |
| */ |
| size_t i; |
| |
| for (i = (len - 1); i != 0; i--) |
| { |
| if (data[i] == '/') |
| { |
| /* |
| * we have found a "/" representing the start of the file, |
| * and the end of the path |
| */ |
| size_t path_len; |
| size_t file_len; |
| |
| path_len = (size_t)(&data[i] - data) + 1; |
| file_len = (size_t)(data_end - &data[i + 1]); |
| |
| /* check for overflow */ |
| if ((const char *)(data + path_len) > data_end) |
| { |
| evhtp_safe_free(req_path, htp__free_); |
| |
| return -1; |
| } |
| |
| /* check for overflow */ |
| if ((const char *)(&data[i + 1] + file_len) > data_end) |
| { |
| evhtp_safe_free(req_path, htp__free_); |
| |
| return -1; |
| } |
| |
| path = htp__strndup_(data, path_len); |
| evhtp_alloc_assert(path); |
| |
| file = htp__strndup_(&data[i + 1], file_len); |
| evhtp_alloc_assert(file); |
| |
| break; |
| } |
| } |
| |
| if (i == 0 && data[i] == '/' && !file && !path) |
| { |
| /* drops here if the request is something like GET /foo */ |
| path = htp__strdup_("/"); |
| evhtp_alloc_assert(path); |
| |
| if (len > 1) |
| { |
| file = htp__strndup_((const char *)(data + 1), len); |
| evhtp_alloc_assert(file); |
| } |
| } |
| } else { |
| /* the last character is a "/", thus the request is just a path */ |
| path = htp__strndup_(data, len); |
| evhtp_alloc_assert(path); |
| } |
| } |
| |
| if (len != 0) |
| { |
| req_path->full = htp__strndup_(data, len); |
| } else { |
| req_path->full = htp__strdup_("/"); |
| } |
| |
| evhtp_alloc_assert(req_path->full); |
| |
| req_path->path = path; |
| req_path->file = file; |
| |
| *out = req_path; |
| |
| return 0; |
| } /* htp__path_new_ */ |
| |
| /** |
| * @brief Correctly frees the evhtp_path_t ptr that is passed in. |
| * @param path |
| */ |
| static void |
| htp__path_free_(evhtp_path_t * path) |
| { |
| if (evhtp_unlikely(path == NULL)) |
| { |
| return; |
| } |
| |
| evhtp_safe_free(path->full, htp__free_); |
| evhtp_safe_free(path->path, htp__free_); |
| evhtp_safe_free(path->file, htp__free_); |
| evhtp_safe_free(path->match_start, htp__free_); |
| evhtp_safe_free(path->match_end, htp__free_); |
| |
| evhtp_safe_free(path, htp__free_); |
| } |
| |
| /** |
| * @brief create an authority structure |
| * |
| * @return 0 on success, -1 on error |
| */ |
| static int |
| htp__authority_new_(evhtp_authority_t ** out) |
| { |
| evhtp_authority_t * authority; |
| |
| if (evhtp_unlikely(out == NULL)) |
| { |
| return -1; |
| } |
| |
| *out = htp__calloc_(1, sizeof(*authority)); |
| |
| return (*out != NULL) ? 0 : -1; |
| } |
| |
| /** |
| * @brief frees an authority structure |
| * |
| * @param authority evhtp_authority_t |
| */ |
| static void |
| htp__authority_free_(evhtp_authority_t * authority) |
| { |
| if (authority == NULL) |
| { |
| return; |
| } |
| |
| evhtp_safe_free(authority->username, htp__free_); |
| evhtp_safe_free(authority->password, htp__free_); |
| evhtp_safe_free(authority->hostname, htp__free_); |
| |
| evhtp_safe_free(authority, htp__free_); |
| } |
| |
| /** |
| * @brief frees an overlay URI structure |
| * |
| * @param uri evhtp_uri_t |
| */ |
| static void |
| htp__uri_free_(evhtp_uri_t * uri) |
| { |
| if (evhtp_unlikely(uri == NULL)) |
| { |
| return; |
| } |
| |
| evhtp_safe_free(uri->query, evhtp_query_free); |
| evhtp_safe_free(uri->path, htp__path_free_); |
| evhtp_safe_free(uri->authority, htp__authority_free_); |
| |
| evhtp_safe_free(uri->fragment, htp__free_); |
| evhtp_safe_free(uri->query_raw, htp__free_); |
| |
| evhtp_safe_free(uri, htp__free_); |
| } |
| |
| /** |
| * @brief create an overlay URI structure |
| * |
| * @return 0 on success, -1 on error. |
| */ |
| static int |
| htp__uri_new_(evhtp_uri_t ** out) |
| { |
| evhtp_uri_t * uri; |
| |
| *out = NULL; |
| |
| if ((uri = htp__calloc_(1, sizeof(*uri))) == NULL) |
| { |
| return -1; |
| } |
| |
| uri->authority = NULL; |
| |
| if (htp__authority_new_(&uri->authority) == -1) |
| { |
| evhtp_safe_free(uri, htp__uri_free_); |
| return -1; |
| } |
| |
| *out = uri; |
| |
| return 0; |
| } |
| |
| /** |
| * @brief frees all data in an evhtp_request_t along with calling finished hooks |
| * |
| * @param request the request structure |
| */ |
| static void |
| htp__request_free_(evhtp_request_t * request) |
| { |
| if (evhtp_unlikely(request == NULL)) |
| { |
| return; |
| } |
| |
| htp__hook_request_fini_(request); |
| |
| evhtp_safe_free(request->uri, htp__uri_free_); |
| evhtp_safe_free(request->headers_in, evhtp_kvs_free); |
| evhtp_safe_free(request->headers_out, evhtp_kvs_free); |
| |
| if (request->conn && request->conn->request == request) |
| { |
| request->conn->request = NULL; |
| } |
| |
| if (request->buffer_in != NULL) |
| { |
| evhtp_safe_free(request->buffer_in, evbuffer_free); |
| } |
| |
| if (request->buffer_out != NULL) |
| { |
| evhtp_safe_free(request->buffer_out, evbuffer_free); |
| } |
| |
| evhtp_safe_free(request->hooks, htp__free_); |
| evhtp_safe_free(request, htp__free_); |
| } |
| |
| /** |
| * @brief Creates a new evhtp_request_t |
| * |
| * @param c |
| * |
| * @return evhtp_request_t structure on success, otherwise NULL |
| */ |
| static evhtp_request_t * |
| htp__request_new_(evhtp_connection_t * c) |
| { |
| evhtp_request_t * req; |
| uint8_t error; |
| |
| if (evhtp_unlikely(!(req = htp__calloc_(sizeof(*req), 1)))) |
| { |
| return NULL; |
| } |
| |
| error = 1; |
| req->conn = c; |
| req->htp = c ? c->htp : NULL; |
| req->status = EVHTP_RES_OK; |
| |
| do { |
| if (evhtp_unlikely(!(req->buffer_in = evbuffer_new()))) |
| { |
| break; |
| } |
| |
| if (evhtp_unlikely(!(req->buffer_out = evbuffer_new()))) |
| { |
| break; |
| } |
| |
| if (evhtp_unlikely(!(req->headers_in = htp__malloc_(sizeof(evhtp_headers_t))))) |
| { |
| break; |
| } |
| |
| if (evhtp_unlikely(!(req->headers_out = htp__malloc_(sizeof(evhtp_headers_t))))) |
| { |
| break; |
| } |
| |
| TAILQ_INIT(req->headers_in); |
| TAILQ_INIT(req->headers_out); |
| |
| error = 0; |
| } while (0); |
| |
| if (error == 0) |
| { |
| return req; |
| } |
| |
| evhtp_safe_free(req, htp__request_free_); |
| |
| return req; |
| } /* htp__request_new_ */ |
| |
| /** |
| * @brief Starts the parser for the connection associated with the parser struct |
| * |
| * @param p |
| * @return 0 on success, -1 on fail |
| */ |
| static int |
| htp__request_parse_start_(htparser * p) |
| { |
| evhtp_connection_t * c = htparser_get_userdata(p); |
| |
| if (evhtp_unlikely(c->type == evhtp_type_client)) |
| { |
| return 0; |
| } |
| |
| if (c->flags & EVHTP_CONN_FLAG_PAUSED) |
| { |
| return -1; |
| } |
| |
| if (c->request) |
| { |
| if (c->request->flags & EVHTP_REQ_FLAG_FINISHED) |
| { |
| htp__request_free_(c->request); |
| } else { |
| return -1; |
| } |
| } |
| |
| if (((c->request = htp__request_new_(c))) == NULL) |
| { |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * @brief parses http request arguments |
| * |
| * @see htparser_get_userdata |
| * |
| * @param p |
| * @param data |
| * @param len |
| * @return 0 on success, -1 on failure (sets connection cr_status as well) |
| */ |
| static int |
| htp__request_parse_args_(htparser * p, const char * data, size_t len) |
| { |
| evhtp_connection_t * c = htparser_get_userdata(p); |
| evhtp_uri_t * uri = c->request->uri; |
| const char * fragment; |
| int ignore_fragment; |
| |
| if (c->type == evhtp_type_client) |
| { |
| /* as a client, technically we should never get here, but just in case |
| * we return a 0 to the parser to continue. |
| */ |
| return 0; |
| } |
| |
| |
| /* if the parser flags has the IGNORE_FRAGMENTS bit set, skip |
| * the fragment parsing |
| */ |
| ignore_fragment = (c->htp->parser_flags & |
| EVHTP_PARSE_QUERY_FLAG_IGNORE_FRAGMENTS); |
| |
| |
| if (!ignore_fragment && (fragment = memchr(data, '#', len))) |
| { |
| /* Separate fragment from query according to RFC 3986. |
| * |
| * XXX: not happy about using strchr stuff, maybe this functionality |
| * is more apt as part of evhtp_parse_query() |
| */ |
| |
| ptrdiff_t frag_offset; |
| |
| frag_offset = fragment - data; |
| |
| if (frag_offset < len) |
| { |
| size_t fraglen; |
| |
| /* Skip '#'. */ |
| fragment += 1; |
| frag_offset += 1; |
| fraglen = len - frag_offset; |
| |
| uri->fragment = htp__malloc_(fraglen + 1); |
| evhtp_alloc_assert(uri->fragment); |
| |
| memcpy(uri->fragment, fragment, fraglen); |
| |
| uri->fragment[fraglen] = '\0'; |
| len -= fraglen + 1; /* Skip '#' + fragment string. */ |
| } |
| } |
| |
| uri->query = evhtp_parse_query_wflags(data, len, c->htp->parser_flags); |
| |
| if (evhtp_unlikely(!uri->query)) |
| { |
| c->cr_status = EVHTP_RES_ERROR; |
| |
| return -1; |
| } |
| |
| uri->query_raw = htp__malloc_(len + 1); |
| evhtp_alloc_assert(uri->query_raw); |
| |
| memcpy(uri->query_raw, data, len); |
| uri->query_raw[len] = '\0'; |
| |
| return 0; |
| } /* htp__request_parse_args_ */ |
| |
| static int |
| htp__request_parse_headers_start_(htparser * p) |
| { |
| evhtp_connection_t * c = htparser_get_userdata(p); |
| |
| if ((c->cr_status = htp__hook_headers_start_(c->request)) != EVHTP_RES_OK) |
| { |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| htp__request_parse_header_key_(htparser * p, const char * data, size_t len) |
| { |
| evhtp_connection_t * c = htparser_get_userdata(p); |
| char * key_s; |
| evhtp_header_t * hdr; |
| |
| key_s = htp__malloc_(len + 1); |
| evhtp_alloc_assert(key_s); |
| |
| key_s[len] = '\0'; |
| memcpy(key_s, data, len); |
| |
| if ((hdr = evhtp_header_key_add(c->request->headers_in, key_s, 0)) == NULL) |
| { |
| c->cr_status = EVHTP_RES_FATAL; |
| |
| return -1; |
| } |
| |
| hdr->k_heaped = 1; |
| |
| return 0; |
| } |
| |
| static int |
| htp__request_parse_header_val_(htparser * p, const char * data, size_t len) |
| { |
| evhtp_connection_t * c = htparser_get_userdata(p); |
| char * val_s; |
| evhtp_header_t * header; |
| |
| val_s = htp__malloc_(len + 1); |
| evhtp_alloc_assert(val_s); |
| |
| val_s[len] = '\0'; |
| memcpy(val_s, data, len); |
| |
| if ((header = evhtp_header_val_add(c->request->headers_in, val_s, 0)) == NULL) |
| { |
| evhtp_safe_free(val_s, htp__free_); |
| c->cr_status = EVHTP_RES_FATAL; |
| |
| return -1; |
| } |
| |
| header->v_heaped = 1; |
| |
| if ((c->cr_status = htp__hook_header_(c->request, header)) != EVHTP_RES_OK) |
| { |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static inline evhtp_t * |
| htp__request_find_vhost_(evhtp_t * evhtp, const char * name) |
| { |
| evhtp_t * evhtp_vhost; |
| evhtp_alias_t * evhtp_alias; |
| |
| TAILQ_FOREACH(evhtp_vhost, &evhtp->vhosts, next_vhost) { |
| if (evhtp_unlikely(evhtp_vhost->server_name == NULL)) |
| { |
| continue; |
| } |
| |
| if (htp__glob_match_(evhtp_vhost->server_name, |
| strlen(evhtp_vhost->server_name), name, |
| strlen(name)) == 1) |
| { |
| return evhtp_vhost; |
| } |
| |
| TAILQ_FOREACH(evhtp_alias, &evhtp_vhost->aliases, next) { |
| if (evhtp_alias->alias == NULL) |
| { |
| continue; |
| } |
| |
| if (htp__glob_match_(evhtp_alias->alias, |
| strlen(evhtp_alias->alias), name, |
| strlen(name)) == 1) |
| { |
| return evhtp_vhost; |
| } |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static inline int |
| htp__request_set_callbacks_(evhtp_request_t * request) |
| { |
| evhtp_t * evhtp; |
| evhtp_connection_t * conn; |
| evhtp_uri_t * uri; |
| evhtp_path_t * path; |
| evhtp_hooks_t * hooks; |
| evhtp_callback_t * callback; |
| evhtp_callback_cb cb; |
| void * cbarg; |
| |
| if (request == NULL) |
| { |
| return -1; |
| } |
| |
| if ((evhtp = request->htp) == NULL) |
| { |
| return -1; |
| } |
| |
| if ((conn = request->conn) == NULL) |
| { |
| return -1; |
| } |
| |
| if ((uri = request->uri) == NULL) |
| { |
| return -1; |
| } |
| |
| if ((path = uri->path) == NULL) |
| { |
| return -1; |
| } |
| |
| hooks = NULL; |
| callback = NULL; |
| cb = NULL; |
| cbarg = NULL; |
| |
| if ((callback = htp__callback_find_(evhtp->callbacks, path->full, |
| &path->matched_soff, &path->matched_eoff))) |
| { |
| /* matched a callback using both path and file (/a/b/c/d) */ |
| cb = callback->cb; |
| cbarg = callback->cbarg; |
| hooks = callback->hooks; |
| } else if ((callback = htp__callback_find_(evhtp->callbacks, path->path, |
| &path->matched_soff, &path->matched_eoff))) |
| { |
| /* matched a callback using *just* the path (/a/b/c/) */ |
| cb = callback->cb; |
| cbarg = callback->cbarg; |
| hooks = callback->hooks; |
| } else { |
| /* no callbacks found for either case, use defaults */ |
| cb = evhtp->defaults.cb; |
| cbarg = evhtp->defaults.cbarg; |
| |
| path->matched_soff = 0; |
| path->matched_eoff = (unsigned int)strlen(path->full); |
| } |
| |
| if (path->match_start == NULL) |
| { |
| path->match_start = htp__calloc_(strlen(path->full) + 1, 1); |
| evhtp_alloc_assert(path->match_start); |
| } |
| |
| if (path->match_end == NULL) |
| { |
| path->match_end = htp__calloc_(strlen(path->full) + 1, 1); |
| evhtp_alloc_assert(path->match_end); |
| } |
| |
| if (path->matched_soff != UINT_MAX /*ONIG_REGION_NOTPOS*/) |
| { |
| if (path->matched_eoff - path->matched_soff) |
| { |
| memcpy(path->match_start, (void *)(path->full + path->matched_soff), |
| path->matched_eoff - path->matched_soff); |
| } else { |
| memcpy(path->match_start, (void *)(path->full + path->matched_soff), |
| strlen((const char *)(path->full + path->matched_soff))); |
| } |
| |
| memcpy(path->match_end, |
| (void *)(path->full + path->matched_eoff), |
| strlen(path->full) - path->matched_eoff); |
| } |
| |
| if (hooks != NULL) |
| { |
| if (request->hooks == NULL) |
| { |
| request->hooks = htp__malloc_(sizeof(evhtp_hooks_t)); |
| evhtp_alloc_assert(request->hooks); |
| } |
| |
| memcpy(request->hooks, hooks, sizeof(evhtp_hooks_t)); |
| } |
| |
| request->cb = cb; |
| request->cbarg = cbarg; |
| |
| return 0; |
| } /* htp__request_set_callbacks_ */ |
| |
| static int |
| htp__request_parse_hostname_(htparser * p, const char * data, size_t len) |
| { |
| evhtp_connection_t * c = htparser_get_userdata(p); |
| evhtp_t * evhtp; |
| evhtp_t * evhtp_vhost; |
| |
| #ifndef EVHTP_DISABLE_SSL |
| if ((c->flags & EVHTP_CONN_FLAG_VHOST_VIA_SNI) && c->ssl != NULL) |
| { |
| /* use the SNI set hostname instead of the header hostname */ |
| const char * host; |
| |
| host = SSL_get_servername(c->ssl, TLSEXT_NAMETYPE_host_name); |
| |
| if ((c->cr_status = htp__hook_hostname_(c->request, host)) != EVHTP_RES_OK) |
| { |
| return -1; |
| } |
| |
| return 0; |
| } |
| #endif |
| |
| evhtp = c->htp; |
| |
| /* since this is called after htp__request_parse_path_(), which already |
| * setup callbacks for the URI, we must now attempt to find callbacks which |
| * are specific to this host. |
| */ |
| htp__lock_(evhtp); |
| { |
| if ((evhtp_vhost = htp__request_find_vhost_(evhtp, data))) |
| { |
| htp__lock_(evhtp_vhost); |
| { |
| /* if we found a match for the host, we must set the htp |
| * variables for both the connection and the request. |
| */ |
| c->htp = evhtp_vhost; |
| c->request->htp = evhtp_vhost; |
| |
| htp__request_set_callbacks_(c->request); |
| } |
| htp__unlock_(evhtp_vhost); |
| } |
| } |
| htp__unlock_(evhtp); |
| |
| if ((c->cr_status = htp__hook_hostname_(c->request, data)) != EVHTP_RES_OK) |
| { |
| return -1; |
| } |
| |
| return 0; |
| } /* htp__request_parse_hostname_ */ |
| |
| static int |
| htp__require_uri_(evhtp_connection_t * c) |
| { |
| if (c != NULL && c->request != NULL) |
| { |
| if (c->request->uri == NULL) |
| { |
| return htp__uri_new_(&c->request->uri); |
| } |
| |
| return 0; |
| } |
| |
| return -1; |
| } |
| |
| static int |
| htp__request_parse_host_(htparser * p, const char * data, size_t len) |
| { |
| evhtp_connection_t * c; |
| evhtp_authority_t * authority; |
| |
| if (evhtp_unlikely(p == NULL)) |
| { |
| return -1; |
| } |
| |
| c = htparser_get_userdata(p); |
| |
| /* all null checks are done in require_uri_, |
| * no need to check twice |
| */ |
| if (htp__require_uri_(c) == -1) |
| { |
| return -1; |
| } |
| |
| authority = c->request->uri->authority; |
| authority->hostname = htp__malloc_(len + 1); |
| evhtp_alloc_assert(authority->hostname); |
| |
| if (authority->hostname == NULL) |
| { |
| c->cr_status = EVHTP_RES_FATAL; |
| |
| return -1; |
| } |
| |
| memcpy(authority->hostname, data, len); |
| authority->hostname[len] = '\0'; |
| |
| return 0; |
| } |
| |
| static int |
| htp__request_parse_port_(htparser * p, const char * data, size_t len) |
| { |
| evhtp_connection_t * c = htparser_get_userdata(p); |
| evhtp_authority_t * authority; |
| char * endptr; |
| unsigned long port; |
| |
| if (htp__require_uri_(c) == -1) |
| { |
| return -1; |
| } |
| |
| authority = c->request->uri->authority; |
| port = strtoul(data, &endptr, 10); |
| |
| if (endptr - data != len || port > 65535) |
| { |
| c->cr_status = EVHTP_RES_FATAL; |
| |
| return -1; |
| } |
| |
| authority->port = port; |
| |
| return 0; |
| } |
| |
| static int |
| htp__request_parse_path_(htparser * p, const char * data, size_t len) |
| { |
| evhtp_connection_t * c = htparser_get_userdata(p); |
| evhtp_path_t * path; |
| |
| if (evhtp_unlikely(p == NULL || c == NULL)) |
| { |
| return -1; |
| } |
| |
| if (htp__require_uri_(c) == -1) |
| { |
| return -1; |
| } |
| |
| if (htp__path_new_(&path, data, len) == -1) |
| { |
| c->cr_status = EVHTP_RES_FATAL; |
| |
| return -1; |
| } |
| |
| c->request->uri->path = path; |
| c->request->uri->scheme = htparser_get_scheme(p); |
| c->request->method = htparser_get_method(p); |
| |
| htp__lock_(c->htp); |
| { |
| htp__request_set_callbacks_(c->request); |
| } |
| htp__unlock_(c->htp); |
| |
| if ((c->cr_status = htp__hook_path_(c->request, path)) != EVHTP_RES_OK) |
| { |
| return -1; |
| } |
| |
| return 0; |
| } /* htp__request_parse_path_ */ |
| |
| static int |
| htp__request_parse_headers_(htparser * p) |
| { |
| evhtp_connection_t * c; |
| |
| if ((c = htparser_get_userdata(p)) == NULL) |
| { |
| return -1; |
| } |
| |
| /* XXX proto should be set with htparsers on_hdrs_begin hook */ |
| |
| if (htparser_should_keep_alive(p) == 1) |
| { |
| c->request->flags |= EVHTP_REQ_FLAG_KEEPALIVE; |
| } |
| |
| c->request->proto = htp__protocol_(htparser_get_major(p), htparser_get_minor(p)); |
| c->cr_status = htp__hook_headers_(c->request, c->request->headers_in); |
| |
| if (c->cr_status != EVHTP_RES_OK) |
| { |
| return -1; |
| } |
| |
| if (c->type == evhtp_type_server |
| && c->htp->flags & EVHTP_FLAG_ENABLE_100_CONT) |
| { |
| /* only send a 100 continue response if it hasn't been disabled via |
| * evhtp_disable_100_continue. |
| */ |
| if (!evhtp_header_find(c->request->headers_in, "Expect")) |
| { |
| return 0; |
| } |
| |
| evbuffer_add_printf(bufferevent_get_output(c->bev), |
| "HTTP/%c.%c 100 Continue\r\n\r\n", |
| evhtp_modp_uchartoa(htparser_get_major(p)), |
| evhtp_modp_uchartoa(htparser_get_minor(p))); |
| } |
| |
| return 0; |
| } |
| |
| static int |
| htp__request_parse_body_(htparser * p, const char * data, size_t len) |
| { |
| evhtp_connection_t * c = htparser_get_userdata(p); |
| struct evbuffer * buf; |
| int res = 0; |
| |
| if (c->max_body_size > 0 && c->body_bytes_read + len >= c->max_body_size) |
| { |
| c->flags |= EVHTP_CONN_FLAG_ERROR; |
| c->cr_status = EVHTP_RES_DATA_TOO_LONG; |
| |
| return -1; |
| } |
| |
| if ((buf = c->scratch_buf) == NULL) |
| { |
| return -1; |
| } |
| |
| evbuffer_add(buf, data, len); |
| |
| if ((c->cr_status = htp__hook_body_(c->request, buf)) != EVHTP_RES_OK) |
| { |
| res = -1; |
| } |
| |
| if (evbuffer_get_length(buf)) |
| { |
| evbuffer_add_buffer(c->request->buffer_in, buf); |
| } |
| |
| evbuffer_drain(buf, -1); |
| |
| c->body_bytes_read += len; |
| |
| return res; |
| } |
| |
| static int |
| htp__request_parse_chunk_new_(htparser * p) |
| { |
| evhtp_connection_t * c = htparser_get_userdata(p); |
| |
| if ((c->cr_status = htp__hook_chunk_new_(c->request, |
| htparser_get_content_length(p))) != EVHTP_RES_OK) |
| { |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| htp__request_parse_chunk_fini_(htparser * p) |
| { |
| evhtp_connection_t * c = htparser_get_userdata(p); |
| |
| if ((c->cr_status = htp__hook_chunk_fini_(c->request)) != EVHTP_RES_OK) |
| { |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| htp__request_parse_chunks_fini_(htparser * p) |
| { |
| evhtp_connection_t * c = htparser_get_userdata(p); |
| |
| if ((c->cr_status = htp__hook_chunks_fini_(c->request)) != EVHTP_RES_OK) |
| { |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * @brief determines if the request body contains the query arguments. |
| * if the query is NULL and the content length of the body has never |
| * been drained, and the content-type is x-www-form-urlencoded, the |
| * function returns 1 |
| * |
| * @param req |
| * |
| * @return 1 if evhtp can use the body as the query arguments, 0 otherwise. |
| */ |
| static int |
| htp__should_parse_query_body_(evhtp_request_t * req) |
| { |
| const char * content_type; |
| |
| if (req == NULL) |
| { |
| return 0; |
| } |
| |
| if (req->uri == NULL || req->uri->query != NULL) |
| { |
| return 0; |
| } |
| |
| if (evhtp_request_content_len(req) == 0) |
| { |
| return 0; |
| } |
| |
| if (evhtp_request_content_len(req) != |
| evbuffer_get_length(req->buffer_in)) |
| { |
| return 0; |
| } |
| |
| content_type = evhtp_kv_find(req->headers_in, "content-type"); |
| |
| if (content_type == NULL) |
| { |
| return 0; |
| } |
| |
| if (strncasecmp(content_type, "application/x-www-form-urlencoded", 33)) |
| { |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| static int |
| htp__request_parse_fini_(htparser * p) |
| { |
| evhtp_connection_t * c = htparser_get_userdata(p); |
| |
| if (c->flags & EVHTP_CONN_FLAG_PAUSED) |
| { |
| return -1; |
| } |
| |
| /* check to see if we should use the body of the request as the query |
| * arguments. |
| * |
| * htp__should_parse_query_body_ does all the proper null checks. |
| */ |
| if (htp__should_parse_query_body_(c->request) == 1) |
| { |
| const char * body; |
| size_t body_len; |
| evhtp_uri_t * uri; |
| struct evbuffer * buf_in; |
| |
| uri = c->request->uri; |
| buf_in = c->request->buffer_in; |
| |
| body_len = evbuffer_get_length(buf_in); |
| body = (const char *)evbuffer_pullup(buf_in, body_len); |
| |
| uri->query_raw = htp__calloc_(body_len + 1, 1); |
| evhtp_alloc_assert(uri->query_raw); |
| |
| memcpy(uri->query_raw, body, body_len); |
| |
| uri->query = evhtp_parse_query(body, body_len); |
| } |
| |
| |
| /* |
| * XXX c->request should never be NULL, but we have found some path of |
| * execution where this actually happens. We will check for now, but the bug |
| * path needs to be tracked down. |
| * |
| */ |
| if (c->request && c->request->cb) |
| { |
| (c->request->cb)(c->request, c->request->cbarg); |
| } |
| |
| if (c->flags & EVHTP_CONN_FLAG_PAUSED) |
| { |
| return -1; |
| } |
| |
| return 0; |
| } /* htp__request_parse_fini_ */ |
| |
| static int |
| htp__create_headers_(evhtp_header_t * header, void * arg) |
| { |
| struct evbuffer * buf = arg; |
| |
| evbuffer_expand(buf, header->klen + 2 + header->vlen + 2); |
| evbuffer_add(buf, header->key, header->klen); |
| evbuffer_add(buf, ": ", 2); |
| evbuffer_add(buf, header->val, header->vlen); |
| evbuffer_add(buf, "\r\n", 2); |
| |
| return 0; |
| } |
| |
| static struct evbuffer * |
| htp__create_reply_(evhtp_request_t * request, evhtp_res code) { |
| struct evbuffer * buf; |
| const char * content_type; |
| char res_buf[2048]; |
| int sres; |
| size_t out_len; |
| unsigned char major; |
| unsigned char minor; |
| char out_buf[64]; |
| |
| evhtp_assert(request |
| && request->headers_out |
| && request->buffer_out |
| && request->conn |
| && request->rc_parser); |
| |
| content_type = evhtp_header_find(request->headers_out, "Content-Type"); |
| out_len = evbuffer_get_length(request->buffer_out); |
| |
| if ((buf = request->rc_scratch) == NULL) |
| { |
| request->rc_scratch = evbuffer_new(); |
| evhtp_alloc_assert(request->rc_scratch); |
| } |
| |
| evbuffer_drain(buf, -1); |
| |
| if (htparser_get_multipart(request->rc_parser) == 1) |
| { |
| goto check_proto; |
| } |
| |
| if (out_len && !(request->flags & EVHTP_REQ_FLAG_CHUNKED)) |
| { |
| /* add extra headers (like content-length/type) if not already present */ |
| |
| if (!evhtp_header_find(request->headers_out, "Content-Length")) |
| { |
| /* convert the buffer_out length to a string and set |
| * and add the new Content-Length header. |
| */ |
| evhtp_modp_sizetoa(out_len, out_buf); |
| |
| evhtp_headers_add_header(request->headers_out, |
| evhtp_header_new("Content-Length", out_buf, 0, 1)); |
| } |
| } |
| check_proto: |
| /* add the proper keep-alive type headers based on http version */ |
| switch (request->proto) { |
| case EVHTP_PROTO_11: |
| if (!(request->flags & EVHTP_REQ_FLAG_KEEPALIVE)) |
| { |
| /* protocol is HTTP/1.1 but client wanted to close */ |
| evhtp_headers_add_header(request->headers_out, |
| evhtp_header_new("Connection", "close", 0, 0)); |
| } |
| |
| if (!evhtp_header_find(request->headers_out, "Content-Length")) |
| { |
| evhtp_headers_add_header(request->headers_out, |
| evhtp_header_new("Content-Length", "0", 0, 0)); |
| } |
| |
| break; |
| case EVHTP_PROTO_10: |
| if (request->flags & EVHTP_REQ_FLAG_KEEPALIVE) |
| { |
| /* protocol is HTTP/1.0 and clients wants to keep established */ |
| evhtp_headers_add_header(request->headers_out, |
| evhtp_header_new("Connection", "keep-alive", 0, 0)); |
| } |
| break; |
| default: |
| /* this sometimes happens when a response is made but paused before |
| * the method has been parsed */ |
| htparser_set_major(request->rc_parser, 1); |
| htparser_set_minor(request->rc_parser, 0); |
| break; |
| } /* switch */ |
| |
| |
| if (!content_type) |
| { |
| evhtp_headers_add_header(request->headers_out, |
| evhtp_header_new("Content-Type", "text/plain", 0, 0)); |
| } |
| |
| /* attempt to add the status line into a temporary buffer and then use |
| * evbuffer_add(). Using plain old snprintf() will be faster than |
| * evbuffer_add_printf(). If the snprintf() fails, which it rarely should, |
| * we fallback to using evbuffer_add_printf(). |
| */ |
| |
| major = evhtp_modp_uchartoa(htparser_get_major(request->rc_parser)); |
| minor = evhtp_modp_uchartoa(htparser_get_minor(request->rc_parser)); |
| |
| evhtp_modp_u32toa((uint32_t)code, out_buf); |
| |
| sres = snprintf(res_buf, sizeof(res_buf), "HTTP/%c.%c %s %s\r\n", |
| major, minor, out_buf, status_code_to_str(code)); |
| |
| if (sres >= sizeof(res_buf) || sres < 0) |
| { |
| /* failed to fit the whole thing in the res_buf, so just fallback to |
| * using evbuffer_add_printf(). |
| */ |
| evbuffer_add_printf(buf, "HTTP/%c.%c %d %s\r\n", |
| major, minor, |
| code, status_code_to_str(code)); |
| } else { |
| /* copy the res_buf using evbuffer_add() instead of add_printf() */ |
| evbuffer_add(buf, res_buf, sres); |
| } |
| |
| |
| evhtp_headers_for_each(request->headers_out, htp__create_headers_, buf); |
| evbuffer_add(buf, "\r\n", 2); |
| |
| if (evbuffer_get_length(request->buffer_out)) |
| { |
| evbuffer_add_buffer(buf, request->buffer_out); |
| } |
| |
| return buf; |
| } /* htp__create_reply_ */ |
| |
| /** |
| * @brief callback definitions for request processing from libhtparse |
| */ |
| static htparse_hooks request_psets = { |
| .on_msg_begin = htp__request_parse_start_, |
| .method = NULL, |
| .scheme = NULL, |
| .host = htp__request_parse_host_, |
| .port = htp__request_parse_port_, |
| .path = htp__request_parse_path_, |
| .args = htp__request_parse_args_, |
| .uri = NULL, |
| .on_hdrs_begin = htp__request_parse_headers_start_, |
| .hdr_key = htp__request_parse_header_key_, |
| .hdr_val = htp__request_parse_header_val_, |
| .hostname = htp__request_parse_hostname_, |
| .on_hdrs_complete = htp__request_parse_headers_, |
| .on_new_chunk = htp__request_parse_chunk_new_, |
| .on_chunk_complete = htp__request_parse_chunk_fini_, |
| .on_chunks_complete = htp__request_parse_chunks_fini_, |
| .body = htp__request_parse_body_, |
| .on_msg_complete = htp__request_parse_fini_ |
| }; |
| |
| static void |
| htp__connection_readcb_(struct bufferevent * bev, void * arg) |
| { |
| evhtp_connection_t * c = arg; |
| void * buf; |
| size_t nread; |
| size_t avail; |
| |
| if (evhtp_unlikely(bev == NULL)) |
| { |
| return; |
| } |
| |
| avail = evbuffer_get_length(bufferevent_get_input(bev)); |
| |
| if (evhtp_unlikely(avail == 0)) |
| { |
| return; |
| } |
| |
| if (c->request) |
| { |
| c->cr_status = EVHTP_RES_OK; |
| } |
| |
| if (c->flags & EVHTP_CONN_FLAG_PAUSED) |
| { |
| return; |
| } |
| |
| buf = evbuffer_pullup(bufferevent_get_input(bev), avail); |
| |
| evhtp_assert(buf != NULL); |
| evhtp_assert(c->parser != NULL); |
| |
| nread = htparser_run(c->parser, &request_psets, (const char *)buf, avail); |
| |
| log_debug("nread = %zu", nread); |
| |
| if (!(c->flags & EVHTP_CONN_FLAG_OWNER)) |
| { |
| /* |
| * someone has taken the ownership of this connection, we still need to |
| * drain the input buffer that had been read up to this point. |
| */ |
| |
| log_debug("EVHTP_CONN_FLAG_OWNER set, removing contexts"); |
| |
| evbuffer_drain(bufferevent_get_input(bev), nread); |
| evhtp_connection_free(c); |
| |
| return; |
| } |
| |
| if (c->request) |
| { |
| switch (c->cr_status) { |
| case EVHTP_RES_DATA_TOO_LONG: |
| htp__hook_connection_error_(c, -1); |
| evhtp_connection_free(c); |
| |
| return; |
| default: |
| break; |
| } |
| } |
| |
| evbuffer_drain(bufferevent_get_input(bev), nread); |
| |
| if (c->request && c->cr_status == EVHTP_RES_PAUSE) |
| { |
| log_debug("Pausing connection"); |
| |
| evhtp_request_pause(c->request); |
| } else if (htparser_get_error(c->parser) != htparse_error_none) |
| { |
| log_debug("error %d, freeing connection", |
| htparser_get_error(c->parser)); |
| |
| evhtp_connection_free(c); |
| } else if (nread < avail) |
| { |
| /* we still have more data to read (piped request probably) */ |
| log_debug("Reading more data via resumption"); |
| |
| evhtp_connection_resume(c); |
| } |
| } /* htp__connection_readcb_ */ |
| |
| static void |
| htp__connection_writecb_(struct bufferevent * bev, void * arg) |
| { |
| evhtp_connection_t * conn; |
| uint64_t keepalive_max; |
| const char * errstr; |
| |
| evhtp_assert(bev != NULL); |
| |
| if (evhtp_unlikely(arg == NULL)) |
| { |
| log_error("No data associated with the bufferevent %p", bev); |
| |
| bufferevent_free(bev); |
| return; |
| } |
| |
| errstr = NULL; |
| conn = (evhtp_connection_t *)arg; |
| |
| do { |
| if (evhtp_unlikely(conn->request == NULL)) |
| { |
| errstr = "no request associated with connection"; |
| break; |
| } |
| |
| if (evhtp_unlikely(conn->parser == NULL)) |
| { |
| errstr = "no parser registered with connection"; |
| break; |
| } |
| |
| if (evhtp_likely(conn->type == evhtp_type_server)) |
| { |
| if (evhtp_unlikely(conn->htp == NULL)) |
| { |
| errstr = "no context associated with the server-connection"; |
| break; |
| } |
| |
| keepalive_max = conn->htp->max_keepalive_requests; |
| } else { |
| keepalive_max = 0; |
| } |
| } while (0); |
| |
| if (evhtp_unlikely(errstr != NULL)) |
| { |
| log_error("shutting down connection: %s", errstr); |
| |
| evhtp_safe_free(conn, evhtp_connection_free); |
| return; |
| } |
| |
| /* run user-hook for on_write callback before further analysis */ |
| htp__hook_connection_write_(conn); |
| |
| /* connection is in a paused state, no further processing yet */ |
| if ((conn->flags & EVHTP_CONN_FLAG_PAUSED)) |
| { |
| return; |
| } |
| |
| if (conn->flags & EVHTP_CONN_FLAG_WAITING) |
| { |
| HTP_FLAG_OFF(conn, EVHTP_CONN_FLAG_WAITING); |
| |
| bufferevent_enable(bev, EV_READ); |
| |
| if (evbuffer_get_length(bufferevent_get_input(bev))) |
| { |
| htp__connection_readcb_(bev, arg); |
| } |
| |
| return; |
| } |
| |
| /* if the connection is not finished, OR there is data ready to output |
| * (can only happen if a user-defined connection_write hook added data |
| * manually, since this is called only when all data has been flushed) |
| * just return and wait. |
| */ |
| if (!(conn->request->flags & EVHTP_REQ_FLAG_FINISHED) |
| || evbuffer_get_length(bufferevent_get_output(bev))) |
| { |
| return; |
| } |
| |
| /* |
| * if there is a set maximum number of keepalive requests configured, check |
| * to make sure we are not over it. If we have gone over the max we set the |
| * keepalive bit to 0, thus closing the connection. |
| */ |
| if (keepalive_max > 0) |
| { |
| conn->num_requests += 1; |
| |
| if (conn->num_requests >= keepalive_max) |
| { |
| HTP_FLAG_OFF(conn->request, EVHTP_REQ_FLAG_KEEPALIVE); |
| } |
| } |
| |
| if (conn->request->flags & EVHTP_REQ_FLAG_KEEPALIVE) |
| { |
| htp_type type; |
| |
| /* free up the current request, set it to NULL, making |
| * way for the next request. |
| */ |
| evhtp_safe_free(conn->request, htp__request_free_); |
| |
| /* since the request is keep-alive, assure that the connection |
| * is aware of the same. |
| */ |
| HTP_FLAG_ON(conn, EVHTP_CONN_FLAG_KEEPALIVE); |
| |
| conn->body_bytes_read = 0; |
| |
| if (conn->type == evhtp_type_server) |
| { |
| if (conn->htp->parent != NULL |
| && !(conn->flags & EVHTP_CONN_FLAG_VHOST_VIA_SNI)) |
| { |
| /* this request was served by a virtual host evhtp_t structure |
| * which was *NOT* found via SSL SNI lookup. In this case we want to |
| * reset our connections evhtp_t structure back to the original so |
| * that subsequent requests can have a different 'Host' header. |
| */ |
| conn->htp = conn->htp->parent; |
| } |
| } |
| |
| switch (conn->type) { |
| case evhtp_type_client: |
| type = htp_type_response; |
| break; |
| case evhtp_type_server: |
| type = htp_type_request; |
| break; |
| default: |
| log_error("Unknown connection type"); |
| |
| evhtp_safe_free(conn, evhtp_connection_free); |
| return; |
| } |
| |
| htparser_init(conn->parser, type); |
| htparser_set_userdata(conn->parser, conn); |
| |
| return; |
| } else { |
| evhtp_safe_free(conn, evhtp_connection_free); |
| |
| return; |
| } |
| |
| return; |
| } /* htp__connection_writecb_ */ |
| |
| static void |
| htp__connection_eventcb_(struct bufferevent * bev, short events, void * arg) |
| { |
| evhtp_connection_t * c = arg; |
| |
| log_debug("%p %p eventcb %s%s%s%s", arg, (void *)bev, |
| events & BEV_EVENT_CONNECTED ? "connected" : "", |
| events & BEV_EVENT_ERROR ? "error" : "", |
| events & BEV_EVENT_TIMEOUT ? "timeout" : "", |
| events & BEV_EVENT_EOF ? "eof" : ""); |
| |
| if (c->hooks && c->hooks->on_event) |
| { |
| (c->hooks->on_event)(c, events, c->hooks->on_event_arg); |
| } |
| |
| if ((events & BEV_EVENT_CONNECTED)) |
| { |
| log_debug("CONNECTED"); |
| |
| if (evhtp_likely(c->type == evhtp_type_client)) |
| { |
| HTP_FLAG_ON(c, EVHTP_CONN_FLAG_CONNECTED); |
| |
| bufferevent_setcb(bev, |
| htp__connection_readcb_, |
| htp__connection_writecb_, |
| htp__connection_eventcb_, c); |
| } |
| |
| return; |
| } |
| |
| #ifndef EVHTP_DISABLE_SSL |
| if (c->ssl && !(events & BEV_EVENT_EOF)) |
| { |
| #ifdef EVHTP_DEBUG |
| unsigned long sslerr; |
| |
| while ((sslerr = bufferevent_get_openssl_error(bev))) { |
| log_error("SSL ERROR %lu:%i:%s:%i:%s:%i:%s", |
| sslerr, |
| ERR_GET_REASON(sslerr), |
| ERR_reason_error_string(sslerr), |
| ERR_GET_LIB(sslerr), |
| ERR_lib_error_string(sslerr), |
| ERR_GET_FUNC(sslerr), |
| ERR_func_error_string(sslerr)); |
| } |
| #endif |
| |
| /* XXX need to do better error handling for SSL specific errors */ |
| HTP_FLAG_ON(c, EVHTP_CONN_FLAG_ERROR); |
| |
| if (c->request) |
| { |
| HTP_FLAG_ON(c->request, EVHTP_REQ_FLAG_ERROR); |
| } |
| } |
| #endif |
| |
| if (events == (BEV_EVENT_EOF | BEV_EVENT_READING)) |
| { |
| if (errno == EAGAIN) |
| { |
| /* libevent will sometimes recv again when it's not actually ready, |
| * this results in a 0 return value, and errno will be set to EAGAIN |
| * (try again). This does not mean there is a hard socket error, but |
| * simply needs to be read again. |
| * |
| * but libevent will disable the read side of the bufferevent |
| * anyway, so we must re-enable it. |
| */ |
| bufferevent_enable(bev, EV_READ); |
| errno = 0; |
| |
| return; |
| } |
| } |
| |
| /* set the error mask */ |
| HTP_FLAG_ON(c, EVHTP_CONN_FLAG_ERROR); |
| |
| /* unset connected flag */ |
| HTP_FLAG_OFF(c, EVHTP_CONN_FLAG_CONNECTED); |
| |
| htp__hook_connection_error_(c, events); |
| |
| if (c->flags & EVHTP_CONN_FLAG_PAUSED) |
| { |
| /* we are currently paused, so we don't want to free just yet, let's |
| * wait till the next loop. |
| */ |
| HTP_FLAG_ON(c, EVHTP_CONN_FLAG_FREE_CONN); |
| } else { |
| evhtp_connection_free((evhtp_connection_t *)arg); |
| } |
| } /* htp__connection_eventcb_ */ |
| |
| static void |
| htp__connection_resumecb_(int fd, short events, void * arg) |
| { |
| evhtp_connection_t * c = arg; |
| |
| /* unset the pause flag */ |
| HTP_FLAG_OFF(c, EVHTP_CONN_FLAG_PAUSED); |
| |
| if (c->request) |
| { |
| c->cr_status = EVHTP_RES_OK; |
| } |
| |
| if (c->flags & EVHTP_CONN_FLAG_FREE_CONN) |
| { |
| evhtp_connection_free(c); |
| |
| return; |
| } |
| |
| /* XXX this is a hack to show a potential fix for issues/86, the main indea |
| * is that you call resume AFTER you have sent the reply (not BEFORE). |
| * |
| * When it has been decided this is a proper fix, the pause bit should be |
| * changed to a state-type flag. |
| */ |
| |
| if (evbuffer_get_length(bufferevent_get_output(c->bev))) |
| { |
| HTP_FLAG_ON(c, EVHTP_CONN_FLAG_WAITING); |
| |
| bufferevent_enable(c->bev, EV_WRITE); |
| } else { |
| bufferevent_enable(c->bev, EV_READ | EV_WRITE); |
| htp__connection_readcb_(c->bev, c); |
| } |
| } |
| |
| static int |
| htp__run_pre_accept_(evhtp_t * htp, evhtp_connection_t * conn) |
| { |
| void * args; |
| evhtp_res res; |
| |
| if (evhtp_likely(htp->defaults.pre_accept == NULL)) |
| { |
| return 0; |
| } |
| |
| args = htp->defaults.pre_accept_cbarg; |
| res = htp->defaults.pre_accept(conn, args); |
| |
| if (res != EVHTP_RES_OK) |
| { |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static int |
| htp__connection_accept_(struct event_base * evbase, evhtp_connection_t * connection) |
| { |
| struct timeval * c_recv_timeo; |
| struct timeval * c_send_timeo; |
| |
| if (htp__run_pre_accept_(connection->htp, connection) < 0) |
| { |
| evutil_closesocket(connection->sock); |
| |
| return -1; |
| } |
| |
| #ifndef EVHTP_DISABLE_SSL |
| if (connection->htp->ssl_ctx != NULL) |
| { |
| connection->ssl = SSL_new(connection->htp->ssl_ctx); |
| connection->bev = bufferevent_openssl_socket_new(evbase, |
| connection->sock, |
| connection->ssl, |
| BUFFEREVENT_SSL_ACCEPTING, |
| connection->htp->bev_flags); |
| SSL_set_app_data(connection->ssl, connection); |
| goto end; |
| } |
| #endif |
| |
| connection->bev = bufferevent_socket_new(evbase, |
| connection->sock, |
| connection->htp->bev_flags); |
| |
| log_debug("enter sock=%d\n", connection->sock); |
| |
| #ifndef EVHTP_DISABLE_SSL |
| end: |
| #endif |
| |
| if (connection->recv_timeo.tv_sec || connection->recv_timeo.tv_usec) |
| { |
| c_recv_timeo = &connection->recv_timeo; |
| } else if (connection->htp->recv_timeo.tv_sec || |
| connection->htp->recv_timeo.tv_usec) |
| { |
| c_recv_timeo = &connection->htp->recv_timeo; |
| } else { |
| c_recv_timeo = NULL; |
| } |
| |
| if (connection->send_timeo.tv_sec || connection->send_timeo.tv_usec) |
| { |
| c_send_timeo = &connection->send_timeo; |
| } else if (connection->htp->send_timeo.tv_sec || |
| connection->htp->send_timeo.tv_usec) |
| { |
| c_send_timeo = &connection->htp->send_timeo; |
| } else { |
| c_send_timeo = NULL; |
| } |
| |
| evhtp_connection_set_timeouts(connection, c_recv_timeo, c_send_timeo); |
| |
| connection->resume_ev = event_new(evbase, -1, EV_READ | EV_PERSIST, |
| htp__connection_resumecb_, connection); |
| event_add(connection->resume_ev, NULL); |
| |
| bufferevent_enable(connection->bev, EV_READ); |
| bufferevent_setcb(connection->bev, |
| htp__connection_readcb_, |
| htp__connection_writecb_, |
| htp__connection_eventcb_, connection); |
| |
| return 0; |
| } /* htp__connection_accept_ */ |
| |
| static void |
| htp__default_request_cb_(evhtp_request_t * request, void * arg) |
| { |
| evhtp_headers_add_header(request->headers_out, |
| evhtp_header_new("Content-Length", "0", 0, 0)); |
| evhtp_send_reply(request, EVHTP_RES_NOTFOUND); |
| } |
| |
| static evhtp_connection_t * |
| htp__connection_new_(evhtp_t * htp, evutil_socket_t sock, evhtp_type type) |
| { |
| evhtp_connection_t * connection; |
| htp_type ptype; |
| |
| switch (type) { |
| case evhtp_type_client: |
| ptype = htp_type_response; |
| break; |
| case evhtp_type_server: |
| ptype = htp_type_request; |
| break; |
| default: |
| return NULL; |
| } |
| |
| connection = htp__calloc_(sizeof(evhtp_connection_t), 1); |
| evhtp_alloc_assert(connection); |
| |
| connection->scratch_buf = evbuffer_new(); |
| evhtp_alloc_assert(connection->scratch_buf); |
| |
| connection->flags = EVHTP_CONN_FLAG_OWNER; |
| connection->sock = sock; |
| connection->htp = htp; |
| connection->type = type; |
| connection->parser = htparser_new(); |
| |
| evhtp_alloc_assert(connection->parser); |
| |
| htparser_init(connection->parser, ptype); |
| htparser_set_userdata(connection->parser, connection); |
| |
| return connection; |
| } /* htp__connection_new_ */ |
| |
| #ifdef LIBEVENT_HAS_SHUTDOWN |
| #ifndef EVHTP_DISABLE_SSL |
| static void |
| htp__shutdown_eventcb_(struct bufferevent * bev, short events, void * arg) |
| { |
| } |
| |
| #endif |
| #endif |
| |
| static int |
| htp__run_post_accept_(evhtp_t * htp, evhtp_connection_t * connection) |
| { |
| void * args; |
| evhtp_res res; |
| |
| if (evhtp_likely(htp->defaults.post_accept == NULL)) |
| { |
| return 0; |
| } |
| |
| args = htp->defaults.post_accept_cbarg; |
| res = htp->defaults.post_accept(connection, args); |
| |
| if (res != EVHTP_RES_OK) |
| { |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| #ifndef EVHTP_DISABLE_EVTHR |
| static void |
| htp__run_in_thread_(evthr_t * thr, void * arg, void * shared) |
| { |
| evhtp_t * htp = shared; |
| evhtp_connection_t * connection = arg; |
| |
| connection->evbase = evthr_get_base(thr); |
| connection->thread = thr; |
| |
| if (htp__connection_accept_(connection->evbase, connection) < 0) |
| { |
| evhtp_connection_free(connection); |
| |
| return; |
| } |
| |
| if (htp__run_post_accept_(htp, connection) < 0) |
| { |
| evhtp_connection_free(connection); |
| |
| return; |
| } |
| } |
| |
| #endif |
| |
| static void |
| htp__accept_cb_(struct evconnlistener * serv, int fd, struct sockaddr * s, int sl, void * arg) |
| { |
| evhtp_t * htp = arg; |
| evhtp_connection_t * connection; |
| |
| evhtp_assert(htp && serv && serv && s); |
| |
| connection = htp__connection_new_(htp, fd, evhtp_type_server); |
| |
| if (evhtp_unlikely(connection == NULL)) |
| { |
| return; |
| } |
| |
| log_debug("fd = %d, conn = %p", fd, connection); |
| |
| connection->saddr = htp__malloc_(sl); |
| evhtp_alloc_assert(connection->saddr); |
| |
| memcpy(connection->saddr, s, sl); |
| |
| #ifndef EVHTP_DISABLE_EVTHR |
| if (htp->thr_pool != NULL) |
| { |
| if (evthr_pool_defer(htp->thr_pool, |
| htp__run_in_thread_, connection) != EVTHR_RES_OK) |
| { |
| evutil_closesocket(connection->sock); |
| evhtp_connection_free(connection); |
| |
| return; |
| } |
| |
| return; |
| } |
| #endif |
| connection->evbase = htp->evbase; |
| |
| if (htp__connection_accept_(htp->evbase, connection) == -1) |
| { |
| evhtp_connection_free(connection); |
| return; |
| } |
| |
| if (htp__run_post_accept_(htp, connection) == -1) |
| { |
| evhtp_connection_free(connection); |
| return; |
| } |
| } /* htp__accept_cb_ */ |
| |
| #ifndef EVHTP_DISABLE_SSL |
| #ifndef EVHTP_DISABLE_EVTHR |
| static unsigned long |
| htp__ssl_get_thread_id_(void) |
| { |
| #ifndef WIN32 |
| |
| return (unsigned long)pthread_self(); |
| #else |
| |
| return (unsigned long)(pthread_self().p); |
| #endif |
| } |
| |
| static void |
| htp__ssl_thread_lock_(int mode, int type, const char * file, int line) |
| { |
| if (type < ssl_num_locks) |
| { |
| if (mode & CRYPTO_LOCK) |
| { |
| pthread_mutex_lock(&(ssl_locks[type])); |
| } else { |
| pthread_mutex_unlock(&(ssl_locks[type])); |
| } |
| } |
| } |
| |
| #endif |
| static void |
| htp__ssl_delete_scache_ent_(evhtp_ssl_ctx_t * ctx, evhtp_ssl_sess_t * sess) |
| { |
| evhtp_t * htp; |
| evhtp_ssl_cfg_t * cfg; |
| evhtp_ssl_data_t * sid; |
| unsigned int slen; |
| |
| htp = (evhtp_t *)SSL_CTX_get_app_data(ctx); |
| cfg = htp->ssl_cfg; |
| sid = (evhtp_ssl_data_t *)SSL_SESSION_get_id(sess, &slen); |
| |
| if (cfg->scache_del) |
| { |
| (cfg->scache_del)(htp, sid, slen); |
| } |
| } |
| |
| static int |
| htp__ssl_add_scache_ent_(evhtp_ssl_t * ssl, evhtp_ssl_sess_t * sess) |
| { |
| evhtp_connection_t * connection; |
| evhtp_ssl_cfg_t * cfg; |
| evhtp_ssl_data_t * sid; |
| int slen; |
| |
| connection = (evhtp_connection_t *)SSL_get_app_data(ssl); |
| if (connection->htp == NULL) |
| { |
| return 0; /* We cannot get the ssl_cfg */ |
| } |
| |
| cfg = connection->htp->ssl_cfg; |
| sid = (evhtp_ssl_data_t *)SSL_SESSION_get_id(sess, &slen); |
| |
| SSL_set_timeout(sess, cfg->scache_timeout); |
| |
| if (cfg->scache_add) |
| { |
| return (cfg->scache_add)(connection, sid, slen, sess); |
| } |
| |
| return 0; |
| } |
| |
| static evhtp_ssl_sess_t * |
| htp__ssl_get_scache_ent_(evhtp_ssl_t * ssl, evhtp_ssl_data_t * sid, int sid_len, int * copy) |
| { |
| evhtp_connection_t * connection; |
| evhtp_ssl_cfg_t * cfg; |
| evhtp_ssl_sess_t * sess; |
| |
| connection = (evhtp_connection_t * )SSL_get_app_data(ssl); |
| |
| if (connection->htp == NULL) |
| { |
| return NULL; /* We have no way of getting ssl_cfg */ |
| } |
| cfg = connection->htp->ssl_cfg; |
| sess = NULL; |
| |
| if (cfg->scache_get) |
| { |
| sess = (cfg->scache_get)(connection, sid, sid_len); |
|