#include "pangu_logger.h" #include "pattern_replace.h" #include "pangu_web_cache.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAX_SCAN_RESULT 16 #define MAX_EDIT_ZONE_NUM 64 enum pangu_action//Bigger action number is prior. { PG_ACTION_NONE = 0x00, PG_ACTION_MONIT = 0x01, PG_ACTION_FORWARD = 0x02, /* N/A */ PG_ACTION_REJECT = 0x10, PG_ACTION_DROP = 0x20, /* N/A */ PG_ACTION_REDIRECT = 0x30, PG_ACTION_RATELIMIT = 0x40, /* N/A */ PG_ACTION_REPLACE = 0x50, PG_ACTION_LOOP = 0x60, /* N/A */ PG_ACTION_WHITELIST = 0x80, __PG_ACTION_MAX }; enum scan_table { PXY_CTRL_IP, PXY_CTRL_HTTP_URL, PXY_CTRL_HTTP_REQ_HDR, PXY_CTRL_HTTP_REQ_BODY, PXY_CTRL_HTTP_RES_HDR, PXY_CTRL_HTTP_RES_BODY, __SCAN_TABLE_MAX }; enum pangu_http_stat { STAT_SESSION, STAT_LOG_NUM, STAT_ACTION_MONIT, STAT_ACTION_REJECT, STAT_ACTION_REDIRECT, STAT_ACTION_PRE_REPLACE, STAT_ACTION_REPLACE, STAT_ACTION_WHITELSIT, __PG_STAT_MAX }; struct pangu_rt { Maat_feather_t maat; struct pangu_logger * send_logger; void * local_logger; int log_level; int thread_num; int scan_table_id[__SCAN_TABLE_MAX]; ctemplate::Template * tpl_403, * tpl_404, * tpl_451; char * reject_page; int page_size; int cache_enabled; struct cache_handle* cache; screen_stat_handle_t fs_handle; long long stat_val[__PG_STAT_MAX]; int fs_id[__PG_STAT_MAX]; struct event_base* gc_evbase; struct event* gcev; }; struct pangu_rt * g_pangu_rt; #define MAAT_INPUT_JSON 0 #define MAAT_INPUT_REDIS 1 #define MAAT_INPUT_FILE 2 static Maat_feather_t create_maat_feather(const char * profile, const char * section, int max_thread, void * logger) { Maat_feather_t target; int input_mode = 0, maat_stat_on = 0, maat_perf_on = 0; int ret = 0, scan_detail = 0, effect_interval = 60; char table_info[TFE_STRING_MAX] = {0}, inc_cfg_dir[TFE_STRING_MAX] = {0}, ful_cfg_dir[TFE_STRING_MAX] = {0}; char redis_server[TFE_STRING_MAX] = {0}; int redis_port = 0; int redis_db_idx = 0; char json_cfg_file[TFE_STRING_MAX] = {0}, maat_stat_file[TFE_STRING_MAX] = {0}; const char * instance_name = "pangu"; MESA_load_profile_int_def(profile, section, "MAAT_INPUT_MODE", &(input_mode), 0); MESA_load_profile_int_def(profile, section, "STAT_SWITCH", &(maat_stat_on), 1); MESA_load_profile_int_def(profile, section, "PERF_SWITCH", &(maat_perf_on), 1); MESA_load_profile_string_def(profile, section, "TABLE_INFO", table_info, sizeof(table_info), ""); MESA_load_profile_string_def(profile, section, "JSON_CFG_FILE", json_cfg_file, sizeof(json_cfg_file), ""); MESA_load_profile_string_def(profile, section, "MAAT_REDIS_SERVER", redis_server, sizeof(redis_server), ""); MESA_load_profile_int_def(profile, section, "MAAT_REDIS_PORT", &(redis_port), 6379); MESA_load_profile_int_def(profile, section, "MAAT_REDIS_DB_INDEX", &(redis_db_idx), 0); MESA_load_profile_string_def(profile, section, "INC_CFG_DIR", inc_cfg_dir, sizeof(inc_cfg_dir), ""); MESA_load_profile_string_def(profile, section, "FULL_CFG_DIR", ful_cfg_dir, sizeof(ful_cfg_dir), ""); MESA_load_profile_string_def(profile, section, "STAT_FILE", maat_stat_file, sizeof(maat_stat_file), ""); MESA_load_profile_int_def(profile, section, "EFFECT_INTERVAL_S", &(effect_interval), 60); effect_interval *= 1000;//convert s to ms assert(strlen(inc_cfg_dir) != 0 && strlen(ful_cfg_dir) != 0); target = Maat_feather(max_thread, table_info, logger); Maat_set_feather_opt(target, MAAT_OPT_INSTANCE_NAME, instance_name, strlen(instance_name) + 1); switch (input_mode) { case MAAT_INPUT_JSON: Maat_set_feather_opt(target, MAAT_OPT_JSON_FILE_PATH, json_cfg_file, strlen(json_cfg_file) + 1); break; case MAAT_INPUT_REDIS: Maat_set_feather_opt(target, MAAT_OPT_REDIS_IP, redis_server, strlen(redis_server) + 1); Maat_set_feather_opt(target, MAAT_OPT_REDIS_PORT, &redis_port, sizeof(redis_port)); Maat_set_feather_opt(target, MAAT_OPT_REDIS_INDEX, &redis_db_idx, sizeof(redis_db_idx)); break; case MAAT_INPUT_FILE: Maat_set_feather_opt(target, MAAT_OPT_FULL_CFG_DIR, ful_cfg_dir, strlen(ful_cfg_dir) + 1); Maat_set_feather_opt(target, MAAT_OPT_INC_CFG_DIR, inc_cfg_dir, strlen(inc_cfg_dir) + 1); break; default: TFE_LOG_ERROR(logger, "Invalid MAAT Input Mode: %d.", input_mode); goto error_out; break; } if (maat_stat_on) { Maat_set_feather_opt(target, MAAT_OPT_STAT_FILE_PATH, maat_stat_file, strlen(maat_stat_file) + 1); Maat_set_feather_opt(target, MAAT_OPT_STAT_ON, NULL, 0); if (maat_perf_on) { Maat_set_feather_opt(target, MAAT_OPT_PERF_ON, NULL, 0); } } Maat_set_feather_opt(target, MAAT_OPT_EFFECT_INVERVAL_MS, &effect_interval, sizeof(effect_interval)); Maat_set_feather_opt(target, MAAT_OPT_SCAN_DETAIL, &scan_detail, sizeof(scan_detail)); ret = Maat_initiate_feather(target); if (ret < 0) { TFE_LOG_ERROR(logger, "%s MAAT init failed.", __FUNCTION__); goto error_out; } return target; error_out: Maat_burn_feather(target); return NULL; } static void pangu_http_gc_cb(evutil_socket_t fd, short what, void * arg) { int i=0; for(i=0;i<__PG_STAT_MAX;i++) { FS_operate(g_pangu_rt->fs_handle, g_pangu_rt->fs_id[i], 0, FS_OP_SET, ATOMIC_READ(&(g_pangu_rt->stat_val[i]))); } return; } static void pangu_http_stat_init(struct pangu_rt * pangu_runtime) { int i=0; struct timeval gc_delay = {0, 500*1000}; //Microseconds, we set 500 miliseconds here. const char* spec[__PG_STAT_MAX]={0}; spec[STAT_SESSION]="http_sess"; spec[STAT_LOG_NUM]="log_num"; spec[STAT_ACTION_MONIT]="monitor"; spec[STAT_ACTION_REJECT]="reject"; spec[STAT_ACTION_REDIRECT]="redirect"; spec[STAT_ACTION_PRE_REPLACE]="pre_replace"; spec[STAT_ACTION_REPLACE]="replace"; spec[STAT_ACTION_WHITELSIT]="whitelist"; for(i=0;i<__PG_STAT_MAX;i++) { if(spec[i]!=NULL) { pangu_runtime->fs_id[i]=FS_register(pangu_runtime->fs_handle, FS_STYLE_STATUS, FS_CALC_CURRENT, spec[i]); } } g_pangu_rt->gcev = event_new(pangu_runtime->gc_evbase, -1, EV_PERSIST, pangu_http_gc_cb, NULL); evtimer_add(g_pangu_rt->gcev, &gc_delay); return; } int pangu_http_init(struct tfe_proxy * proxy) { const char * profile = "./pangu_conf/pangu_pxy.conf"; const char * logfile = "./log/pangu_pxy.log"; g_pangu_rt = ALLOC(struct pangu_rt, 1); g_pangu_rt->thread_num = tfe_proxy_get_work_thread_count(); g_pangu_rt->gc_evbase=tfe_proxy_get_gc_evbase(); MESA_load_profile_int_def(profile, "DEBUG", "LOG_LEVEL", &(g_pangu_rt->log_level), 0); g_pangu_rt->local_logger = MESA_create_runtime_log_handle(logfile, g_pangu_rt->log_level); g_pangu_rt->send_logger = pangu_log_handle_create(profile, "LOG", g_pangu_rt->local_logger); if (!g_pangu_rt->send_logger) { goto error_out; } g_pangu_rt->fs_handle = tfe_proxy_get_fs_handle(); pangu_http_stat_init(g_pangu_rt); g_pangu_rt->maat = create_maat_feather(profile, "MAAT", g_pangu_rt->thread_num, g_pangu_rt->local_logger); if (!g_pangu_rt->maat) { goto error_out; } const char * table_name[__SCAN_TABLE_MAX]; table_name[PXY_CTRL_IP] = "PXY_CTRL_IP"; table_name[PXY_CTRL_HTTP_URL] = "PXY_CTRL_HTTP_URL"; table_name[PXY_CTRL_HTTP_REQ_HDR] = "PXY_CTRL_HTTP_REQ_HDR"; table_name[PXY_CTRL_HTTP_REQ_BODY] = "PXY_CTRL_HTTP_REQ_BODY"; table_name[PXY_CTRL_HTTP_RES_HDR] = "PXY_CTRL_HTTP_RES_HDR"; table_name[PXY_CTRL_HTTP_RES_BODY] = "PXY_CTRL_HTTP_RES_BODY"; for (int i = 0; i < __SCAN_TABLE_MAX; i++) { g_pangu_rt->scan_table_id[i] = Maat_table_register(g_pangu_rt->maat, table_name[i]); if (g_pangu_rt->scan_table_id[i] < 0) { TFE_LOG_ERROR(NULL, "Pangu HTTP Maat table %s register failed.", table_name[i]); goto error_out; } } char page_path[256]; memset(page_path, 0, sizeof(page_path)); MESA_load_profile_string_def(profile, "TEMPLATE", "PAGE_403", page_path, sizeof(page_path), "./pangu_conf/template/HTTP403.html"); g_pangu_rt->tpl_403 = ctemplate::Template::GetTemplate(page_path, ctemplate::DO_NOT_STRIP); memset(page_path, 0, sizeof(page_path)); MESA_load_profile_string_def(profile, "TEMPLATE", "PAGE_404", page_path, sizeof(page_path), "./pangu_conf/template/HTTP404.html"); g_pangu_rt->tpl_404 = ctemplate::Template::GetTemplate(page_path, ctemplate::DO_NOT_STRIP); memset(page_path, 0, sizeof(page_path)); MESA_load_profile_string_def(profile, "TEMPLATE", "PAGE_451", page_path, sizeof(page_path), "./pangu_conf/template/HTTP451.html"); g_pangu_rt->tpl_451 = ctemplate::Template::GetTemplate(page_path, ctemplate::DO_NOT_STRIP); MESA_load_profile_int_def(profile, "TANGO_CACHE", "enable_cache", &(g_pangu_rt->cache_enabled), 1); if(g_pangu_rt->cache_enabled) { g_pangu_rt->cache = create_web_cache_handle(profile, "TANGO_CACHE", g_pangu_rt->gc_evbase, g_pangu_rt->local_logger); if(!g_pangu_rt->cache) { TFE_LOG_INFO(NULL, "Tango Cache init failed."); goto error_out; } TFE_LOG_INFO(NULL, "Tango Cache Enabled."); } TFE_LOG_INFO(NULL, "Pangu HTTP init success."); return 0; error_out: TFE_LOG_ERROR(NULL, "Pangu HTTP init failed."); return -1; } struct replace_ctx { struct replace_rule * rule; size_t n_rule; struct tfe_http_half * replacing; struct evbuffer * http_body; int actually_replaced; }; struct pangu_http_ctx { int magic_num; enum pangu_action action; char * action_para; scan_status_t mid; stream_para_t sp; struct Maat_rule_t * enforce_rules; size_t n_enforce; char * enforce_para; struct replace_ctx * rep_ctx; int resume_from_cache_query; enum cache_query_status cache_query_status; struct future* f_cache_query; struct tfe_http_session * ref_session; struct tfe_http_half* cached_response; size_t cache_result_declared_sz, cache_result_actual_sz; struct cache_update_context* cache_update_ctx; int thread_id; }; void http_repl_ctx_free(struct replace_ctx* rep_ctx) { for (size_t i = 0; i < rep_ctx->n_rule; i++) { FREE(&(rep_ctx->rule[i].find)); FREE(&(rep_ctx->rule[i].replace_with)); } if (rep_ctx->http_body) { evbuffer_free(rep_ctx->http_body); rep_ctx->http_body = NULL; } //todo destroy http_half; assert(rep_ctx->replacing == NULL); FREE(&rep_ctx); return; } #define HTTP_CTX_MAGIC_NUM 20181021 static struct pangu_http_ctx * pangu_http_ctx_new(unsigned int thread_id) { struct pangu_http_ctx * ctx = ALLOC(struct pangu_http_ctx, 1); ctx->magic_num=HTTP_CTX_MAGIC_NUM; ctx->mid = NULL; ctx->thread_id = (int) thread_id; return ctx; } static void pangu_http_ctx_free(struct pangu_http_ctx * ctx) { assert(ctx->magic_num==HTTP_CTX_MAGIC_NUM); if (ctx->rep_ctx) { http_repl_ctx_free(ctx->rep_ctx); ctx->rep_ctx = NULL; } FREE(&ctx->enforce_rules); FREE(&ctx->enforce_para); Maat_clean_status(&(ctx->mid)); ctx->mid = NULL; if(ctx->sp) { Maat_stream_scan_string_end(&(ctx->sp)); } if(ctx->cache_update_ctx) { web_cache_update_end(ctx->cache_update_ctx); ctx->cache_update_ctx=NULL; } assert(ctx->cached_response==NULL); if(ctx->f_cache_query) { future_destroy(ctx->f_cache_query); ctx->f_cache_query=NULL; } ctx->magic_num=0; FREE(&ctx); } static inline void addr_tfe2sapp(const struct tfe_stream_addr * tfe_addr, struct ipaddr * sapp_addr) { if(tfe_addr->addrtype==TFE_ADDR_STREAM_TUPLE4_V4||tfe_addr->addrtype==TFE_ADDR_IPV4) { sapp_addr->addrtype = ADDR_TYPE_IPV4; } else { sapp_addr->addrtype=ADDR_TYPE_IPV6; } sapp_addr->paddr = (char *) tfe_addr->paddr; return; } static int pangu_action_weight[__PG_ACTION_MAX] = {0}; void __pangu_action_weight_init() __attribute__((constructor, used)); void __pangu_action_weight_init() { pangu_action_weight[PG_ACTION_NONE] = 0; pangu_action_weight[PG_ACTION_MONIT] = 1; pangu_action_weight[PG_ACTION_REPLACE] = 2; pangu_action_weight[PG_ACTION_REDIRECT] = 3; pangu_action_weight[PG_ACTION_REJECT] = 4; pangu_action_weight[PG_ACTION_WHITELIST] = 5; } static inline int action_cmp(enum pangu_action a1, enum pangu_action a2) { return pangu_action_weight[a1] - pangu_action_weight[a2]; } //enforce_rules[0] contains execute action. static enum pangu_action decide_ctrl_action(const struct Maat_rule_t * hit_rules, size_t n_hit, struct Maat_rule_t ** enforce_rules, size_t * n_enforce) { size_t n_monit = 0, exist_enforce_num = 0, i = 0; const struct Maat_rule_t * prior_rule = hit_rules; struct Maat_rule_t monit_rule[n_hit]; enum pangu_action prior_action = PG_ACTION_NONE; for (i = 0; i < n_hit; i++) { if ((enum pangu_action) hit_rules[i].action == PG_ACTION_MONIT) { memcpy(monit_rule + n_monit, hit_rules + i, sizeof(struct Maat_rule_t)); n_monit++; } if (action_cmp((enum pangu_action) hit_rules[i].action, prior_action)>0) { prior_rule = hit_rules + i; prior_action = (enum pangu_action) hit_rules[i].action; } else if (action_cmp((enum pangu_action) hit_rules[i].action, prior_action) == 0) { if (hit_rules[i].config_id > prior_rule->config_id) { prior_rule = hit_rules + i; } } else { continue; } } if (prior_action == PG_ACTION_WHITELIST) { return PG_ACTION_WHITELIST; } exist_enforce_num = *n_enforce; if (prior_action == PG_ACTION_MONIT) { *n_enforce += n_monit; } else { *n_enforce += n_monit + 1; } *enforce_rules = (struct Maat_rule_t *) realloc(*enforce_rules, sizeof(struct Maat_rule_t) * (*n_enforce)); if (prior_action == PG_ACTION_MONIT) { memcpy(*enforce_rules + exist_enforce_num, monit_rule, n_monit * sizeof(struct Maat_rule_t)); } else { memcpy(*enforce_rules + exist_enforce_num, prior_rule, sizeof(struct Maat_rule_t)); memcpy(*enforce_rules + exist_enforce_num + 1, monit_rule, n_monit * sizeof(struct Maat_rule_t)); } return prior_action; } //HTML template is downloaded from https://github.com/AndiDittrich/HttpErrorPages static void html_generate(int status_code, int cfg_id, const char* msg, char ** page_buff, size_t * page_size) { ctemplate::TemplateDictionary dict("pg_page_dict"); //dict is automatically finalized after function returned. dict.SetIntValue("cfg_id", cfg_id); dict.SetValue("msg", msg); std::string output; ctemplate::Template * tpl = NULL; switch (status_code) { case 403: tpl = g_pangu_rt->tpl_403; break; case 404: tpl = g_pangu_rt->tpl_404; break; case 451: tpl = g_pangu_rt->tpl_451; break; default: return; } tpl->Expand(&output, &dict); *page_size = output.length() + 1; *page_buff = tfe_strdup(output.c_str()); } static void html_free(char ** page_buff) { FREE(page_buff); return; } static void cache_query_on_succ(future_result_t * result, void * user) { struct pangu_http_ctx * ctx = (struct pangu_http_ctx *)user; struct cached_meta* meta=NULL; enum cache_query_result_type type=cache_query_result_get_type(result); const unsigned char* data=NULL; size_t data_sz=0; char temp[TFE_STRING_MAX]; switch(type) { case CACHE_QUERY_RESULT_META: meta=cache_query_result_get_header(result); ctx->cache_result_declared_sz=meta->content_length; ctx->resume_from_cache_query=1; tfe_http_session_resume(ctx->ref_session); ctx->cached_response=tfe_http_session_response_create(ctx->ref_session, 200); tfe_http_nonstd_field_write(ctx->cached_response, "X-Cache-Lookup", "Hit From TFE"); tfe_http_std_field_write(ctx->cached_response, TFE_HTTP_CONT_TYPE, meta->content_type); snprintf(temp, sizeof(temp), "%lu", meta->content_length); tfe_http_std_field_write(ctx->cached_response, TFE_HTTP_CONT_LENGTH, temp); tfe_http_session_response_set(ctx->ref_session, ctx->cached_response); tfe_http_half_write_body_begin(ctx->cached_response, 1); cache_query_free_meta(meta); meta=NULL; break; case CACHE_QUERY_RESULT_DATA: data_sz=cache_query_result_get_data(result, &data); tfe_http_half_write_body_data(ctx->cached_response, data, data_sz); ctx->cache_result_actual_sz+=data_sz; break; case CACHE_QUERY_RESULT_END: assert(ctx->cached_response!=NULL); ctx->cache_query_status=WEB_CACHE_HIT; tfe_http_half_write_body_end(ctx->cached_response); //ownership has been transferred to http session, set to NULL. ctx->cached_response=NULL; assert(ctx->cache_result_actual_sz==ctx->cache_result_declared_sz); future_destroy(ctx->f_cache_query); ctx->f_cache_query=NULL; break; case CACHE_QUERY_RESULT_MISS: ctx->cache_query_status=WEB_CACHE_NOT_HIT; ctx->resume_from_cache_query=1; tfe_http_session_resume(ctx->ref_session); future_destroy(ctx->f_cache_query); ctx->f_cache_query=NULL; break; default: break; } return; } static void cache_query_on_fail(enum e_future_error err, const char * what, void * user) { struct pangu_http_ctx * ctx = (struct pangu_http_ctx *)user; future_destroy(ctx->f_cache_query); ctx->f_cache_query=NULL; ctx->cache_query_status=WEB_CACHE_NOT_HIT; ctx->resume_from_cache_query=1; if(!ctx->cached_response) { tfe_http_session_resume(ctx->ref_session); } else { tfe_http_half_write_body_end(ctx->cached_response); ctx->cached_response=NULL; } printf("cache query failed: %s %s\n", ctx->ref_session->req->req_spec.url, what); } void http_replace(const struct tfe_stream * stream, const struct tfe_http_session * session, enum tfe_http_event events, const unsigned char * body_frag, size_t frag_size, struct pangu_http_ctx * ctx) { struct tfe_http_session * to_write_sess = NULL; char * rewrite_buff = NULL; size_t rewrite_sz = 0; to_write_sess = tfe_http_session_allow_write(session); if (to_write_sess == NULL) //fail to wirte, abandon. { TFE_STREAM_LOG_INFO(stream, "tfe_http_session_allow_write() %s failed.", session->req->req_spec.uri); ctx->action = PG_ACTION_NONE; tfe_http_session_detach(session); return; } struct replace_ctx * rep_ctx = ctx->rep_ctx; if (ctx->rep_ctx == NULL) { /* we must determinate the replace action on HTTP header, otherwise, * the header has been forwarded, only replace the body but not modify header will raise exception */ if ((events & EV_HTTP_REQ_HDR) || (events & EV_HTTP_RESP_HDR)) { ctx->rep_ctx = rep_ctx = ALLOC(struct replace_ctx, 1); rep_ctx->rule = ALLOC(struct replace_rule, MAX_EDIT_ZONE_NUM); rep_ctx->n_rule = format_replace_rule(ctx->enforce_para, rep_ctx->rule, MAX_EDIT_ZONE_NUM); } else { TFE_STREAM_LOG_INFO(stream, "Can only setup replace on REQ/RESP headers, detached."); ctx->action = PG_ACTION_NONE; tfe_http_session_detach(session); return; } } struct tfe_http_half * in_req_half = session->req; struct tfe_http_half * in_resp_half = session->resp; struct tfe_http_req_spec * in_req_spec = &in_req_half->req_spec; struct tfe_http_resp_spec * in_resp_spec = &in_resp_half->resp_spec; if ((events & EV_HTTP_REQ_HDR) || (events & EV_HTTP_RESP_HDR)) { char * rewrite_uri = NULL; size_t rewrite_uri_sz=0; if (tfe_http_in_request(events)) { rewrite_uri_sz = execute_replace_rule(in_req_spec->uri, strlen(in_req_spec->uri), kZoneRequestUri, rep_ctx->rule, rep_ctx->n_rule, &rewrite_uri); if(rewrite_uri_sz>0) rep_ctx->actually_replaced=1; rep_ctx->replacing = tfe_http_session_request_create(to_write_sess, in_req_spec->method, rewrite_uri_sz >0 ? rewrite_uri : in_req_spec->uri); tfe_http_session_request_set(to_write_sess, rep_ctx->replacing); } else { rep_ctx->replacing = tfe_http_session_response_create(to_write_sess, in_resp_spec->resp_code); tfe_http_session_response_set(to_write_sess, rep_ctx->replacing); } if (rewrite_uri != NULL) { FREE(&rewrite_uri); } enum replace_zone zone = tfe_http_in_request(events) ? kZoneRequestHeaders : kZoneResponseHeader; struct tfe_http_half * in_half = tfe_http_in_request(events) ? in_req_half : in_resp_half; struct http_field_name in_header_field{}; const char * in_header_value = NULL; void * iterator = NULL; while (true) { if ((in_header_value = tfe_http_field_iterate(in_half, &iterator, &in_header_field)) == NULL) { break; } rewrite_buff = NULL; rewrite_sz = 0; rewrite_sz=execute_replace_rule(in_header_value, strlen(in_header_value), zone, rep_ctx->rule, rep_ctx->n_rule, &rewrite_buff); if(rewrite_sz>0) rep_ctx->actually_replaced=1; tfe_http_field_write(rep_ctx->replacing, &in_header_field, rewrite_sz>0? rewrite_buff : in_header_value); if(rewrite_buff != NULL) { FREE(&rewrite_buff); } } } if ((events & EV_HTTP_REQ_BODY_BEGIN) || (events & EV_HTTP_RESP_BODY_BEGIN)) { assert(rep_ctx->http_body == NULL); rep_ctx->http_body = evbuffer_new(); } if ((events & EV_HTTP_REQ_BODY_CONT) || (events & EV_HTTP_RESP_BODY_CONT)) { evbuffer_add(rep_ctx->http_body, body_frag, frag_size); } if ((events & EV_HTTP_REQ_BODY_END) || (events & EV_HTTP_RESP_BODY_END)) { char * __http_body = (char *) evbuffer_pullup(rep_ctx->http_body, -1); size_t __http_body_len = evbuffer_get_length(rep_ctx->http_body); enum replace_zone r_zone = tfe_http_in_request(events) ? kZoneRequestBody : kZoneResponseBody; rewrite_buff = NULL; rewrite_sz = 0; rewrite_sz = execute_replace_rule(__http_body, __http_body_len, r_zone, rep_ctx->rule, rep_ctx->n_rule, &rewrite_buff); if (rewrite_sz >0 ) { tfe_http_half_append_body(rep_ctx->replacing, rewrite_buff, rewrite_sz, 0); rep_ctx->actually_replaced=1; } else { tfe_http_half_append_body(rep_ctx->replacing, __http_body, __http_body_len, 0); } if (rewrite_buff != NULL) { FREE(&rewrite_buff); } if (rep_ctx->http_body != NULL) { evbuffer_free(rep_ctx->http_body); rep_ctx->http_body = NULL; } } if ((events & EV_HTTP_REQ_END) || (events & EV_HTTP_RESP_END)) { tfe_http_half_append_body(rep_ctx->replacing, NULL, 0, 0); rep_ctx->replacing = NULL; } } static void http_reject(const struct tfe_http_session * session, enum tfe_http_event events, struct pangu_http_ctx * ctx) { int resp_code = 0, ret = 0; struct tfe_http_half * response = NULL; char * page_buff = NULL; size_t page_size = 0; char cont_len_str[16]; char msg[TFE_STRING_MAX]; struct tfe_http_session * to_write_sess = NULL; ret = sscanf(ctx->enforce_para, "code=%d;content=%[^\n]", &resp_code, msg); if (ret != 2) { TFE_LOG_ERROR(g_pangu_rt->local_logger, "Invalid reject rule %d paramter %s", ctx->enforce_rules[0].config_id, ctx->enforce_para); ctx->action = PG_ACTION_NONE; goto error_out; } to_write_sess = tfe_http_session_allow_write(session); response = tfe_http_session_response_create(to_write_sess, resp_code); html_generate(resp_code, ctx->enforce_rules[0].config_id, msg, &page_buff, &page_size); tfe_http_std_field_write(response, TFE_HTTP_CONT_TYPE, "text/html; charset=utf-8"); snprintf(cont_len_str, sizeof(cont_len_str), "%lu", page_size); tfe_http_std_field_write(response, TFE_HTTP_CONT_LENGTH, cont_len_str); tfe_http_half_append_body(response, page_buff, page_size, 0); tfe_http_half_append_body(response, NULL, 0, 0); tfe_http_session_response_set(to_write_sess, response); tfe_http_session_detach(session); error_out: html_free(&page_buff); } static void http_redirect(const struct tfe_http_session * session, enum tfe_http_event events, struct pangu_http_ctx * ctx) { int resp_code = 0, ret = 0; char * url = NULL; struct tfe_http_half * response = NULL; struct tfe_http_session * to_write = NULL; url = ALLOC(char, ctx->enforce_rules[0].serv_def_len); ret = sscanf(ctx->enforce_para, "code=%d;url=%[^;]", &resp_code, url); if (ret != 2) { TFE_LOG_ERROR(g_pangu_rt->local_logger, "Invalid redirect rule %d paramter %s", ctx->enforce_rules[0].config_id, ctx->enforce_para); goto error_out; } to_write = tfe_http_session_allow_write(session); if (to_write == NULL) { assert(0); } response = tfe_http_session_response_create(to_write, resp_code); tfe_http_std_field_write(response, TFE_HTTP_LOCATION, url); tfe_http_half_append_body(response, NULL, 0, 0); tfe_http_session_response_set(to_write, response); tfe_http_session_detach(session); error_out: free(url); } enum pangu_action http_scan(const struct tfe_http_session * session, enum tfe_http_event events, const unsigned char * body_frag, size_t frag_size, struct pangu_http_ctx * ctx) { void * interator = NULL; const char * field_val = NULL; struct http_field_name field_name; struct Maat_rule_t result[MAX_SCAN_RESULT]; char buff[TFE_STRING_MAX], * p = NULL; int scan_ret = 0, table_id = 0, read_rule_ret = 0; size_t hit_cnt = 0, i = 0; if (events & EV_HTTP_REQ_HDR) { const char * str_url = session->req->req_spec.url; int str_url_length = (int) (strlen(session->req->req_spec.url)); scan_ret = Maat_full_scan_string(g_pangu_rt->maat, g_pangu_rt->scan_table_id[PXY_CTRL_HTTP_URL], CHARSET_UTF8, str_url, str_url_length, result, NULL, MAX_SCAN_RESULT, &(ctx->mid), ctx->thread_id); if (scan_ret > 0) { hit_cnt += scan_ret; } } if ((events & EV_HTTP_REQ_HDR) || (events & EV_HTTP_RESP_HDR)) { table_id = events & EV_HTTP_REQ_HDR ? g_pangu_rt->scan_table_id[PXY_CTRL_HTTP_REQ_HDR] : g_pangu_rt ->scan_table_id[PXY_CTRL_HTTP_RES_HDR]; while (hit_cnt < MAX_SCAN_RESULT) { field_val = tfe_http_field_iterate(session->req, &interator, &field_name); if (field_val == NULL) { break; } const char * str_field_name = http_field_to_string(&field_name); scan_ret = Maat_set_scan_status(g_pangu_rt->maat, &(ctx->mid), MAAT_SET_SCAN_DISTRICT, str_field_name, strlen(str_field_name)); assert(scan_ret == 0); scan_ret = Maat_full_scan_string(g_pangu_rt->maat, table_id, CHARSET_UTF8, field_val, strlen(field_val), result + hit_cnt, NULL, MAX_SCAN_RESULT - hit_cnt, &(ctx->mid), ctx->thread_id); if (scan_ret > 0) { hit_cnt += scan_ret; } } } if ((events & EV_HTTP_REQ_BODY_BEGIN) | (events & EV_HTTP_RESP_BODY_BEGIN)) { assert(ctx->sp == NULL); table_id = events & EV_HTTP_REQ_BODY_BEGIN ? g_pangu_rt->scan_table_id[PXY_CTRL_HTTP_REQ_BODY] : g_pangu_rt ->scan_table_id[PXY_CTRL_HTTP_RES_BODY]; ctx->sp = Maat_stream_scan_string_start(g_pangu_rt->maat, table_id, ctx->thread_id); } if (body_frag != NULL) { scan_ret = Maat_stream_scan_string(&(ctx->sp), CHARSET_UTF8, (const char *) body_frag, (int) frag_size, result + hit_cnt, NULL, MAX_SCAN_RESULT - hit_cnt, &(ctx->mid)); if (scan_ret > 0) { hit_cnt += scan_ret; } } if ((events & EV_HTTP_REQ_BODY_END) | (events & EV_HTTP_RESP_BODY_END)) { Maat_stream_scan_string_end(&(ctx->sp)); ctx->sp = NULL; } if (hit_cnt > 0) { ctx->action = decide_ctrl_action(result, hit_cnt, &ctx->enforce_rules, &ctx->n_enforce); size_t __serv_def_len = (size_t)ctx->enforce_rules[0].serv_def_len; ctx->enforce_para = ALLOC(char, __serv_def_len); if (__serv_def_len > MAX_SERVICE_DEFINE_LEN) { read_rule_ret = Maat_read_rule(g_pangu_rt->maat, &ctx->enforce_rules[0], MAAT_RULE_SERV_DEFINE, ctx->enforce_para, ctx->enforce_rules[0].serv_def_len); assert(read_rule_ret == ctx->enforce_rules[0].serv_def_len); } else { strcpy(ctx->enforce_para, ctx->enforce_rules[0].service_defined); } if (hit_cnt > 1) { p = buff; for (i = 0; i < hit_cnt; i++) { p += snprintf(p, sizeof(buff) - (p - buff), "%d:", result[i].config_id); } *p = '\0'; TFE_LOG_INFO(g_pangu_rt->local_logger, "Multiple rules matched: url=%s num=%lu ids=%s execute=%d.", session->req->req_spec.url, hit_cnt, buff, ctx->enforce_rules[0].config_id); } } return ctx->action; } void enforce_control_policy(const struct tfe_stream * stream, const struct tfe_http_session * session, enum tfe_http_event events, const unsigned char * body_frag, size_t frag_size, unsigned int thread_id, struct pangu_http_ctx * ctx) { if(ctx->action==PG_ACTION_NONE||ctx->action==PG_ACTION_MONIT) { //ctx->action changed in http_scan. http_scan(session, events, body_frag, frag_size, ctx); } switch (ctx->action) { case PG_ACTION_NONE: break; case PG_ACTION_MONIT: ATOMIC_INC(&(g_pangu_rt->stat_val[STAT_ACTION_MONIT])); //send log on close. break; case PG_ACTION_REJECT: http_reject(session, events, ctx); ATOMIC_INC(&(g_pangu_rt->stat_val[STAT_ACTION_REJECT])); break; case PG_ACTION_REDIRECT: http_redirect(session, events, ctx); ATOMIC_INC(&(g_pangu_rt->stat_val[STAT_ACTION_REDIRECT])); break; case PG_ACTION_REPLACE: http_replace(stream, session, events, body_frag, frag_size, ctx); ATOMIC_INC(&(g_pangu_rt->stat_val[STAT_ACTION_PRE_REPLACE])); break; case PG_ACTION_WHITELIST: tfe_http_session_detach(session); ATOMIC_INC(&(g_pangu_rt->stat_val[STAT_ACTION_WHITELSIT])); break; default: assert(0); break; } return; } void cache_query(const struct tfe_http_session * session, unsigned int thread_id, struct pangu_http_ctx * ctx) { ctx->f_cache_query=future_create("cache_get", cache_query_on_succ, cache_query_on_fail, ctx); ctx->cache_query_status=async_web_cache_query(g_pangu_rt->cache, thread_id, session->req, ctx->f_cache_query); if(ctx->cache_query_status==WEB_CACHE_QUERING) { ctx->ref_session=tfe_http_session_allow_write(session); tfe_http_session_suspend(ctx->ref_session); } else { future_destroy(ctx->f_cache_query); ctx->f_cache_query=NULL; } } void cache_update(const struct tfe_http_session * session, enum tfe_http_event events, const unsigned char * body_frag, size_t frag_size, unsigned int thread_id, struct pangu_http_ctx * ctx) { if(events & EV_HTTP_RESP_BODY_BEGIN && ctx->cache_query_status == WEB_CACHE_NOT_HIT) { ctx->cache_update_ctx=web_cache_update_start(g_pangu_rt->cache, thread_id, session); } if(events & EV_HTTP_RESP_BODY_CONT && ctx->cache_update_ctx!=NULL) { web_cache_update(ctx->cache_update_ctx, body_frag, frag_size); } if(events & EV_HTTP_RESP_BODY_END && ctx->cache_update_ctx!=NULL) { web_cache_update_end(ctx->cache_update_ctx); ctx->cache_update_ctx=NULL; //printf("cache update success: %s\n", ctx->ref_session->req->req_spec.url); } } void pangu_on_http_begin(const struct tfe_stream * stream, const struct tfe_http_session * session, unsigned int thread_id, void ** pme) { struct pangu_http_ctx * ctx = *(struct pangu_http_ctx **) pme; struct Maat_rule_t result[MAX_SCAN_RESULT]; struct ipaddr sapp_addr; int hit_cnt = 0; assert(ctx == NULL); ATOMIC_INC(&(g_pangu_rt->stat_val[STAT_SESSION])); ctx = pangu_http_ctx_new(thread_id); addr_tfe2sapp(stream->addr, &sapp_addr); hit_cnt = Maat_scan_proto_addr(g_pangu_rt->maat, g_pangu_rt->scan_table_id[PXY_CTRL_IP], &sapp_addr, 0, result, MAX_SCAN_RESULT, &(ctx->mid), (int) thread_id); if (hit_cnt > 0) { ctx->action = decide_ctrl_action(result, hit_cnt, &ctx->enforce_rules, &ctx->n_enforce); } if (ctx->action == PG_ACTION_WHITELIST) { tfe_http_session_detach(session); } *pme = ctx; return; } void pangu_on_http_end(const struct tfe_stream * stream, const struct tfe_http_session * session, unsigned int thread_id, void ** pme) { struct pangu_http_ctx * ctx = *(struct pangu_http_ctx **) pme; int i=0, j=0,ret=0; if(ctx->action == PG_ACTION_REPLACE && ctx->rep_ctx->actually_replaced==0) { for(i=0; i< ctx->n_enforce; i++) { if(ctx->enforce_rules[i].action == PG_ACTION_REPLACE) { if(i+1 > ctx->n_enforce) { memmove(ctx->enforce_rules+i, ctx->enforce_rules+i+1, sizeof(struct Maat_rule_t)); } j++; } } ctx->n_enforce-=j; if(ctx->n_enforce==0) { ctx->action = PG_ACTION_NONE; FREE(&(ctx->enforce_rules)); } } struct pangu_log log_msg = {.stream=stream, .http=session, .result=ctx->enforce_rules, .result_num=ctx->n_enforce}; if (ctx->action != PG_ACTION_NONE&& !(ctx->action == PG_ACTION_REPLACE && ctx->n_enforce==1 && ctx->rep_ctx->actually_replaced==0)) { ret=pangu_send_log(g_pangu_rt->send_logger, &log_msg); ATOMIC_ADD(&(g_pangu_rt->stat_val[STAT_LOG_NUM]), ret); } if(ctx->rep_ctx && ctx->rep_ctx->actually_replaced==0) { ATOMIC_INC(&(g_pangu_rt->stat_val[STAT_ACTION_REPLACE])); } pangu_http_ctx_free(ctx); *pme = NULL; return; } void pangu_on_http_data(const struct tfe_stream * stream, const struct tfe_http_session * session, enum tfe_http_event events, const unsigned char * body_frag, size_t frag_size, unsigned int thread_id, void ** pme) { struct pangu_http_ctx * ctx = *(struct pangu_http_ctx **) pme; if(events & EV_HTTP_REQ_HDR && ctx->resume_from_cache_query && ctx->cache_query_status == WEB_CACHE_HIT) { //resume from cache query. assert(ctx->action==PG_ACTION_NONE); tfe_http_session_detach(session); return; } if(!ctx->resume_from_cache_query) { enforce_control_policy(stream, session, events, body_frag, frag_size,thread_id, ctx); } if(ctx->action != PG_ACTION_NONE) { return; } if(g_pangu_rt->cache_enabled) { if(events & EV_HTTP_REQ_HDR && !ctx->resume_from_cache_query) { cache_query(session, thread_id, ctx); } if(!tfe_http_in_request(events)) { cache_update(session, events, body_frag, frag_size, thread_id, ctx); } } return; } struct tfe_plugin pangu_http_spec = { .symbol=NULL, .type = TFE_PLUGIN_TYPE_BUSINESS, .on_init = pangu_http_init, .on_deinit = NULL, .on_open = NULL, .on_data = NULL, .on_close = NULL, .on_session_begin=pangu_on_http_begin, .on_session_data=pangu_on_http_data, .on_session_end=pangu_on_http_end }; TFE_PLUGIN_REGISTER(pangu_http, pangu_http_spec)