static liHandlerResult vhost_map(liVRequest *vr, gpointer param, gpointer *context) { liValue *v; vhost_map_data *md = param; gboolean debug = _OPTION(vr, md->plugin, 0).boolean; UNUSED(context); v = g_hash_table_lookup(md->hash, vr->request.uri.host); if (NULL != v) { if (debug) { VR_DEBUG(vr, "vhost_map: host %s found in hashtable", vr->request.uri.host->str); } li_action_enter(vr, v->data.val_action.action); } else if (NULL != md->default_action) { if (debug) { VR_DEBUG(vr, "vhost_map: host %s not found in hashtable, executing default action", vr->request.uri.host->str); } li_action_enter(vr, md->default_action->data.val_action.action); } else { if (debug) { VR_DEBUG(vr, "vhost_map: neither host %s found in hashtable nor default action specified, doing nothing", vr->request.uri.host->str); } } return LI_HANDLER_GO_ON; }
static liHandlerResult vhost_map_regex(liVRequest *vr, gpointer param, gpointer *context) { guint i; vhost_map_regex_data *mrd = param; GArray *list = mrd->list; gboolean debug = _OPTION(vr, mrd->plugin, 0).boolean; liValue *v = NULL; vhost_map_regex_entry *entry = NULL; UNUSED(context); /* loop through all rules to find a match */ for (i = 0; i < list->len; i++) { entry = &g_array_index(list, vhost_map_regex_entry, i); if (!g_regex_match(entry->regex, vr->request.uri.host->str, 0, NULL)) continue; v = entry->action; break; } if (NULL != v) { if (debug) { VR_DEBUG(vr, "vhost_map_regex: host %s matches pattern \"%s\"", vr->request.uri.host->str, g_regex_get_pattern(entry->regex)); } li_action_enter(vr, v->data.val_action.action); } else if (NULL != mrd->default_action) { if (debug) { VR_DEBUG(vr, "vhost_map_regex: host %s didn't match, executing default action", vr->request.uri.host->str); } li_action_enter(vr, mrd->default_action->data.val_action.action); } else { if (debug) { VR_DEBUG(vr, "vhost_map_regex: neither did %s match nor default action specified, doing nothing", vr->request.uri.host->str); } } return LI_HANDLER_GO_ON; }
static liHandlerResult lua_handle(liVRequest *vr, gpointer param, gpointer *context) { lua_config *conf = (lua_config*) param; lua_worker_config *wc; gboolean timeout = FALSE; liHandlerResult res; UNUSED(context); wc = &conf->worker_config[vr->wrk->ndx]; if (wc->act) timeout = (conf->ttl > 0 && wc->ts_loaded + conf->ttl < li_cur_ts(vr->wrk)); if (!wc->act || timeout) { int err; struct stat st; time_t last_load; res = li_stat_cache_get(vr, conf->filename, &st, &err, NULL); switch (res) { case LI_HANDLER_ERROR: VR_ERROR(vr, "lua.handler: couldn't stat file '%s': %s", conf->filename->str, g_strerror(err)); return LI_HANDLER_ERROR; case LI_HANDLER_WAIT_FOR_EVENT: return LI_HANDLER_WAIT_FOR_EVENT; default: break; } last_load = wc->ts_loaded; wc->ts_loaded = li_cur_ts(vr->wrk); if (timeout && st.st_mtime <= last_load) { goto loaded; } li_action_release(vr->wrk->srv, wc->act); wc->act = NULL; if (!li_config_lua_load(&vr->wrk->LL, vr->wrk->srv, vr->wrk, conf->filename->str, &wc->act, FALSE, conf->args) || !wc->act) { VR_ERROR(vr, "lua.handler: couldn't load '%s'", conf->filename->str); return LI_HANDLER_ERROR; } } loaded: li_action_enter(vr, wc->act); return LI_HANDLER_GO_ON; }
static int lua_vrequest_enter_action(lua_State *L) { liVRequest *vr; liAction *act; if (lua_gettop(L) != 2) { lua_pushstring(L, "incorrect number of arguments"); lua_error(L); } vr = li_lua_get_vrequest(L, 1); act = li_lua_get_action(L, 2); if (!vr || !act) { lua_pushstring(L, "wrong arguments"); lua_error(L); } li_action_enter(vr, act); return 0; }
/* tcp/ssl -> http "parser" */ static void _connection_http_in_cb(liStream *stream, liStreamEvent event) { liConnection *con = LI_CONTAINER_OF(stream, liConnection, in); liChunkQueue *raw_in, *in; liVRequest *vr = con->mainvr; switch (event) { case LI_STREAM_NEW_DATA: /* handle below */ break; case LI_STREAM_DISCONNECTED_SOURCE: connection_close(con); return; case LI_STREAM_DESTROY: con->info.req = NULL; li_job_later(&con->wrk->loop.jobqueue, &con->job_reset); return; default: return; } if (NULL == stream->source) return; /* raw_in never gets closed normally - if we receive EOF from the client it means it cancelled the request */ raw_in = stream->source->out; if (raw_in->is_closed) { connection_close(con); return; } /* always close "in" after request body end. reopen it on keep-alive */ in = con->in.out; if (0 == raw_in->length) return; /* no (new) data */ if (LI_CON_STATE_UPGRADED == con->state) { li_chunkqueue_steal_all(in, raw_in); li_stream_notify(stream); return; } if (con->state == LI_CON_STATE_KEEP_ALIVE) { /* stop keep alive timeout watchers */ if (con->keep_alive_data.link) { g_queue_delete_link(&con->wrk->keep_alive_queue, con->keep_alive_data.link); con->keep_alive_data.link = NULL; } con->keep_alive_data.timeout = 0; li_event_stop(&con->keep_alive_data.watcher); con->keep_alive_requests++; /* disable keep alive if limit is reached */ if (con->keep_alive_requests == CORE_OPTION(LI_CORE_OPTION_MAX_KEEP_ALIVE_REQUESTS).number) con->info.keep_alive = FALSE; /* reopen stream for request body */ li_chunkqueue_reset(in); /* reset stuff from keep-alive and record timestamp */ li_vrequest_start(con->mainvr); con->state = LI_CON_STATE_READ_REQUEST_HEADER; /* put back in io timeout queue */ li_connection_update_io_wait(con); } else if (con->state == LI_CON_STATE_REQUEST_START) { con->state = LI_CON_STATE_READ_REQUEST_HEADER; li_connection_update_io_wait(con); } if (con->state == LI_CON_STATE_READ_REQUEST_HEADER) { liHandlerResult res; if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "%s", "reading request header"); } res = li_http_request_parse(vr, &con->req_parser_ctx); /* max uri length 8 kilobytes */ /* TODO: check this and similar in request_parse and response_parse */ if (vr->request.uri.raw->len > 8*1024) { VR_INFO(vr, "request uri too large. limit: 8kb, received: %s", li_counter_format(vr->request.uri.raw->len, COUNTER_BYTES, vr->wrk->tmp_str)->str ); con->info.keep_alive = FALSE; vr->response.http_status = 414; /* Request-URI Too Large */ con->state = LI_CON_STATE_WRITE; li_connection_update_io_wait(con); li_stream_again(&con->out); return; } switch(res) { case LI_HANDLER_GO_ON: break; /* go on */ case LI_HANDLER_WAIT_FOR_EVENT: return; case LI_HANDLER_ERROR: case LI_HANDLER_COMEBACK: /* unexpected */ /* unparsable header */ if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "%s", "parsing header failed"); } con->wrk->stats.requests++; con->info.keep_alive = FALSE; /* set status 400 if not already set to e.g. 413 */ if (vr->response.http_status == 0) vr->response.http_status = 400; con->state = LI_CON_STATE_WRITE; li_connection_update_io_wait(con); li_stream_again(&con->out); return; } con->wrk->stats.requests++; /* headers ready */ if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "%s", "validating request header"); } if (!li_request_validate_header(con)) { /* set status 400 if not already set */ if (vr->response.http_status == 0) vr->response.http_status = 400; con->state = LI_CON_STATE_WRITE; con->info.keep_alive = FALSE; li_connection_update_io_wait(con); li_stream_again(&con->out); return; } /* When does a client ask for 100 Continue? probably not while trying to ddos us * as post content probably goes to a dynamic backend anyway, we don't * care about the rare cases we could determine that we don't want a request at all * before sending it to a backend - so just send the stupid header */ if (con->expect_100_cont) { if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "%s", "send 100 Continue"); } li_chunkqueue_append_mem(con->out.out, CONST_STR_LEN("HTTP/1.1 100 Continue\r\n\r\n")); con->expect_100_cont = FALSE; li_stream_notify(&con->out); } con->state = LI_CON_STATE_HANDLE_MAINVR; li_connection_update_io_wait(con); li_action_enter(vr, con->srv->mainaction); li_vrequest_handle_request_headers(vr); } if (con->state != LI_CON_STATE_READ_REQUEST_HEADER && !in->is_closed) { goffset newbytes = 0; if (-1 == vr->request.content_length) { if (!in->is_closed) { if (!li_filter_chunked_decode(vr, in, raw_in, &con->in_chunked_decode_state)) { if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "%s", "failed decoding chunked request body"); } li_connection_error(con); return; } if (in->is_closed) vr->request.content_length = in->bytes_in; newbytes = 1; /* always notify */ } } else { if (in->bytes_in < vr->request.content_length) { newbytes = li_chunkqueue_steal_len(in, raw_in, vr->request.content_length - in->bytes_in); } if (in->bytes_in == vr->request.content_length) { in->is_closed = TRUE; } } if (newbytes > 0 || in->is_closed) { li_stream_notify(&con->in); } } }
static liHandlerResult mc_handle_lookup(liVRequest *vr, gpointer param, gpointer *context) { memcached_ctx *ctx = param; memcache_request *req = *context; if (req) { static const GString default_mime_str = { CONST_STR_LEN("application/octet-stream"), 0 }; liBuffer *buf = req->buffer; const GString *mime_str; if (NULL != req->req) return LI_HANDLER_WAIT_FOR_EVENT; /* not done yet */ g_slice_free(memcache_request, req); *context = NULL; if (NULL == buf) { /* miss */ if (ctx->act_miss) li_action_enter(vr, ctx->act_miss); return LI_HANDLER_GO_ON; } if (!li_vrequest_handle_direct(vr)) { if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "%s", "memcached.lookup: request already handled"); } li_buffer_release(buf); return LI_HANDLER_GO_ON; } if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "%s", "memcached.lookup: key found, handling request"); } li_chunkqueue_append_buffer(vr->direct_out, buf); vr->response.http_status = 200; mime_str = li_mimetype_get(vr, vr->request.uri.path); if (!mime_str) mime_str = &default_mime_str; li_http_header_overwrite(vr->response.headers, CONST_STR_LEN("Content-Type"), GSTR_LEN(mime_str)); /* hit */ if (ctx->act_found) li_action_enter(vr, ctx->act_found); return LI_HANDLER_GO_ON; } else { liMemcachedCon *con; GError *err = NULL; if (li_vrequest_is_handled(vr)) { if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "%s", "memcached.lookup: request already handled"); } return LI_HANDLER_GO_ON; } con = mc_ctx_prepare(ctx, vr->wrk); mc_ctx_build_key(vr->wrk->tmp_str, ctx, vr); if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "memcached.lookup: looking up key '%s'", vr->wrk->tmp_str->str); } req = g_slice_new0(memcache_request); req->req = li_memcached_get(con, vr->wrk->tmp_str, memcache_callback, req, &err); if (NULL == req->req) { if (NULL != err) { if (LI_MEMCACHED_DISABLED != err->code) { VR_ERROR(vr, "memcached.lookup: get failed: %s", err->message); } g_clear_error(&err); } else { VR_ERROR(vr, "memcached.lookup: get failed: %s", "Unkown error"); } g_slice_free(memcache_request, req); /* miss */ if (ctx->act_miss) li_action_enter(vr, ctx->act_miss); return LI_HANDLER_GO_ON; } req->vr = vr; *context = req; return LI_HANDLER_WAIT_FOR_EVENT; } }
static liHandlerResult mod_limit_action_handle(liVRequest *vr, gpointer param, gpointer *context) { gboolean limit_reached = FALSE; mod_limit_context *ctx = (mod_limit_context*) param; GPtrArray *arr = g_ptr_array_index(vr->plugin_ctx, ctx->plugin->id); gint cons; mod_limit_req_ip_data *rid; liSocketAddress *remote_addr = &vr->coninfo->remote_addr; gpointer addr; guint32 bits; UNUSED(context); if (li_vrequest_is_handled(vr)) { VR_DEBUG(vr, "%s", "mod_limit: already have a content handler - ignoring limits. Put limit.* before content handlers such as 'static', 'fastcgi' or 'proxy'"); return LI_HANDLER_GO_ON; } /* IPv4 or IPv6? */ switch (remote_addr->addr->plain.sa_family) { case AF_INET: addr = &remote_addr->addr->ipv4.sin_addr.s_addr; bits = 32; break; case AF_INET6: addr = &remote_addr->addr->ipv6.sin6_addr.s6_addr; bits = 128; break; default: if (ctx->type == ML_TYPE_CON_IP || ctx->type == ML_TYPE_REQ_IP) { VR_DEBUG(vr, "%s", "mod_limit only supports ipv4 or ipv6 clients"); return LI_HANDLER_ERROR; } addr = NULL; bits = 0; } if (!arr) { /* request is not in any context yet, create new array */ arr = g_ptr_array_sized_new(2); g_ptr_array_index(vr->plugin_ctx, ctx->plugin->id) = arr; } switch (ctx->type) { case ML_TYPE_CON: #ifdef GLIB_VERSION_2_30 /* since 2.30 g_atomic_int_add does the same as g_atomic_int_exchange_and_add, * before it didn't return the old value. this fixes the deprecation warning. */ if (g_atomic_int_add(&ctx->pool.con, 1) > ctx->limit) { g_atomic_int_add(&ctx->pool.con, -1); limit_reached = TRUE; VR_DEBUG(vr, "limit.con: limit reached (%d active connections)", ctx->limit); } #else if (g_atomic_int_exchange_and_add(&ctx->pool.con, 1) > ctx->limit) { g_atomic_int_add(&ctx->pool.con, -1); limit_reached = TRUE; VR_DEBUG(vr, "limit.con: limit reached (%d active connections)", ctx->limit); } #endif break; case ML_TYPE_CON_IP: g_mutex_lock(ctx->mutex); cons = GPOINTER_TO_INT(li_radixtree_lookup_exact(ctx->pool.con_ip, addr, bits)); if (cons < ctx->limit) { li_radixtree_insert(ctx->pool.con_ip, addr, bits, GINT_TO_POINTER(cons+1)); } else { limit_reached = TRUE; VR_DEBUG(vr, "limit.con_ip: limit reached (%d active connections)", ctx->limit); } g_mutex_unlock(ctx->mutex); break; case ML_TYPE_REQ: g_mutex_lock(ctx->mutex); if (li_cur_ts(vr->wrk) - ctx->pool.req.ts > 1.0) { /* reset pool */ ctx->pool.req.ts = li_cur_ts(vr->wrk); ctx->pool.req.num = 1; } else { ctx->pool.req.num++; if (ctx->pool.req.num > ctx->limit) { limit_reached = TRUE; VR_DEBUG(vr, "limit.req: limit reached (%d req/s)", ctx->limit); } } g_mutex_unlock(ctx->mutex); break; case ML_TYPE_REQ_IP: g_mutex_lock(ctx->mutex); rid = li_radixtree_lookup_exact(ctx->pool.req_ip, addr, bits); if (!rid) { /* IP not known */ rid = g_slice_new0(mod_limit_req_ip_data); rid->requests = 1; rid->ip = li_sockaddr_dup(*remote_addr); rid->ctx = ctx; rid->timeout_elem.data = rid; li_radixtree_insert(ctx->pool.req_ip, addr, bits, rid); li_waitqueue_push(&(((mod_limit_data*)ctx->plugin->data)->timeout_queues[vr->wrk->ndx]), &rid->timeout_elem); } else if (rid->requests < ctx->limit) { rid->requests++; } else { limit_reached = TRUE; VR_DEBUG(vr, "limit.req_ip: limit reached (%d req/s)", ctx->limit); } g_mutex_unlock(ctx->mutex); break; } if (limit_reached) { /* limit reached, we either execute the defined action or return a 503 error page */ if (ctx->action_limit_reached) { /* execute action */ li_action_enter(vr, ctx->action_limit_reached); } else { /* return 503 error page */ if (!li_vrequest_handle_direct(vr)) { return LI_HANDLER_ERROR; } vr->response.http_status = 503; } } else { g_ptr_array_add(arr, ctx); g_atomic_int_inc(&ctx->refcount); } return LI_HANDLER_GO_ON; }