1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2  * contributor license agreements.  See the NOTICE file distributed with
3  * this work for additional information regarding copyright ownership.
4  * The ASF licenses this file to You under the Apache License, Version 2.0
5  * (the "License"); you may not use this file except in compliance with
6  * the License.  You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #include "mod_proxy.h"
17 #include "mod_watchdog.h"
18 #include "ap_slotmem.h"
19 #include "ap_expr.h"
20 #if APR_HAS_THREADS
21 #include "apr_thread_pool.h"
22 #endif
23 #include "http_ssl.h"
24 
25 module AP_MODULE_DECLARE_DATA proxy_hcheck_module;
26 
27 #define HCHECK_WATHCHDOG_NAME ("_proxy_hcheck_")
28 #define HC_THREADPOOL_SIZE (16)
29 
30 /* Why? So we can easily set/clear HC_USE_THREADS during dev testing */
31 #if APR_HAS_THREADS
32 #ifndef HC_USE_THREADS
33 #define HC_USE_THREADS 1
34 #endif
35 #else
36 #define HC_USE_THREADS 0
37 #endif
38 
39 typedef struct {
40     char *name;
41     hcmethod_t method;
42     int passes;
43     int fails;
44     apr_interval_time_t interval;
45     char *hurl;
46     char *hcexpr;
47 } hc_template_t;
48 
49 typedef struct {
50     char *expr;
51     ap_expr_info_t *pexpr;       /* parsed expression */
52 } hc_condition_t;
53 
54 typedef struct {
55     apr_pool_t *p;
56     apr_array_header_t *templates;
57     apr_table_t *conditions;
58     apr_hash_t *hcworkers;
59     server_rec *s;
60 } sctx_t;
61 
62 /* Used in the HC worker via the context field */
63 typedef struct {
64     const char *path;   /* The path of the original worker URL */
65     const char *method; /* Method string for the HTTP/AJP request */
66     const char *req;    /* pre-formatted HTTP/AJP request */
67     proxy_worker *w;    /* Pointer to the actual worker */
68 } wctx_t;
69 
70 typedef struct {
71     apr_pool_t *ptemp;
72     sctx_t *ctx;
73     proxy_balancer *balancer;
74     proxy_worker *worker;
75     proxy_worker *hc;
76     apr_time_t *now;
77 } baton_t;
78 
79 static APR_OPTIONAL_FN_TYPE(ajp_handle_cping_cpong) *ajp_handle_cping_cpong = NULL;
80 
hc_create_config(apr_pool_t * p,server_rec * s)81 static void *hc_create_config(apr_pool_t *p, server_rec *s)
82 {
83     sctx_t *ctx = apr_pcalloc(p, sizeof(sctx_t));
84     ctx->s = s;
85     apr_pool_create(&ctx->p, p);
86     apr_pool_tag(ctx->p, "proxy_hcheck");
87     ctx->templates = apr_array_make(p, 10, sizeof(hc_template_t));
88     ctx->conditions = apr_table_make(p, 10);
89     ctx->hcworkers = apr_hash_make(p);
90     return ctx;
91 }
92 
93 static ap_watchdog_t *watchdog;
94 #if HC_USE_THREADS
95 static apr_thread_pool_t *hctp;
96 static int tpsize;
97 #endif
98 
99 /*
100  * This serves double duty by not only validating (and creating)
101  * the health-check template, but also ties into set_worker_param()
102  * which does the actual setting of worker params in shm.
103  */
set_worker_hc_param(apr_pool_t * p,server_rec * s,proxy_worker * worker,const char * key,const char * val,void * v)104 static const char *set_worker_hc_param(apr_pool_t *p,
105                                     server_rec *s,
106                                     proxy_worker *worker,
107                                     const char *key,
108                                     const char *val,
109                                     void *v)
110 {
111     int ival;
112     hc_template_t *temp;
113     sctx_t *ctx = (sctx_t *) ap_get_module_config(s->module_config,
114                                                   &proxy_hcheck_module);
115     if (!worker && !v) {
116         return "Bad call to set_worker_hc_param()";
117     }
118     if (!ctx) {
119         ctx = hc_create_config(p, s);
120         ap_set_module_config(s->module_config, &proxy_hcheck_module, ctx);
121     }
122     temp = (hc_template_t *)v;
123     if (!strcasecmp(key, "hctemplate")) {
124         hc_template_t *template;
125         template = (hc_template_t *)ctx->templates->elts;
126         for (ival = 0; ival < ctx->templates->nelts; ival++, template++) {
127             if (!ap_cstr_casecmp(template->name, val)) {
128                 if (worker) {
129                     worker->s->method = template->method;
130                     worker->s->interval = template->interval;
131                     worker->s->passes = template->passes;
132                     worker->s->fails = template->fails;
133                     PROXY_STRNCPY(worker->s->hcuri, template->hurl);
134                     PROXY_STRNCPY(worker->s->hcexpr, template->hcexpr);
135                 } else {
136                     temp->method = template->method;
137                     temp->interval = template->interval;
138                     temp->passes = template->passes;
139                     temp->fails = template->fails;
140                     temp->hurl = apr_pstrdup(p, template->hurl);
141                     temp->hcexpr = apr_pstrdup(p, template->hcexpr);
142                 }
143                 return NULL;
144             }
145         }
146         return apr_psprintf(p, "Unknown ProxyHCTemplate name: %s", val);
147     }
148     else if (!strcasecmp(key, "hcmethod")) {
149         proxy_hcmethods_t *method = proxy_hcmethods;
150         for (; method->name; method++) {
151             if (!ap_cstr_casecmp(val, method->name)) {
152                 if (!method->implemented) {
153                     return apr_psprintf(p, "Health check method %s not (yet) implemented",
154                                         val);
155                 }
156                 if (worker) {
157                     worker->s->method = method->method;
158                 } else {
159                     temp->method = method->method;
160                 }
161                 return NULL;
162             }
163         }
164         return "Unknown method";
165     }
166     else if (!strcasecmp(key, "hcinterval")) {
167         apr_interval_time_t hci;
168         apr_status_t rv;
169         rv = ap_timeout_parameter_parse(val, &hci, "s");
170         if (rv != APR_SUCCESS)
171             return "Unparse-able hcinterval setting";
172         if (hci < AP_WD_TM_SLICE)
173             return apr_psprintf(p, "Interval must be a positive value greater than %"
174                                 APR_TIME_T_FMT "ms", apr_time_as_msec(AP_WD_TM_SLICE));
175         if (worker) {
176             worker->s->interval = hci;
177         } else {
178             temp->interval = hci;
179         }
180     }
181     else if (!strcasecmp(key, "hcpasses")) {
182         ival = atoi(val);
183         if (ival < 0)
184             return "Passes must be a positive value";
185         if (worker) {
186             worker->s->passes = ival;
187         } else {
188             temp->passes = ival;
189         }
190     }
191     else if (!strcasecmp(key, "hcfails")) {
192         ival = atoi(val);
193         if (ival < 0)
194             return "Fails must be a positive value";
195         if (worker) {
196             worker->s->fails = ival;
197         } else {
198             temp->fails = ival;
199         }
200     }
201     else if (!strcasecmp(key, "hcuri")) {
202         if (strlen(val) >= sizeof(worker->s->hcuri))
203             return apr_psprintf(p, "Health check uri length must be < %d characters",
204                     (int)sizeof(worker->s->hcuri));
205         if (worker) {
206             PROXY_STRNCPY(worker->s->hcuri, val);
207         } else {
208             temp->hurl = apr_pstrdup(p, val);
209         }
210     }
211     else if (!strcasecmp(key, "hcexpr")) {
212         hc_condition_t *cond;
213         cond = (hc_condition_t *)apr_table_get(ctx->conditions, val);
214         if (!cond) {
215             return apr_psprintf(p, "Unknown health check condition expr: %s", val);
216         }
217         /* This check is wonky... a known expr can't be this big. Check anyway */
218         if (strlen(val) >= sizeof(worker->s->hcexpr))
219             return apr_psprintf(p, "Health check uri length must be < %d characters",
220                     (int)sizeof(worker->s->hcexpr));
221         if (worker) {
222             PROXY_STRNCPY(worker->s->hcexpr, val);
223         } else {
224             temp->hcexpr = apr_pstrdup(p, val);
225         }
226     }
227   else {
228         return "unknown Worker hcheck parameter";
229     }
230     return NULL;
231 }
232 
set_hc_condition(cmd_parms * cmd,void * dummy,const char * arg)233 static const char *set_hc_condition(cmd_parms *cmd, void *dummy, const char *arg)
234 {
235     char *name = NULL;
236     char *expr;
237     sctx_t *ctx;
238     hc_condition_t *cond;
239 
240     const char *err = ap_check_cmd_context(cmd, NOT_IN_HTACCESS);
241     if (err)
242         return err;
243     ctx = (sctx_t *) ap_get_module_config(cmd->server->module_config,
244                                           &proxy_hcheck_module);
245 
246     name = ap_getword_conf(cmd->pool, &arg);
247     if (!*name) {
248         return apr_pstrcat(cmd->temp_pool, "Missing expression name for ",
249                            cmd->cmd->name, NULL);
250     }
251     if (strlen(name) > (PROXY_WORKER_MAX_SCHEME_SIZE - 1)) {
252         return apr_psprintf(cmd->temp_pool, "Expression name limited to %d characters",
253                            (PROXY_WORKER_MAX_SCHEME_SIZE - 1));
254     }
255     /* get expr. Allow fancy new {...} quoting style */
256     expr = ap_getword_conf2(cmd->temp_pool, &arg);
257     if (!*expr) {
258         return apr_pstrcat(cmd->temp_pool, "Missing expression for ",
259                            cmd->cmd->name, NULL);
260     }
261     cond = apr_palloc(cmd->pool, sizeof(hc_condition_t));
262     cond->pexpr = ap_expr_parse_cmd(cmd, expr, 0, &err, NULL);
263     if (err) {
264         return apr_psprintf(cmd->temp_pool, "Could not parse expression \"%s\": %s",
265                             expr, err);
266     }
267     cond->expr = apr_pstrdup(cmd->pool, expr);
268     apr_table_setn(ctx->conditions, name, (void *)cond);
269     expr = ap_getword_conf(cmd->temp_pool, &arg);
270     if (*expr) {
271         return "error: extra parameter(s)";
272     }
273     return NULL;
274 }
275 
set_hc_template(cmd_parms * cmd,void * dummy,const char * arg)276 static const char *set_hc_template(cmd_parms *cmd, void *dummy, const char *arg)
277 {
278     char *name = NULL;
279     char *word, *val;
280     hc_template_t *template;
281     sctx_t *ctx;
282 
283     const char *err = ap_check_cmd_context(cmd, NOT_IN_HTACCESS);
284     if (err)
285         return err;
286     ctx = (sctx_t *) ap_get_module_config(cmd->server->module_config,
287                                           &proxy_hcheck_module);
288 
289     name = ap_getword_conf(cmd->temp_pool, &arg);
290     if (!*name) {
291         return apr_pstrcat(cmd->temp_pool, "Missing template name for ",
292                            cmd->cmd->name, NULL);
293     }
294 
295     template = (hc_template_t *)apr_array_push(ctx->templates);
296 
297     template->name = apr_pstrdup(cmd->pool, name);
298     template->method = template->passes = template->fails = 1;
299     template->interval = apr_time_from_sec(HCHECK_WATHCHDOG_DEFAULT_INTERVAL);
300     template->hurl = NULL;
301     template->hcexpr = NULL;
302     while (*arg) {
303         word = ap_getword_conf(cmd->pool, &arg);
304         val = strchr(word, '=');
305         if (!val) {
306             return "Invalid ProxyHCTemplate parameter. Parameter must be "
307                    "in the form 'key=value'";
308         }
309         else
310             *val++ = '\0';
311         err = set_worker_hc_param(cmd->pool, ctx->s, NULL, word, val, template);
312 
313         if (err) {
314             /* get rid of recently pushed (bad) template */
315             apr_array_pop(ctx->templates);
316             return apr_pstrcat(cmd->temp_pool, "ProxyHCTemplate: ", err, " ", word, "=", val, "; ", name, NULL);
317         }
318         /* No error means we have a valid template */
319     }
320     return NULL;
321 }
322 
323 #if HC_USE_THREADS
set_hc_tpsize(cmd_parms * cmd,void * dummy,const char * arg)324 static const char *set_hc_tpsize (cmd_parms *cmd, void *dummy, const char *arg)
325 {
326     const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
327     if (err)
328         return err;
329 
330     tpsize = atoi(arg);
331     if (tpsize < 0)
332         return "Invalid ProxyHCTPsize parameter. Parameter must be "
333                ">= 0";
334     return NULL;
335 }
336 #endif
337 
338 /*
339  * Create a dummy request rec, simply so we can use ap_expr.
340  * Use our short-lived pool for bucket_alloc so that we can simply move
341  * buckets and use them after the backend connection is released.
342  */
create_request_rec(apr_pool_t * p,server_rec * s,proxy_balancer * balancer,const char * method)343 static request_rec *create_request_rec(apr_pool_t *p, server_rec *s,
344                                        proxy_balancer *balancer,
345                                        const char *method)
346 {
347     request_rec *r;
348 
349     r = apr_pcalloc(p, sizeof(request_rec));
350     r->pool            = p;
351     r->server          = s;
352 
353     r->per_dir_config = r->server->lookup_defaults;
354     if (balancer->section_config) {
355         r->per_dir_config = ap_merge_per_dir_configs(r->pool,
356                                                      r->per_dir_config,
357                                                      balancer->section_config);
358     }
359 
360     r->proxyreq        = PROXYREQ_RESPONSE;
361 
362     r->user            = NULL;
363     r->ap_auth_type    = NULL;
364 
365     r->allowed_methods = ap_make_method_list(p, 2);
366 
367     r->headers_in      = apr_table_make(r->pool, 1);
368     r->trailers_in     = apr_table_make(r->pool, 1);
369     r->subprocess_env  = apr_table_make(r->pool, 25);
370     r->headers_out     = apr_table_make(r->pool, 12);
371     r->err_headers_out = apr_table_make(r->pool, 5);
372     r->trailers_out    = apr_table_make(r->pool, 1);
373     r->notes           = apr_table_make(r->pool, 5);
374 
375     r->request_config  = ap_create_request_config(r->pool);
376     /* Must be set before we run create request hook */
377 
378     r->sent_bodyct     = 0;                      /* bytect isn't for body */
379 
380     r->read_length     = 0;
381     r->read_body       = REQUEST_NO_BODY;
382 
383     r->status          = HTTP_OK;  /* Until further notice */
384     r->the_request     = NULL;
385 
386     /* Begin by presuming any module can make its own path_info assumptions,
387      * until some module interjects and changes the value.
388      */
389     r->used_path_info = AP_REQ_DEFAULT_PATH_INFO;
390 
391 
392     /* Time to populate r with the data we have. */
393     r->method = method;
394     /* Provide quick information about the request method as soon as known */
395     r->method_number = ap_method_number_of(r->method);
396     if (r->method_number == M_OPTIONS
397             || (r->method_number == M_GET && r->method[0] == 'H')) {
398         r->header_only = 1;
399     }
400     else {
401         r->header_only = 0;
402     }
403 
404     r->protocol = "HTTP/1.0";
405     r->proto_num = HTTP_VERSION(1, 0);
406 
407     r->hostname = NULL;
408 
409     return r;
410 }
411 
set_request_connection(request_rec * r,conn_rec * conn)412 static void set_request_connection(request_rec *r, conn_rec *conn)
413 {
414     conn->bucket_alloc = apr_bucket_alloc_create(r->pool);
415     r->connection = conn;
416 
417     r->kept_body = apr_brigade_create(r->pool, conn->bucket_alloc);
418     r->output_filters = r->proto_output_filters = conn->output_filters;
419     r->input_filters = r->proto_input_filters = conn->input_filters;
420 
421     r->useragent_addr = conn->client_addr;
422     r->useragent_ip = conn->client_ip;
423 }
424 
create_hcheck_req(wctx_t * wctx,proxy_worker * hc,apr_pool_t * p)425 static void create_hcheck_req(wctx_t *wctx, proxy_worker *hc,
426                               apr_pool_t *p)
427 {
428     char *req = NULL;
429     const char *method = NULL;
430     switch (hc->s->method) {
431         case OPTIONS:
432             method = "OPTIONS";
433             req = apr_psprintf(p,
434                                "OPTIONS * HTTP/1.0\r\n"
435                                "Host: %s:%d\r\n"
436                                "\r\n",
437                                hc->s->hostname_ex, (int)hc->s->port);
438             break;
439 
440         case HEAD:
441             method = "HEAD";
442             /* fallthru */
443         case GET:
444             if (!method) { /* did we fall thru? If not, we are GET */
445                 method = "GET";
446             }
447             req = apr_psprintf(p,
448                                "%s %s%s%s HTTP/1.0\r\n"
449                                "Host: %s:%d\r\n"
450                                "\r\n",
451                                method,
452                                (wctx->path ? wctx->path : ""),
453                                (wctx->path && *hc->s->hcuri ? "/" : "" ),
454                                (*hc->s->hcuri ? hc->s->hcuri : ""),
455                                hc->s->hostname_ex, (int)hc->s->port);
456             break;
457 
458         default:
459             break;
460     }
461     wctx->req = req;
462     wctx->method = method;
463 }
464 
hc_get_hcworker(sctx_t * ctx,proxy_worker * worker,apr_pool_t * p)465 static proxy_worker *hc_get_hcworker(sctx_t *ctx, proxy_worker *worker,
466                                      apr_pool_t *p)
467 {
468     proxy_worker *hc = NULL;
469     apr_port_t port;
470 
471     hc = (proxy_worker *)apr_hash_get(ctx->hcworkers, &worker, sizeof worker);
472     if (!hc) {
473         apr_uri_t uri;
474         apr_status_t rv;
475         const char *url = worker->s->name_ex;
476         wctx_t *wctx = apr_pcalloc(ctx->p, sizeof(wctx_t));
477 
478         port = (worker->s->port ? worker->s->port
479                                 : ap_proxy_port_of_scheme(worker->s->scheme));
480         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ctx->s, APLOGNO(03248)
481                      "Creating hc worker %pp for %s://%s:%d",
482                      worker, worker->s->scheme, worker->s->hostname_ex,
483                      (int)port);
484 
485         ap_proxy_define_worker(ctx->p, &hc, NULL, NULL, worker->s->name_ex, 0);
486         apr_snprintf(hc->s->name, sizeof hc->s->name, "%pp", worker);
487         apr_snprintf(hc->s->name_ex, sizeof hc->s->name_ex, "%pp", worker);
488         PROXY_STRNCPY(hc->s->hostname, worker->s->hostname); /* for compatibility */
489         PROXY_STRNCPY(hc->s->hostname_ex, worker->s->hostname_ex);
490         PROXY_STRNCPY(hc->s->scheme,   worker->s->scheme);
491         PROXY_STRNCPY(hc->s->hcuri,    worker->s->hcuri);
492         PROXY_STRNCPY(hc->s->hcexpr,   worker->s->hcexpr);
493         hc->hash.def = hc->s->hash.def = ap_proxy_hashfunc(hc->s->name_ex,
494                                                            PROXY_HASHFUNC_DEFAULT);
495         hc->hash.fnv = hc->s->hash.fnv = ap_proxy_hashfunc(hc->s->name_ex,
496                                                            PROXY_HASHFUNC_FNV);
497         hc->s->port = port;
498         if (worker->s->conn_timeout_set) {
499             hc->s->conn_timeout_set = worker->s->conn_timeout_set;
500             hc->s->conn_timeout = worker->s->conn_timeout;
501         }
502         /* Do not disable worker in case of errors */
503         hc->s->status |= PROXY_WORKER_IGNORE_ERRORS;
504         /* Mark as the "generic" worker */
505         hc->s->status |= PROXY_WORKER_GENERIC;
506         ap_proxy_initialize_worker(hc, ctx->s, ctx->p);
507         hc->s->is_address_reusable = worker->s->is_address_reusable;
508         hc->s->disablereuse = worker->s->disablereuse;
509         hc->s->method = worker->s->method;
510         rv = apr_uri_parse(p, url, &uri);
511         if (rv == APR_SUCCESS) {
512             wctx->path = apr_pstrdup(ctx->p, uri.path);
513         }
514         wctx->w = worker;
515         create_hcheck_req(wctx, hc, ctx->p);
516         hc->context = wctx;
517         apr_hash_set(ctx->hcworkers, &worker, sizeof worker, hc);
518     }
519     /* This *could* have changed via the Balancer Manager */
520     /* TODO */
521     if (hc->s->method != worker->s->method) {
522         wctx_t *wctx = hc->context;
523         port = (worker->s->port ? worker->s->port
524                                 : ap_proxy_port_of_scheme(worker->s->scheme));
525         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ctx->s, APLOGNO(03311)
526                      "Updating hc worker %pp for %s://%s:%d",
527                      worker, worker->s->scheme, worker->s->hostname_ex,
528                      (int)port);
529         hc->s->method = worker->s->method;
530         create_hcheck_req(wctx, hc, ctx->p);
531     }
532     return hc;
533 }
534 
hc_determine_connection(sctx_t * ctx,proxy_worker * worker,apr_sockaddr_t ** addr,apr_pool_t * p)535 static int hc_determine_connection(sctx_t *ctx, proxy_worker *worker,
536                                    apr_sockaddr_t **addr, apr_pool_t *p)
537 {
538     apr_status_t rv = APR_SUCCESS;
539     /*
540      * normally, this is done in ap_proxy_determine_connection().
541      * TODO: Look at using ap_proxy_determine_connection() with a
542      * fake request_rec
543      */
544     if (worker->cp->addr) {
545         *addr = worker->cp->addr;
546     }
547     else {
548         rv = apr_sockaddr_info_get(addr, worker->s->hostname_ex,
549                                    APR_UNSPEC, worker->s->port, 0, p);
550         if (rv != APR_SUCCESS) {
551             ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, ctx->s, APLOGNO(03249)
552                          "DNS lookup failure for: %s:%d",
553                          worker->s->hostname_ex, (int)worker->s->port);
554         }
555     }
556     return (rv == APR_SUCCESS ? OK : !OK);
557 }
558 
hc_init_worker(sctx_t * ctx,proxy_worker * worker)559 static apr_status_t hc_init_worker(sctx_t *ctx, proxy_worker *worker)
560 {
561     apr_status_t rv = APR_SUCCESS;
562     /*
563      * Since this is the watchdog, workers never actually handle a
564      * request here, and so the local data isn't initialized (of
565      * course, the shared memory is). So we need to bootstrap
566      * worker->cp. Note, we only need do this once.
567      */
568     if (!worker->cp) {
569         rv = ap_proxy_initialize_worker(worker, ctx->s, ctx->p);
570         if (rv != APR_SUCCESS) {
571             ap_log_error(APLOG_MARK, APLOG_EMERG, rv, ctx->s, APLOGNO(03250) "Cannot init worker");
572             return rv;
573         }
574         if (worker->s->is_address_reusable && !worker->s->disablereuse &&
575                 hc_determine_connection(ctx, worker, &worker->cp->addr,
576                                         worker->cp->pool) != OK) {
577             rv = APR_EGENERAL;
578         }
579     }
580     return rv;
581 }
582 
backend_cleanup(const char * proxy_function,proxy_conn_rec * backend,server_rec * s,int status)583 static apr_status_t backend_cleanup(const char *proxy_function, proxy_conn_rec *backend,
584                                     server_rec *s, int status)
585 {
586     if (backend) {
587         backend->close = 1;
588         ap_proxy_release_connection(proxy_function, backend, s);
589         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03251)
590                          "Health check %s Status (%d) for %s.",
591                          ap_proxy_show_hcmethod(backend->worker->s->method),
592                          status,
593                          backend->worker->s->name_ex);
594     }
595     if (status != OK) {
596         return APR_EGENERAL;
597     }
598     return APR_SUCCESS;
599 }
600 
hc_get_backend(const char * proxy_function,proxy_conn_rec ** backend,proxy_worker * hc,sctx_t * ctx,apr_pool_t * ptemp)601 static int hc_get_backend(const char *proxy_function, proxy_conn_rec **backend,
602                           proxy_worker *hc, sctx_t *ctx, apr_pool_t *ptemp)
603 {
604     int status;
605     status = ap_proxy_acquire_connection(proxy_function, backend, hc, ctx->s);
606     if (status == OK) {
607         (*backend)->addr = hc->cp->addr;
608         (*backend)->hostname = hc->s->hostname_ex;
609         if (strcmp(hc->s->scheme, "https") == 0 || strcmp(hc->s->scheme, "wss") == 0 ) {
610             if (!ap_ssl_has_outgoing_handlers()) {
611                 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, ctx->s, APLOGNO(03252)
612                               "mod_ssl not configured?");
613                 return !OK;
614             }
615             (*backend)->is_ssl = 1;
616         }
617 
618     }
619     return hc_determine_connection(ctx, hc, &(*backend)->addr, ptemp);
620 }
621 
hc_check_cping(baton_t * baton,apr_thread_t * thread)622 static apr_status_t hc_check_cping(baton_t *baton, apr_thread_t *thread)
623 {
624     int status;
625     sctx_t *ctx = baton->ctx;
626     proxy_worker *hc = baton->hc;
627     proxy_conn_rec *backend = NULL;
628     apr_pool_t *ptemp = baton->ptemp;
629     request_rec *r;
630     apr_interval_time_t timeout;
631 
632     if (!ajp_handle_cping_cpong) {
633         return APR_ENOTIMPL;
634     }
635 
636     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, baton->ctx->s, "HCCPING starting");
637     if ((status = hc_get_backend("HCCPING", &backend, hc, ctx, baton->ptemp)) != OK) {
638         return backend_cleanup("HCCPING", backend, ctx->s, status);
639     }
640     if ((status = ap_proxy_connect_backend("HCCPING", backend, hc, ctx->s)) != OK) {
641         return backend_cleanup("HCCPING", backend, ctx->s, status);
642     }
643     r = create_request_rec(ptemp, ctx->s, baton->balancer, "CPING");
644     if ((status = ap_proxy_connection_create_ex("HCCPING", backend, r)) != OK) {
645         return backend_cleanup("HCCPING", backend, ctx->s, status);
646     }
647     set_request_connection(r, backend->connection);
648     backend->connection->current_thread = thread;
649 
650     if (hc->s->ping_timeout_set) {
651         timeout = hc->s->ping_timeout;
652     } else if ( hc->s->conn_timeout_set) {
653         timeout = hc->s->conn_timeout;
654     } else if ( hc->s->timeout_set) {
655         timeout = hc->s->timeout;
656     } else {
657         /* default to socket timeout */
658         apr_socket_timeout_get(backend->sock, &timeout);
659     }
660     status = ajp_handle_cping_cpong(backend->sock, r, timeout);
661     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, baton->ctx->s, "HCCPING done %d", status);
662     return backend_cleanup("HCCPING", backend, ctx->s, status);
663 }
664 
hc_check_tcp(baton_t * baton)665 static apr_status_t hc_check_tcp(baton_t *baton)
666 {
667     int status;
668     sctx_t *ctx = baton->ctx;
669     proxy_worker *hc = baton->hc;
670     proxy_conn_rec *backend = NULL;
671 
672     status = hc_get_backend("HCTCP", &backend, hc, ctx, baton->ptemp);
673     if (status == OK) {
674         status = ap_proxy_connect_backend("HCTCP", backend, hc, ctx->s);
675         /* does an unconditional ap_proxy_is_socket_connected() */
676     }
677     return backend_cleanup("HCTCP", backend, ctx->s, status);
678 }
679 
hc_send(request_rec * r,const char * out,apr_bucket_brigade * bb)680 static int hc_send(request_rec *r, const char *out, apr_bucket_brigade *bb)
681 {
682     apr_status_t rv;
683     conn_rec *c = r->connection;
684     apr_bucket_alloc_t *ba = c->bucket_alloc;
685     ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, r->server, "%s", out);
686     APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_pool_create(out, strlen(out),
687                                                        r->pool, ba));
688     APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_flush_create(ba));
689     rv = ap_pass_brigade(c->output_filters, bb);
690     apr_brigade_cleanup(bb);
691     return (rv) ? !OK : OK;
692 }
693 
hc_read_headers(request_rec * r)694 static int hc_read_headers(request_rec *r)
695 {
696     char buffer[HUGE_STRING_LEN];
697     int len;
698     const char *ct;
699 
700     len = ap_getline(buffer, sizeof(buffer), r, 1);
701     if (len <= 0) {
702         return !OK;
703     }
704     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, APLOGNO(03254)
705                  "%.*s", len, buffer);
706     /* for the below, see ap_proxy_http_process_response() */
707     if (apr_date_checkmask(buffer, "HTTP/#.# ###*")) {
708         int major;
709         char keepchar;
710         int proxy_status = OK;
711         const char *proxy_status_line = NULL;
712 
713         major = buffer[5] - '0';
714         if ((major != 1) || (len >= sizeof(buffer)-1)) {
715             return !OK;
716         }
717 
718         keepchar = buffer[12];
719         buffer[12] = '\0';
720         proxy_status = atoi(&buffer[9]);
721         if (keepchar != '\0') {
722             buffer[12] = keepchar;
723         } else {
724             buffer[12] = ' ';
725             buffer[13] = '\0';
726         }
727         proxy_status_line = apr_pstrdup(r->pool, &buffer[9]);
728         r->status = proxy_status;
729         r->status_line = proxy_status_line;
730     } else {
731         return !OK;
732     }
733 
734     /* OK, 1st line is OK... scarf in the headers */
735     while ((len = ap_getline(buffer, sizeof(buffer), r, 1)) > 0) {
736         char *value, *end;
737         ap_log_error(APLOG_MARK, APLOG_TRACE7, 0, r->server, "%.*s",
738                      len, buffer);
739         if (!(value = strchr(buffer, ':'))) {
740             return !OK;
741         }
742         *value = '\0';
743         ++value;
744         while (apr_isspace(*value))
745             ++value;            /* Skip to start of value   */
746         for (end = &value[strlen(value)-1]; end > value && apr_isspace(*end); --end)
747             *end = '\0';
748         apr_table_add(r->headers_out, buffer, value);
749     }
750 
751     /* Set the Content-Type for the request if set */
752     if ((ct = apr_table_get(r->headers_out, "Content-Type")) != NULL)
753         ap_set_content_type(r, ct);
754 
755     return OK;
756 }
757 
hc_read_body(request_rec * r,apr_bucket_brigade * bb)758 static int hc_read_body(request_rec *r, apr_bucket_brigade *bb)
759 {
760     apr_status_t rv = APR_SUCCESS;
761     int seen_eos = 0;
762 
763     do {
764         apr_size_t len = HUGE_STRING_LEN;
765 
766         apr_brigade_cleanup(bb);
767         rv = ap_get_brigade(r->proto_input_filters, bb, AP_MODE_READBYTES,
768                             APR_BLOCK_READ, len);
769 
770         if (rv != APR_SUCCESS) {
771             if (APR_STATUS_IS_EOF(rv)) {
772                 rv = APR_SUCCESS;
773                 break;
774             }
775             ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, r->server, APLOGNO(03300)
776                           "Error reading response body");
777             break;
778         }
779 
780         while (!APR_BRIGADE_EMPTY(bb)) {
781             apr_bucket *bucket = APR_BRIGADE_FIRST(bb);
782             if (APR_BUCKET_IS_EOS(bucket)) {
783                 seen_eos = 1;
784                 break;
785             }
786             if (APR_BUCKET_IS_FLUSH(bucket)) {
787                 apr_bucket_delete(bucket);
788                 continue;
789             }
790             APR_BUCKET_REMOVE(bucket);
791             APR_BRIGADE_INSERT_TAIL(r->kept_body, bucket);
792         }
793     }
794     while (!seen_eos);
795     apr_brigade_cleanup(bb);
796     return (rv == APR_SUCCESS ? OK : !OK);
797 }
798 
799 /*
800  * Send the HTTP OPTIONS, HEAD or GET request to the backend
801  * server associated w/ worker. If we have Conditions,
802  * then apply those to the resulting response, otherwise
803  * any status code 2xx or 3xx is considered "passing"
804  */
hc_check_http(baton_t * baton,apr_thread_t * thread)805 static apr_status_t hc_check_http(baton_t *baton, apr_thread_t *thread)
806 {
807     int status;
808     proxy_conn_rec *backend = NULL;
809     sctx_t *ctx = baton->ctx;
810     proxy_worker *hc = baton->hc;
811     proxy_worker *worker = baton->worker;
812     apr_pool_t *ptemp = baton->ptemp;
813     request_rec *r;
814     wctx_t *wctx;
815     hc_condition_t *cond;
816     apr_bucket_brigade *bb;
817 
818     wctx = (wctx_t *)hc->context;
819     if (!wctx->req || !wctx->method) {
820         return APR_ENOTIMPL;
821     }
822 
823     if ((status = hc_get_backend("HCOH", &backend, hc, ctx, ptemp)) != OK) {
824         return backend_cleanup("HCOH", backend, ctx->s, status);
825     }
826     if ((status = ap_proxy_connect_backend("HCOH", backend, hc, ctx->s)) != OK) {
827         return backend_cleanup("HCOH", backend, ctx->s, status);
828     }
829 
830     r = create_request_rec(ptemp, ctx->s, baton->balancer, wctx->method);
831     if ((status = ap_proxy_connection_create_ex("HCOH", backend, r)) != OK) {
832         return backend_cleanup("HCOH", backend, ctx->s, status);
833     }
834     set_request_connection(r, backend->connection);
835     backend->connection->current_thread = thread;
836 
837     bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
838 
839     if ((status = hc_send(r, wctx->req, bb)) != OK) {
840         return backend_cleanup("HCOH", backend, ctx->s, status);
841     }
842     if ((status = hc_read_headers(r)) != OK) {
843         return backend_cleanup("HCOH", backend, ctx->s, status);
844     }
845     if (!r->header_only) {
846         apr_table_t *saved_headers_in = r->headers_in;
847         r->headers_in = apr_table_copy(r->pool, r->headers_out);
848         ap_proxy_pre_http_request(backend->connection, r);
849         status = hc_read_body(r, bb);
850         r->headers_in = saved_headers_in;
851         if (status != OK) {
852             return backend_cleanup("HCOH", backend, ctx->s, status);
853         }
854         r->trailers_out = apr_table_copy(r->pool, r->trailers_in);
855     }
856 
857     if (*worker->s->hcexpr &&
858             (cond = (hc_condition_t *)apr_table_get(ctx->conditions, worker->s->hcexpr)) != NULL) {
859         const char *err;
860         int ok = ap_expr_exec(r, cond->pexpr, &err);
861         if (ok > 0) {
862             ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, ctx->s,
863                          "Condition %s for %s (%s): passed", worker->s->hcexpr,
864                          hc->s->name_ex, worker->s->name_ex);
865         } else if (ok < 0 || err) {
866             ap_log_error(APLOG_MARK, APLOG_INFO, 0, ctx->s, APLOGNO(03301)
867                          "Error on checking condition %s for %s (%s): %s", worker->s->hcexpr,
868                          hc->s->name_ex, worker->s->name_ex, err);
869             status = !OK;
870         } else {
871             ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, ctx->s,
872                          "Condition %s for %s (%s) : failed", worker->s->hcexpr,
873                          hc->s->name_ex, worker->s->name_ex);
874             status = !OK;
875         }
876     } else if (r->status < 200 || r->status > 399) {
877         ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, ctx->s,
878                      "Response status %i for %s (%s): failed", r->status,
879                      hc->s->name_ex, worker->s->name_ex);
880         status = !OK;
881     }
882     return backend_cleanup("HCOH", backend, ctx->s, status);
883 }
884 
hc_check(apr_thread_t * thread,void * b)885 static void * APR_THREAD_FUNC hc_check(apr_thread_t *thread, void *b)
886 {
887     baton_t *baton = (baton_t *)b;
888     server_rec *s = baton->ctx->s;
889     proxy_worker *worker = baton->worker;
890     proxy_worker *hc = baton->hc;
891     apr_time_t now;
892     apr_status_t rv;
893 
894     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03256)
895                  "%sHealth checking %s", (thread ? "Threaded " : ""),
896                  worker->s->name_ex);
897 
898     if (hc->s->method == TCP) {
899         rv = hc_check_tcp(baton);
900     }
901     else if (hc->s->method == CPING) {
902         rv = hc_check_cping(baton, thread);
903     }
904     else {
905         rv = hc_check_http(baton, thread);
906     }
907 
908     now = apr_time_now();
909     if (rv == APR_ENOTIMPL) {
910         ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(03257)
911                          "Somehow tried to use unimplemented hcheck method: %d",
912                          (int)hc->s->method);
913     }
914     /* what state are we in ? */
915     else if (PROXY_WORKER_IS_HCFAILED(worker)) {
916         if (rv == APR_SUCCESS) {
917             worker->s->pcount += 1;
918             if (worker->s->pcount >= worker->s->passes) {
919                 ap_proxy_set_wstatus(PROXY_WORKER_HC_FAIL_FLAG, 0, worker);
920                 ap_proxy_set_wstatus(PROXY_WORKER_IN_ERROR_FLAG, 0, worker);
921                 worker->s->pcount = 0;
922                 ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(03302)
923                              "%sHealth check ENABLING %s", (thread ? "Threaded " : ""),
924                              worker->s->name_ex);
925 
926             }
927         }
928     }
929     else {
930         if (rv != APR_SUCCESS) {
931             worker->s->error_time = now;
932             worker->s->fcount += 1;
933             if (worker->s->fcount >= worker->s->fails) {
934                 ap_proxy_set_wstatus(PROXY_WORKER_HC_FAIL_FLAG, 1, worker);
935                 worker->s->fcount = 0;
936                 ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(03303)
937                              "%sHealth check DISABLING %s", (thread ? "Threaded " : ""),
938                              worker->s->name_ex);
939             }
940         }
941     }
942     if (baton->now) {
943         *baton->now = now;
944     }
945     apr_pool_destroy(baton->ptemp);
946     worker->s->updated = now;
947 
948     return NULL;
949 }
950 
hc_watchdog_callback(int state,void * data,apr_pool_t * pool)951 static apr_status_t hc_watchdog_callback(int state, void *data,
952                                          apr_pool_t *pool)
953 {
954     apr_status_t rv = APR_SUCCESS;
955     proxy_balancer *balancer;
956     sctx_t *ctx = (sctx_t *)data;
957     server_rec *s = ctx->s;
958     proxy_server_conf *conf;
959 
960     switch (state) {
961         case AP_WATCHDOG_STATE_STARTING:
962             ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03258)
963                          "%s watchdog started.",
964                          HCHECK_WATHCHDOG_NAME);
965 #if HC_USE_THREADS
966             if (tpsize && hctp == NULL) {
967                 rv =  apr_thread_pool_create(&hctp, tpsize,
968                                              tpsize, ctx->p);
969                 if (rv != APR_SUCCESS) {
970                     ap_log_error(APLOG_MARK, APLOG_INFO, rv, s, APLOGNO(03312)
971                                  "apr_thread_pool_create() with %d threads failed",
972                                  tpsize);
973                     /* we can continue on without the threadpools */
974                     hctp = NULL;
975                 } else {
976                     ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(03313)
977                                  "apr_thread_pool_create() with %d threads succeeded",
978                                  tpsize);
979                 }
980             } else {
981                 ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(03314)
982                              "Skipping apr_thread_pool_create()");
983                 hctp = NULL;
984             }
985 #endif
986             break;
987 
988         case AP_WATCHDOG_STATE_RUNNING:
989             /* loop thru all workers */
990             if (s) {
991                 int i;
992                 conf = (proxy_server_conf *) ap_get_module_config(s->module_config, &proxy_module);
993                 balancer = (proxy_balancer *)conf->balancers->elts;
994                 ctx->s = s;
995                 for (i = 0; i < conf->balancers->nelts; i++, balancer++) {
996                     int n;
997                     apr_time_t now;
998                     proxy_worker **workers;
999                     proxy_worker *worker;
1000                     /* Have any new balancers or workers been added dynamically? */
1001                     ap_proxy_sync_balancer(balancer, s, conf);
1002                     workers = (proxy_worker **)balancer->workers->elts;
1003                     now = apr_time_now();
1004                     for (n = 0; n < balancer->workers->nelts; n++) {
1005                         worker = *workers;
1006                         if (!PROXY_WORKER_IS(worker, PROXY_WORKER_STOPPED) &&
1007                             (worker->s->method != NONE) &&
1008                             (worker->s->updated != 0) &&
1009                             (now > worker->s->updated + worker->s->interval)) {
1010                             baton_t *baton;
1011                             apr_pool_t *ptemp;
1012 
1013                             ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s,
1014                                          "Checking %s worker: %s  [%d] (%pp)", balancer->s->name,
1015                                          worker->s->name_ex, worker->s->method, worker);
1016 
1017                             if ((rv = hc_init_worker(ctx, worker)) != APR_SUCCESS) {
1018                                 worker->s->updated = now;
1019                                 return rv;
1020                             }
1021                             worker->s->updated = 0;
1022 
1023                             /* This pool has the lifetime of the check */
1024                             apr_pool_create(&ptemp, ctx->p);
1025                             apr_pool_tag(ptemp, "hc_request");
1026                             baton = apr_pcalloc(ptemp, sizeof(baton_t));
1027                             baton->ctx = ctx;
1028                             baton->balancer = balancer;
1029                             baton->worker = worker;
1030                             baton->ptemp = ptemp;
1031                             baton->hc = hc_get_hcworker(ctx, worker, ptemp);
1032 #if HC_USE_THREADS
1033                             if (hctp) {
1034                                 apr_thread_pool_push(hctp, hc_check, (void *)baton,
1035                                                      APR_THREAD_TASK_PRIORITY_NORMAL,
1036                                                      NULL);
1037                             }
1038                             else
1039 #endif
1040                             {
1041                                 baton->now = &now;
1042                                 hc_check(NULL, baton);
1043                             }
1044                         }
1045                         workers++;
1046                     }
1047                 }
1048             }
1049             break;
1050 
1051         case AP_WATCHDOG_STATE_STOPPING:
1052             ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03261)
1053                          "stopping %s watchdog.",
1054                          HCHECK_WATHCHDOG_NAME);
1055 #if HC_USE_THREADS
1056             if (hctp) {
1057                 rv =  apr_thread_pool_destroy(hctp);
1058                 if (rv != APR_SUCCESS) {
1059                     ap_log_error(APLOG_MARK, APLOG_INFO, rv, s, APLOGNO(03315)
1060                                  "apr_thread_pool_destroy() failed");
1061                 }
1062                 hctp = NULL;
1063             }
1064 #endif
1065             break;
1066     }
1067     return rv;
1068 }
hc_pre_config(apr_pool_t * pconf,apr_pool_t * plog,apr_pool_t * ptemp)1069 static int hc_pre_config(apr_pool_t *pconf, apr_pool_t *plog,
1070                          apr_pool_t *ptemp)
1071 {
1072 #if HC_USE_THREADS
1073     hctp = NULL;
1074     tpsize = HC_THREADPOOL_SIZE;
1075 #endif
1076     return OK;
1077 }
hc_post_config(apr_pool_t * p,apr_pool_t * plog,apr_pool_t * ptemp,server_rec * main_s)1078 static int hc_post_config(apr_pool_t *p, apr_pool_t *plog,
1079                        apr_pool_t *ptemp, server_rec *main_s)
1080 {
1081     apr_status_t rv;
1082     server_rec *s = main_s;
1083 
1084     APR_OPTIONAL_FN_TYPE(ap_watchdog_get_instance) *hc_watchdog_get_instance;
1085     APR_OPTIONAL_FN_TYPE(ap_watchdog_register_callback) *hc_watchdog_register_callback;
1086 
1087     if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG) {
1088         return OK;
1089     }
1090     hc_watchdog_get_instance = APR_RETRIEVE_OPTIONAL_FN(ap_watchdog_get_instance);
1091     hc_watchdog_register_callback = APR_RETRIEVE_OPTIONAL_FN(ap_watchdog_register_callback);
1092     if (!hc_watchdog_get_instance || !hc_watchdog_register_callback) {
1093         ap_log_error(APLOG_MARK, APLOG_CRIT, 0, s, APLOGNO(03262)
1094                      "mod_watchdog is required");
1095         return !OK;
1096     }
1097     rv = hc_watchdog_get_instance(&watchdog,
1098                                   HCHECK_WATHCHDOG_NAME,
1099                                   0, 1, p);
1100     if (rv) {
1101         ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(03263)
1102                      "Failed to create watchdog instance (%s)",
1103                      HCHECK_WATHCHDOG_NAME);
1104         return !OK;
1105     }
1106     while (s) {
1107         sctx_t *ctx = ap_get_module_config(s->module_config,
1108                                            &proxy_hcheck_module);
1109 
1110         if (s != ctx->s) {
1111             ap_log_error(APLOG_MARK, APLOG_TRACE4, 0, s, APLOGNO(10019)
1112                          "Missing unique per-server context: %s (%pp:%pp) (no hchecks)",
1113                          s->server_hostname, s, ctx->s);
1114             s = s->next;
1115             continue;
1116         }
1117         rv = hc_watchdog_register_callback(watchdog,
1118                 AP_WD_TM_SLICE,
1119                 ctx,
1120                 hc_watchdog_callback);
1121         if (rv) {
1122             ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(03264)
1123                          "Failed to register watchdog callback (%s)",
1124                          HCHECK_WATHCHDOG_NAME);
1125             return !OK;
1126         }
1127         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03265)
1128                      "watchdog callback registered (%s for %s)", HCHECK_WATHCHDOG_NAME, s->server_hostname);
1129         s = s->next;
1130     }
1131 
1132     ajp_handle_cping_cpong = APR_RETRIEVE_OPTIONAL_FN(ajp_handle_cping_cpong);
1133     if (ajp_handle_cping_cpong) {
1134        proxy_hcmethods_t *method = proxy_hcmethods;
1135        for (; method->name; method++) {
1136            if (method->method == CPING) {
1137                method->implemented = 1;
1138                break;
1139            }
1140        }
1141     }
1142 
1143     return OK;
1144 }
1145 
hc_show_exprs(request_rec * r)1146 static void hc_show_exprs(request_rec *r)
1147 {
1148     const apr_table_entry_t *elts;
1149     const apr_array_header_t *hdr;
1150     int i;
1151     sctx_t *ctx = (sctx_t *) ap_get_module_config(r->server->module_config,
1152                                                   &proxy_hcheck_module);
1153     if (!ctx)
1154         return;
1155     if (apr_is_empty_table(ctx->conditions))
1156         return;
1157 
1158     ap_rputs("\n\n<table>"
1159              "<tr><th colspan='2'>Health check cond. expressions:</th></tr>\n"
1160              "<tr><th>Expr name</th><th>Expression</th></tr>\n", r);
1161 
1162     hdr = apr_table_elts(ctx->conditions);
1163     elts = (const apr_table_entry_t *) hdr->elts;
1164     for (i = 0; i < hdr->nelts; ++i) {
1165         hc_condition_t *cond;
1166         if (!elts[i].key) {
1167             continue;
1168         }
1169         cond = (hc_condition_t *)elts[i].val;
1170         ap_rprintf(r, "<tr><td>%s</td><td>%s</td></tr>\n",
1171                    ap_escape_html(r->pool, elts[i].key),
1172                    ap_escape_html(r->pool, cond->expr));
1173     }
1174     ap_rputs("</table><hr/>\n", r);
1175 }
1176 
hc_select_exprs(request_rec * r,const char * expr)1177 static void hc_select_exprs(request_rec *r, const char *expr)
1178 {
1179     const apr_table_entry_t *elts;
1180     const apr_array_header_t *hdr;
1181     int i;
1182     sctx_t *ctx = (sctx_t *) ap_get_module_config(r->server->module_config,
1183                                                   &proxy_hcheck_module);
1184     if (!ctx)
1185         return;
1186     if (apr_is_empty_table(ctx->conditions))
1187         return;
1188 
1189     hdr = apr_table_elts(ctx->conditions);
1190     elts = (const apr_table_entry_t *) hdr->elts;
1191     for (i = 0; i < hdr->nelts; ++i) {
1192         if (!elts[i].key) {
1193             continue;
1194         }
1195         ap_rprintf(r, "<option value='%s' %s >%s</option>\n",
1196                    ap_escape_html(r->pool, elts[i].key),
1197                    (!strcmp(elts[i].key, expr)) ? "selected" : "",
1198                            ap_escape_html(r->pool, elts[i].key));
1199     }
1200 }
1201 
hc_valid_expr(request_rec * r,const char * expr)1202 static int hc_valid_expr(request_rec *r, const char *expr)
1203 {
1204     const apr_table_entry_t *elts;
1205     const apr_array_header_t *hdr;
1206     int i;
1207     sctx_t *ctx = (sctx_t *) ap_get_module_config(r->server->module_config,
1208                                                   &proxy_hcheck_module);
1209     if (!ctx)
1210         return 0;
1211     if (apr_is_empty_table(ctx->conditions))
1212         return 0;
1213 
1214     hdr = apr_table_elts(ctx->conditions);
1215     elts = (const apr_table_entry_t *) hdr->elts;
1216     for (i = 0; i < hdr->nelts; ++i) {
1217         if (!elts[i].key) {
1218             continue;
1219         }
1220         if (!strcmp(elts[i].key, expr))
1221             return 1;
1222     }
1223     return 0;
1224 }
1225 
hc_get_body(request_rec * r)1226 static const char *hc_get_body(request_rec *r)
1227 {
1228     apr_off_t length;
1229     apr_size_t len;
1230     apr_status_t rv;
1231     char *buf;
1232 
1233     if (!r || !r->kept_body)
1234         return "";
1235 
1236     rv = apr_brigade_length(r->kept_body, 1, &length);
1237     len = (apr_size_t)length;
1238     if (rv != APR_SUCCESS || len == 0)
1239         return "";
1240 
1241     buf = apr_palloc(r->pool, len + 1);
1242     rv = apr_brigade_flatten(r->kept_body, buf, &len);
1243     if (rv != APR_SUCCESS)
1244         return "";
1245     buf[len] = '\0'; /* ensure */
1246     return (const char*)buf;
1247 }
1248 
hc_expr_var_fn(ap_expr_eval_ctx_t * ctx,const void * data)1249 static const char *hc_expr_var_fn(ap_expr_eval_ctx_t *ctx, const void *data)
1250 {
1251     char *var = (char *)data;
1252 
1253     if (var && *var && ctx->r && ap_cstr_casecmp(var, "BODY") == 0) {
1254         return hc_get_body(ctx->r);
1255     }
1256     return NULL;
1257 }
1258 
hc_expr_func_fn(ap_expr_eval_ctx_t * ctx,const void * data,const char * arg)1259 static const char *hc_expr_func_fn(ap_expr_eval_ctx_t *ctx, const void *data,
1260                                 const char *arg)
1261 {
1262     char *var = (char *)arg;
1263 
1264     if (var && *var && ctx->r && ap_cstr_casecmp(var, "BODY") == 0) {
1265         return hc_get_body(ctx->r);
1266     }
1267     return NULL;
1268 }
1269 
hc_expr_lookup(ap_expr_lookup_parms * parms)1270 static int hc_expr_lookup(ap_expr_lookup_parms *parms)
1271 {
1272     switch (parms->type) {
1273     case AP_EXPR_FUNC_VAR:
1274         /* for now, we just handle everything that starts with HC_.
1275          */
1276         if (strncasecmp(parms->name, "HC_", 3) == 0) {
1277             *parms->func = hc_expr_var_fn;
1278             *parms->data = parms->name + 3;
1279             return OK;
1280         }
1281         break;
1282     case AP_EXPR_FUNC_STRING:
1283         /* Function HC() is implemented by us.
1284          */
1285         if (strcasecmp(parms->name, "HC") == 0) {
1286             *parms->func = hc_expr_func_fn;
1287             *parms->data = parms->arg;
1288             return OK;
1289         }
1290         break;
1291     }
1292     return DECLINED;
1293 }
1294 
1295 static const command_rec command_table[] = {
1296     AP_INIT_RAW_ARGS("ProxyHCTemplate", set_hc_template, NULL, OR_FILEINFO,
1297                      "Health check template"),
1298     AP_INIT_RAW_ARGS("ProxyHCExpr", set_hc_condition, NULL, OR_FILEINFO,
1299                      "Define a health check condition ruleset expression"),
1300 #if HC_USE_THREADS
1301     AP_INIT_TAKE1("ProxyHCTPsize", set_hc_tpsize, NULL, RSRC_CONF,
1302                      "Set size of health check thread pool"),
1303 #endif
1304     { NULL }
1305 };
1306 
hc_register_hooks(apr_pool_t * p)1307 static void hc_register_hooks(apr_pool_t *p)
1308 {
1309     static const char *const aszPre[] = { "mod_proxy_balancer.c", "mod_proxy.c", NULL};
1310     static const char *const aszSucc[] = { "mod_watchdog.c", NULL};
1311     APR_REGISTER_OPTIONAL_FN(set_worker_hc_param);
1312     APR_REGISTER_OPTIONAL_FN(hc_show_exprs);
1313     APR_REGISTER_OPTIONAL_FN(hc_select_exprs);
1314     APR_REGISTER_OPTIONAL_FN(hc_valid_expr);
1315     ap_hook_pre_config(hc_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
1316     ap_hook_post_config(hc_post_config, aszPre, aszSucc, APR_HOOK_LAST);
1317     ap_hook_expr_lookup(hc_expr_lookup, NULL, NULL, APR_HOOK_MIDDLE);
1318 }
1319 
1320 /* the main config structure */
1321 
1322 AP_DECLARE_MODULE(proxy_hcheck) =
1323 {
1324     STANDARD20_MODULE_STUFF,
1325     NULL,              /* create per-dir config structures */
1326     NULL,              /* merge  per-dir config structures */
1327     hc_create_config,  /* create per-server config structures */
1328     NULL,              /* merge  per-server config structures */
1329     command_table,     /* table of config file commands */
1330     hc_register_hooks  /* register hooks */
1331 };
1332