static liHandlerResult cache_etag_handle(liVRequest *vr, gpointer param, gpointer *context) { cache_etag_context *ctx = (cache_etag_context*) param; cache_etag_file *cfile = (cache_etag_file*) *context; GList *etag_entry; liHttpHeader *etag; struct stat st; GString *tmp_str = vr->wrk->tmp_str; liHandlerResult res; int err, fd; if (!cfile) { if (vr->request.http_method != LI_HTTP_METHOD_GET) return LI_HANDLER_GO_ON; LI_VREQUEST_WAIT_FOR_RESPONSE_HEADERS(vr); if (vr->response.http_status != 200) return LI_HANDLER_GO_ON; /* Don't cache static files if filter list is empty */ if (NULL == vr->filters_out_first && vr->backend_source->out->is_closed && 0 == vr->backend_source->out->mem_usage) return LI_HANDLER_GO_ON; etag_entry = li_http_header_find_first(vr->response.headers, CONST_STR_LEN("etag")); if (!etag_entry) return LI_HANDLER_GO_ON; /* no etag -> no caching */ if (li_http_header_find_next(etag_entry, CONST_STR_LEN("etag"))) { VR_ERROR(vr, "%s", "duplicate etag header in response, will not cache it"); return LI_HANDLER_GO_ON; } etag = (liHttpHeader*) etag_entry->data; cfile = cache_etag_file_create(createFileName(vr, ctx->path, etag)); *context = cfile; } res = li_stat_cache_get(vr, cfile->filename, &st, &err, &fd); if (res == LI_HANDLER_WAIT_FOR_EVENT) return res; if (res == LI_HANDLER_GO_ON) { liFilter *f; if (!S_ISREG(st.st_mode)) { VR_ERROR(vr, "Unexpected file type for cache file '%s' (mode %o)", cfile->filename->str, (unsigned int) st.st_mode); close(fd); return LI_HANDLER_GO_ON; /* no caching */ } cfile->hit_fd = fd; #ifdef FD_CLOEXEC fcntl(cfile->hit_fd, F_SETFD, FD_CLOEXEC); #endif if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "cache hit for '%s'", vr->request.uri.path->str); } cfile->hit_length = st.st_size; g_string_truncate(tmp_str, 0); li_string_append_int(tmp_str, st.st_size); li_http_header_overwrite(vr->response.headers, CONST_STR_LEN("Content-Length"), GSTR_LEN(tmp_str)); f = li_vrequest_add_filter_out(vr, cache_etag_filter_hit, NULL, NULL, NULL); if (NULL != f) { li_chunkqueue_append_file_fd(f->out, NULL, 0, cfile->hit_length, cfile->hit_fd); f->out->is_closed = TRUE; cfile->hit_fd = -1; } cache_etag_file_free(cfile); *context = NULL; return LI_HANDLER_GO_ON; } if (CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "cache miss for '%s'", vr->request.uri.path->str); } if (!cache_etag_file_start(vr, cfile)) { cache_etag_file_free(cfile); *context = NULL; return LI_HANDLER_GO_ON; /* no caching */ } li_vrequest_add_filter_out(vr, cache_etag_filter_miss, cache_etag_filter_free, NULL, cfile); *context = NULL; return LI_HANDLER_GO_ON; }
static liHandlerResult auth_basic(liVRequest *vr, gpointer param, gpointer *context) { liHttpHeader *hdr; gboolean auth_ok = FALSE; AuthBasicData *bdata = param; gboolean debug = _OPTION(vr, bdata->p, 0).boolean; UNUSED(context); if (li_vrequest_is_handled(vr)) { if (debug || CORE_OPTION(LI_CORE_OPTION_DEBUG_REQUEST_HANDLING).boolean) { VR_DEBUG(vr, "skipping auth.basic as request is already handled with current status %i", vr->response.http_status); } return LI_HANDLER_GO_ON; } /* check for Authorization header */ hdr = li_http_header_lookup(vr->request.headers, CONST_STR_LEN("Authorization")); if (!hdr || !g_str_has_prefix(LI_HEADER_VALUE(hdr), "Basic ")) { if (debug) { VR_DEBUG(vr, "requesting authorization from client for realm \"%s\"", bdata->realm->str); } } else { gchar *decoded, *username = NULL, *password; size_t len; /* auth_info contains username:password encoded in base64 */ if (NULL != (decoded = (gchar*)g_base64_decode(LI_HEADER_VALUE(hdr) + sizeof("Basic ") - 1, &len))) { /* bogus data? */ if (NULL != (password = strchr(decoded, ':'))) { *password = '******'; password++; username = decoded; } else { g_free(decoded); } } if (!username) { if (debug) { VR_DEBUG(vr, "couldn't parse authorization info from client for realm \"%s\"", bdata->realm->str); } } else { GString user = li_const_gstring(username, password - username - 1); GString pass = li_const_gstring(password, len - (password - username)); if (bdata->backend(vr, &user, &pass, bdata, debug)) { auth_ok = TRUE; li_environment_set(&vr->env, CONST_STR_LEN("REMOTE_USER"), username, password - username - 1); li_environment_set(&vr->env, CONST_STR_LEN("AUTH_TYPE"), CONST_STR_LEN("Basic")); } else { if (debug) { VR_DEBUG(vr, "wrong authorization info from client on realm \"%s\" (user: \"%s\")", bdata->realm->str, username); } } g_free(decoded); } } g_string_truncate(vr->wrk->tmp_str, 0); g_string_append_len(vr->wrk->tmp_str, CONST_STR_LEN("Basic realm=\"")); g_string_append_len(vr->wrk->tmp_str, GSTR_LEN(bdata->realm)); g_string_append_c(vr->wrk->tmp_str, '"'); /* generate header always */ if (!auth_ok) { li_http_header_overwrite(vr->response.headers, CONST_STR_LEN("WWW-Authenticate"), GSTR_LEN(vr->wrk->tmp_str)); /* we already checked for handled */ if (!li_vrequest_handle_direct(vr)) return LI_HANDLER_ERROR; vr->response.http_status = 401; return LI_HANDLER_GO_ON; } else { /* lets hope browser just ignore the header if status is not 401 * but this way it is easier to use a later "auth.deny;" */ li_http_header_overwrite(vr->response.headers, CONST_STR_LEN("WWW-Authenticate"), GSTR_LEN(vr->wrk->tmp_str)); } if (debug) { VR_DEBUG(vr, "client authorization successful for realm \"%s\"", bdata->realm->str); } return LI_HANDLER_GO_ON; }
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; }