flood_round_robin.c (1,116 lines of code) (raw):

/* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Originally developed by Aaron Bannert and Justin Erenkrantz, eBuilt. */ #include <apr_file_io.h> #include <apr_network_io.h> #include <apr_strings.h> #include <apr_uri.h> #include <apr_lib.h> #include <apr_hash.h> #include <apr_base64.h> #include <apr_poll.h> #include <apr_thread_proc.h> #include <apr_errno.h> #if APR_HAVE_STRINGS_H #include <strings.h> /* strncasecmp */ #endif #if APR_HAVE_STRING_H #include <string.h> /* strncasecmp */ #endif #if APR_HAVE_STDLIB_H #include <stdlib.h> /* strtol */ #endif #if APR_HAVE_UNISTD_H #include <unistd.h> /* For pause */ #endif #if APR_HAVE_SYS_TYPES_H #include <sys/types.h> #endif #if APR_HAVE_LIMITS_H #include <limits.h> #endif #include <assert.h> #ifdef FLOOD_USE_PCRE #include "flood_pcre.h" #else #include "regex.h" #endif #include "config.h" #include "flood_net.h" #include "flood_round_robin.h" #include "flood_subst_file.h" #include "flood_profile.h" /* On FreeBSD, the return of regexec() is 0 or REG_NOMATCH, and REG_OK is undefined */ #ifndef REG_OK #define REG_OK 0 #endif extern apr_file_t *local_stdout; extern apr_file_t *local_stderr; typedef enum { EPE_EXPAND, EPE_EXPAND_SET, EPE_PASSTHROUGH } expand_param_e; typedef struct { char *url; method_e method; const char *method_string; char *payload; char *contenttype; char *extra_headers; apr_int64_t predelay; apr_int64_t predelayprecision; apr_int64_t postdelay; apr_int64_t postdelayprecision; char *payloadtemplate; char *requesttemplate; char *responsetemplate; char *responsescript; char *responsename; int responselen; char *user; char *password; } url_t; typedef struct cookie_t { char *name; char *value; char *path; char *expires; char *raw; struct cookie_t *next; } cookie_t; typedef struct { apr_pool_t *pool; int execute_rounds; int urls; url_t *url; char *baseurl; apr_uri_t *proxy_url; cookie_t *cookie; apr_hash_t *state; int subst_count; subst_rec_t* subst_list; int current_round; int current_url; } round_robin_profile_t; static char *handle_param_string(round_robin_profile_t *rp, char *template, expand_param_e set) { char *cpy, *cur, *prev, *data, *returnValue, *pattern; int size, matchsize; regex_t re; regmatch_t match[2]; subst_rec_t* subst_rec_p; char* lookup_val; char subst_buf[8096]; apr_pool_t *local_pool; if (apr_pool_create(&local_pool, NULL) != APR_SUCCESS) { apr_file_printf(local_stderr, "Failed apr_pool_create!\n"); exit(-1); } prev = template; returnValue = NULL; pattern = "\\$\\{([^\\}]+)\\}"; regcomp(&re, pattern, REG_EXTENDED); cur = template; while (regexec(&re, cur, 2, match, 0) == REG_OK) { /* We must backup over the ${ characters. */ size = match[1].rm_so - 2; if (size++) { cpy = apr_palloc(rp->pool, size); apr_cpystrn(cpy, cur, size); } else cpy = NULL; if (*(cur+match[1].rm_so) == '=') { if (set) { /* We need to assign it a random value. */ #if FLOOD_USE_RAND data = apr_psprintf(rp->pool, "%d", rand()); #elif FLOOD_USE_RAND48 data = apr_psprintf(rp->pool, "%ld", lrand48()); #elif FLOOD_USE_RANDOM data = apr_psprintf(rp->pool, "%ld", (long)random()); #endif matchsize = match[1].rm_eo - match[1].rm_so - 1; apr_hash_set(rp->state, cur+match[1].rm_so+1, matchsize, data); } else data = NULL; } else { matchsize = match[1].rm_eo - match[1].rm_so; data = apr_hash_get(rp->state, cur+match[1].rm_so, matchsize); } /* if there is no data, maybe it's a random string subst */ /* try to do the substition */ if (!data) { matchsize = match[1].rm_eo - match[1].rm_so; lookup_val = apr_pstrndup(local_pool, cur+match[1].rm_so, matchsize); memset(subst_buf, 0, sizeof(subst_buf)); subst_rec_p = subst_file_get(lookup_val, rp->subst_list); subst_file_entry_get(&subst_rec_p->subst_file, &subst_rec_p->fsize, subst_buf, sizeof(subst_buf)); if (!strlen(subst_buf)) { apr_file_printf(local_stderr, "substitution didn't return data!\n"); exit(-1); } data = apr_pstrdup(rp->pool, subst_buf); } /* If there is no data, place the original string back. */ if (!data) { data = apr_psprintf(rp->pool, "${%s}", apr_pstrmemdup(rp->pool, cur+match[1].rm_so, match[1].rm_eo - match[1].rm_so)); } if (!returnValue) { if (cpy) returnValue = apr_pstrcat(rp->pool, cpy, data, NULL); else returnValue = apr_pstrdup(rp->pool, data); } else { if (cpy) returnValue = apr_pstrcat(rp->pool, returnValue, cpy, data, NULL); else returnValue = apr_pstrcat(rp->pool, returnValue, data, NULL); } /* Skip over the trailing } */ cur += match[1].rm_eo + 1; } if (!returnValue) returnValue = apr_pstrdup(rp->pool, cur); else returnValue = apr_pstrcat(rp->pool, returnValue, cur, NULL); subst_file_entry_unescape(returnValue, sizeof(returnValue)); regfree(&re); apr_pool_destroy(local_pool); return returnValue; } static char *expand_param_string(round_robin_profile_t *rp, char *template) { return handle_param_string(rp, template, EPE_EXPAND); } static char *parse_param_string(round_robin_profile_t *rp, char *template) { return handle_param_string(rp, template, EPE_EXPAND_SET); } /* Construct a request */ apr_status_t round_robin_create_req(profile_t *profile, request_t *r) { round_robin_profile_t *p; char *cookies, *path; char *enc_credtls, *credtls, *authz_hdr = NULL, *extra_hdr = NULL; cookie_t *cook; p = (round_robin_profile_t*)profile; /* Do we want to save the entire response? */ r->wantresponse = p->url[p->current_url].responsetemplate ? 1 : 0; /* FIXME: This algorithm sucks. */ if (p->cookie) { cookies = apr_pstrdup(p->pool, "Cookie: "); cook = p->cookie; while (cook) { if (cook != p->cookie) cookies = apr_pstrcat(p->pool, cookies, ";", NULL); cookies = apr_pstrcat(p->pool, cookies, cook->name, "=", cook->value, NULL); cook = cook->next; } cookies = apr_pstrcat(p->pool, cookies, CRLF, NULL); } else cookies = ""; if (p->url[p->current_url].user) { if (!p->url[p->current_url].password) { apr_file_printf(local_stderr, "missing password for user '%s'\n", p->url[p->current_url].user); return APR_EGENERAL; } else { int credlen; credtls = apr_pstrcat(r->pool, p->url[p->current_url].user, ":", p->url[p->current_url].password, NULL); credlen = strlen(credtls); enc_credtls = (char *) apr_palloc(r->pool, apr_base64_encode_len(credlen) + 1); apr_base64_encode(enc_credtls, credtls, credlen); authz_hdr = apr_pstrcat(r->pool, "Authorization: Basic ", enc_credtls, CRLF, NULL); } } if (p->url[p->current_url].extra_headers) { extra_hdr = p->url[p->current_url].extra_headers; } if (p->proxy_url != NULL) { path = apr_pstrcat(r->pool, r->parsed_uri->scheme, "://", r->parsed_uri->hostinfo, r->parsed_uri->path, NULL); } else { path = r->parsed_uri->path; } switch (r->method) { case GET: case HEAD: r->rbuf = apr_psprintf(r->pool, "%s %s%s%s HTTP/1.1" CRLF "User-Agent: Flood/" FLOOD_VERSION CRLF "Connection: %s" CRLF "Host: %s" CRLF "%s" /* Extra headers */ "%s" /* Authz */ "%s" /* Cookies */ CRLF, /* End of HTTP request headers */ p->url[p->current_url].method_string, path, r->parsed_uri->query ? "?" : "", r->parsed_uri->query ? r->parsed_uri->query : "", r->keepalive ? "Keep-Alive" : "Close", r->parsed_uri->hostinfo, extra_hdr ? extra_hdr : "", authz_hdr ? authz_hdr : "", cookies); r->rbuftype = POOL; r->rbufsize = strlen(r->rbuf); break; case POST: case OTHER: if (r->payload) { r->rbuf = apr_psprintf(r->pool, "%s %s%s%s HTTP/1.1" CRLF "User-Agent: Flood/" FLOOD_VERSION CRLF "Connection: %s" CRLF "Host: %s" CRLF "Content-Length: %d" CRLF "Content-Type: %s" CRLF "%s" /* Extra headers */ "%s" /* Authz */ "%s" /* Cookies */ CRLF /* End of HTTP request headers */ "%s" /* Body */, p->url[p->current_url].method_string, path, r->parsed_uri->query ? "?" : "", r->parsed_uri->query ? r->parsed_uri->query : "", r->keepalive ? "Keep-Alive" : "Close", r->parsed_uri->hostinfo, r->payloadsize, r->contenttype ? r->contenttype : "application/x-www-form-urlencoded", extra_hdr ? extra_hdr : "", authz_hdr ? authz_hdr : "", cookies, (char*)r->payload); } else { /* There is no payload, but it's still a POST */ r->rbuf = apr_psprintf(r->pool, "%s %s%s%s HTTP/1.1" CRLF "User-Agent: Flood/" FLOOD_VERSION CRLF "Connection: %s" CRLF "Host: %s" CRLF "%s" /* Extra headers */ "%s" /* Authz */ "%s" /* Cookies */ CRLF /* End of HTTP request headers */ "" /* No body */, p->url[p->current_url].method_string, path, r->parsed_uri->query ? "?" : "", r->parsed_uri->query ? r->parsed_uri->query : "", r->keepalive ? "Keep-Alive" : "Close", r->parsed_uri->hostinfo, extra_hdr ? extra_hdr : "", authz_hdr ? authz_hdr : "", cookies); } r->rbuftype = POOL; r->rbufsize = strlen(r->rbuf); break; } return APR_SUCCESS; } static apr_status_t parse_xml_url_info(apr_xml_elem *e, url_t *url, apr_pool_t *pool) { /* Grab the url from the text section. */ if (e->first_cdata.first && e->first_cdata.first->text) { if (e->first_cdata.first->next) { apr_text *t; t = e->first_cdata.first; url->url = apr_pstrdup(pool, t->text); while ((t = t->next)) { url->url = apr_pstrcat(pool, url->url, t->text, NULL); } } else { url->url = apr_pstrdup(pool, e->first_cdata.first->text); } } /* Parse any attributes. */ if (e->attr) { apr_xml_attr *attr = e->attr; while (attr) { if (strncasecmp(attr->name, XML_URLLIST_METHOD, FLOOD_STRLEN_MAX) == 0) { if (strncasecmp(attr->value, XML_URLLIST_METHOD_POST, 4) == 0) { url->method = POST; url->method_string = "POST"; } else if (strncasecmp(attr->value, XML_URLLIST_METHOD_HEAD, 4) == 0) { url->method = HEAD; url->method_string = "HEAD"; } else if (strncasecmp(attr->value, XML_URLLIST_METHOD_GET, 3) == 0) { url->method = GET; url->method_string = "GET"; } else { url->method = OTHER; url->method_string = apr_pstrdup(pool, attr->value); } } else if (strncasecmp(attr->name, XML_URLLIST_PAYLOAD, FLOOD_STRLEN_MAX) == 0) { url->payload = (char*)attr->value; } else if (strncasecmp(attr->name, XML_URLLIST_PAYLOAD_FILE, FLOOD_STRLEN_MAX) == 0) { apr_status_t status; const char *fname; apr_finfo_t finfo; apr_file_t *file; apr_size_t len; fname = attr->value; status = apr_stat(&finfo, fname, APR_FINFO_MIN, pool); if (status != APR_SUCCESS) return status; status = apr_file_open(&file, fname, APR_READ, APR_OS_DEFAULT, pool); if (status != APR_SUCCESS) return status; url->payload = apr_palloc(pool, finfo.size + 1); status = apr_file_read_full(file, url->payload, finfo.size, &len); if (len != finfo.size) return status; apr_file_close(file); } else if (strncasecmp(attr->name,XML_URLLIST_CONTENT_TYPE, FLOOD_STRLEN_MAX) == 0) { url->contenttype = (char*)attr->value; } else if (strncasecmp(attr->name,XML_URLLIST_EXTRA_HEADERS, FLOOD_STRLEN_MAX) == 0) { char *last, *header; header = apr_strtok((char*)attr->value, ";", &last); while (header) { apr_collapse_spaces(header, header); if (url->extra_headers) { url->extra_headers = apr_pstrcat(pool, url->extra_headers, header, CRLF, NULL); } else { url->extra_headers = apr_pstrcat(pool, header, CRLF, NULL); } header = apr_strtok(NULL, ";", &last); } } else if (strncasecmp(attr->name, XML_URLLIST_PREDELAY, FLOOD_STRLEN_MAX) == 0) { char *endptr; url->predelay = strtoll(attr->value, &endptr, 10); if (*endptr != '\0') { apr_file_printf(local_stderr, "Attribute %s has invalid value %s.\n", XML_URLLIST_PREDELAY, attr->value); return APR_EGENERAL; } url->predelay *= APR_USEC_PER_SEC; } else if (strncasecmp(attr->name, XML_URLLIST_PREDELAYPRECISION, FLOOD_STRLEN_MAX) == 0) { char *endptr; url->predelayprecision = strtoll(attr->value, &endptr, 10); if (*endptr != '\0') { apr_file_printf(local_stderr, "Attribute %s has invalid value %s.\n", XML_URLLIST_PREDELAYPRECISION, attr->value); return APR_EGENERAL; } url->predelayprecision *= APR_USEC_PER_SEC; } else if (strncasecmp(attr->name, XML_URLLIST_POSTDELAY, FLOOD_STRLEN_MAX) == 0) { char *endptr; url->postdelay = strtoll(attr->value, &endptr, 10); if (*endptr != '\0') { apr_file_printf(local_stderr, "Attribute %s has invalid value %s.\n", XML_URLLIST_POSTDELAY, attr->value); return APR_EGENERAL; } url->postdelay *= APR_USEC_PER_SEC; } else if (strncasecmp(attr->name, XML_URLLIST_POSTDELAYPRECISION, FLOOD_STRLEN_MAX) == 0) { char *endptr; url->postdelayprecision = strtoll(attr->value, &endptr, 10); if (*endptr != '\0') { apr_file_printf(local_stderr, "Attribute %s has invalid value %s.\n", XML_URLLIST_POSTDELAYPRECISION, attr->value); return APR_EGENERAL; } url->postdelayprecision *= APR_USEC_PER_SEC; } else if (strncasecmp(attr->name, XML_URLLIST_PAYLOAD_TEMPLATE, FLOOD_STRLEN_MAX) == 0) { url->payloadtemplate = (char*)attr->value; } else if (strncasecmp(attr->name, XML_URLLIST_REQUEST_TEMPLATE, FLOOD_STRLEN_MAX) == 0) { url->requesttemplate = (char*)attr->value; } else if (strncasecmp(attr->name, XML_URLLIST_RESPONSE_TEMPLATE, FLOOD_STRLEN_MAX) == 0) { url->responsetemplate = (char*)attr->value; } else if (strncasecmp(attr->name, XML_URLLIST_RESPONSE_SCRIPT, FLOOD_STRLEN_MAX) == 0) { url->responsescript = (char*)attr->value; } else if (strncasecmp(attr->name, XML_URLLIST_RESPONSE_NAME, FLOOD_STRLEN_MAX) == 0) { url->responsename = (char*)attr->value; url->responselen = strlen((char*)attr->value); } else if (strncasecmp(attr->name, XML_URLLIST_USER, FLOOD_STRLEN_MAX) == 0) { url->user = (char*)attr->value; } else if (strncasecmp(attr->name, XML_URLLIST_PASSWORD, FLOOD_STRLEN_MAX) == 0) { url->password = (char*)attr->value; } attr = attr->next; } } else { url->method = GET; url->method_string = "GET"; url->payload = NULL; url->contenttype = NULL; } return APR_SUCCESS; } static apr_status_t parse_xml_seq_info(apr_xml_elem *e, round_robin_profile_t *p, apr_pool_t *pool) { char *seqname, **seqlist; int seqnamelen, seqcount, curseq; struct apr_xml_elem *child_url_elem; apr_status_t rv; if (e->attr) { apr_xml_attr *attr = e->attr; while (attr) { if (strncasecmp(attr->name, XML_URLLIST_SEQUENCE_NAME, FLOOD_STRLEN_MAX) == 0) { seqname = (char*)attr->value; seqnamelen = strlen(seqname); } else if (strncasecmp(attr->name, XML_URLLIST_SEQUENCE_LIST, FLOOD_STRLEN_MAX) == 0) { /* FIXME: ap_getword needs to be in apr-util! */ char *end, *cur; int count = 1, num = 0; end = (char*)attr->value; while (*end && (end = strchr(end, ','))) { count++; end++; } seqlist = apr_palloc(pool, sizeof(char*) * count); seqcount = count; cur = (char*)attr->value; end = strchr(cur, ','); for (num = 0; num < count; num++) { while (apr_isspace(*cur)) { cur++; } if (end) { seqlist[num] = apr_pstrmemdup(pool, cur, end - cur); cur = ++end; end = strchr(cur, ','); } else { seqlist[num] = apr_pstrdup(pool, cur); } } } attr = attr->next; } } for (curseq = 0; curseq < seqcount; curseq++) { apr_hash_set(p->state, seqname, seqnamelen, seqlist[curseq]); for (child_url_elem = e->first_child; child_url_elem; child_url_elem = child_url_elem->next) { if (strncasecmp(child_url_elem->name, XML_URLLIST_SEQUENCE, FLOOD_STRLEN_MAX) == 0) { rv = parse_xml_seq_info(child_url_elem, p, pool); if (rv != APR_SUCCESS) { return rv; } } else if (strncasecmp(child_url_elem->name, XML_URLLIST_URL, FLOOD_STRLEN_MAX) == 0) { rv = parse_xml_url_info(child_url_elem, &p->url[p->current_url], pool); if (rv != APR_SUCCESS) { return rv; } /* Expand them. */ if (p->url[p->current_url].payloadtemplate) { p->url[p->current_url].payloadtemplate = handle_param_string(p, p->url[p->current_url].payloadtemplate, EPE_PASSTHROUGH); } if (p->url[p->current_url].requesttemplate) { p->url[p->current_url].requesttemplate = handle_param_string(p, p->url[p->current_url].requesttemplate, EPE_PASSTHROUGH); } if (p->url[p->current_url].responsetemplate) { p->url[p->current_url].responsetemplate = handle_param_string(p, p->url[p->current_url].responsetemplate, EPE_PASSTHROUGH); } p->current_url++; } } } return APR_SUCCESS; } static int count_xml_seq_child(apr_xml_elem *urllist_elem) { struct apr_xml_elem *e; int items = 0; for (e = urllist_elem->first_child; e; e = e->next) { if (strncasecmp(e->name, XML_URLLIST_SEQUENCE, FLOOD_STRLEN_MAX) == 0) { int children_urls, list_count; list_count = 0; if (e->attr) { apr_xml_attr *attr = e->attr; while (attr) { if (strncasecmp(attr->name, XML_URLLIST_SEQUENCE_LIST, FLOOD_STRLEN_MAX) == 0) { char *end = (char*)attr->value; list_count++; while (*end && (end = strchr(end, ','))) { list_count++; end++; } } attr = attr->next; } } if (!list_count) { apr_file_printf(local_stderr, "Sequence doesn't have any items!\n"); return 0; } children_urls = count_xml_seq_child(e); children_urls += count_xml_elem_child(e, XML_URLLIST_URL); items += list_count * children_urls; } } return items; } apr_status_t round_robin_profile_init(profile_t **profile, config_t *config, const char *profile_name, apr_pool_t *pool) { apr_status_t rv; int i; struct apr_xml_elem *root_elem, *profile_elem, *urllist_elem, *count_elem, *useurllist_elem, *baseurl_elem, *subst_list_elem, *subst_entry_elem, *subst_entry_child, *proxyurl_elem, *e; round_robin_profile_t *p; char *xml_profile, *xml_urllist, *urllist_name; char *xml_subst_list, *subst_list_name; subst_rec_t* subst_rec_p; int valid_substs = 0; p = apr_pcalloc(pool, sizeof(round_robin_profile_t)); p->pool = pool; /* yeah, yeah; calloc(), whatever...this is readability baby! */ p->current_url = 0; /* start on the first URL */ p->current_round = 0; /* start counting rounds at 0 */ p->state = apr_hash_make(pool); /* get the XML pathes to the profile and the urllist */ xml_profile = apr_pstrdup(pool, XML_PROFILE); xml_urllist = apr_pstrdup(pool, XML_URLLIST); if ((rv = retrieve_root_xml_elem(&root_elem, config)) != APR_SUCCESS) { return rv; } /* retrieve our profile xml element */ if ((rv = retrieve_xml_elem_with_childmatch( &profile_elem, root_elem, xml_profile, "name", profile_name)) != APR_SUCCESS) return rv; /* find the count */ if ((rv = retrieve_xml_elem_child( &count_elem, profile_elem, XML_PROFILE_COUNT)) != APR_SUCCESS) { /* if it's missing, just default to 1 */ p->execute_rounds = 1; } else { if (count_elem->first_cdata.first && count_elem->first_cdata.first->text) { p->execute_rounds = strtol(count_elem->first_cdata.first->text, NULL, 10); if (p->execute_rounds == LONG_MAX || p->execute_rounds == LONG_MIN) /* error, over/under-flow */ return errno; } else { apr_file_printf(local_stderr, "Profile '%s' has element <%s> with no value, assuming 1.\n", profile_name, XML_PROFILE_COUNT); p->execute_rounds = 1; } } #ifdef PROFILE_DEBUG apr_file_printf(local_stdout, "Profile '%s' will be run %d times.\n", profile_name, p->execute_rounds); #endif /* PROFILE_DEBUG */ /* find out what the name of our urllist is */ if ((rv = retrieve_xml_elem_child( &useurllist_elem, profile_elem, XML_PROFILE_USEURLLIST)) != APR_SUCCESS) { /* useurllist is a required parameter, error */ apr_file_printf(local_stderr, "Profile '%s' has no <%s> parameter.\n", profile_name, XML_PROFILE_USEURLLIST); return APR_EGENERAL; } else { urllist_name = apr_pstrdup(pool, useurllist_elem->first_cdata.first->text); } /* retrieve our urllist xml element */ if ((rv = retrieve_xml_elem_with_childmatch( &urllist_elem, root_elem, xml_urllist, XML_URLLIST_NAME, urllist_name)) != APR_SUCCESS) return rv; /* do we have base url? */ if ((rv = retrieve_xml_elem_child( &baseurl_elem, urllist_elem, XML_URLLIST_BASE_URL)) == APR_SUCCESS) { /* yes we do */ p->baseurl = apr_pstrdup(pool, baseurl_elem->first_cdata.first->text); } else { p->baseurl = NULL; } /* do we have proxy url? */ if ((rv = retrieve_xml_elem_child( &proxyurl_elem, urllist_elem, XML_URLLIST_PROXY_URL)) == APR_SUCCESS) { /* yes we do */ p->proxy_url = apr_pcalloc(p->pool, sizeof(apr_uri_t)); apr_uri_parse(p->pool, proxyurl_elem->first_cdata.first->text, p->proxy_url); } else { p->proxy_url = NULL; } p->urls = 0; /* Include sequences. We'll expand them later. */ p->urls = count_xml_seq_child(urllist_elem); /* find the urls for this profile, put 'em in this list */ p->urls += count_xml_elem_child(urllist_elem, XML_URLLIST_URL); if (p->urls <= 0) { apr_file_printf(local_stderr, "Urllist '%s' doesn't have any urls!\n", urllist_name); return APR_EGENERAL; } p->url = apr_pcalloc(p->pool, sizeof(url_t) * (p->urls + 1)); i = 0; for (e = urllist_elem->first_child; e; e = e->next) { if (strncasecmp(e->name, XML_URLLIST_SEQUENCE, FLOOD_STRLEN_MAX) == 0) { rv = parse_xml_seq_info(e, p, pool); if (rv != APR_SUCCESS) { return rv; } } if (strncasecmp(e->name, XML_URLLIST_URL, FLOOD_STRLEN_MAX) == 0) { rv = parse_xml_url_info(e, &p->url[p->current_url++], pool); if (rv != APR_SUCCESS) { return rv; } } } /* Reset this back to 0. */ p->current_url = 0; /* now initialize the subst_list for random text substitution */ /* get the subst_list from the config file */ /* the subst_list has pairs or substitution variables and files */ /* later on, in handle_param_string(), when a substitution variable */ /* is found, it will be substituted with a randomly chosen line from */ /* the subsitution file */ /* there can be an arbitrary number of such pairs */ /* the pairs scope is the entire configuration file */ /* they are not specific to a profile, url or urllist */ xml_subst_list = apr_pstrdup(pool, XML_SUBST_LIST); if ((rv = retrieve_xml_elem_child( &subst_list_elem, root_elem, XML_SUBST_LIST)) == APR_SUCCESS) { /* count the subst_entries for this config file and allocate space */ p->subst_count = 0; p->subst_count += count_xml_elem_child(subst_list_elem, XML_SUBST_ENTRY); p->subst_list = apr_pcalloc(p->pool, sizeof(subst_rec_t) * (p->subst_count + 1)); /* get the subst_list info and populate the data structures */ subst_rec_p = p->subst_list; for (e = subst_list_elem->first_child; e; e = e->next) { if (strncasecmp(e->name, XML_SUBST_ENTRY, FLOOD_STRLEN_MAX) == 0) { subst_entry_elem = e; for (subst_entry_child = subst_entry_elem->first_child; subst_entry_child; subst_entry_child = subst_entry_child->next) { if (strncasecmp(subst_entry_child->name, XML_SUBST_VAR, FLOOD_STRLEN_MAX) == 0) { if (subst_entry_child->first_cdata.first && subst_entry_child->first_cdata.first->text) { ((subst_rec_t*)subst_rec_p)->subst_var = apr_pstrdup(pool, subst_entry_child->first_cdata.first->text); } } if (strncasecmp(subst_entry_child->name, XML_SUBST_FILE, FLOOD_STRLEN_MAX) == 0) { if (subst_entry_child->first_cdata.first && subst_entry_child->first_cdata.first->text) { ((subst_rec_t*)subst_rec_p)->subst_file_name = apr_pstrdup(pool, subst_entry_child->first_cdata.first->text); } } } } /* this is the end of each subst_entry fetch */ /* at this point we should have the subst_var and subst_file_name */ if (subst_rec_p->subst_var && subst_rec_p->subst_file_name) { subst_rec_p->valid = 1; } /* we should have the same number of valid substs as */ /* the subst_count above */ valid_substs++; subst_rec_p++; } if (valid_substs != p->subst_count) { apr_file_printf(local_stderr, "Profile '%s' valid substs: %d inconsistent with subst count %d.\n", profile_name, valid_substs, p->subst_count); return APR_EGENERAL; } /* now open all the substitution files */ subst_rec_p = p->subst_list; while(subst_rec_p->valid) { subst_file_open(&(subst_rec_p->subst_file), subst_rec_p->subst_file_name, &(subst_rec_p->fsize), pool); subst_rec_p++; } } *profile = p; return APR_SUCCESS; } apr_status_t round_robin_get_next_url(request_t **request, profile_t *profile) { round_robin_profile_t *rp; request_t *r; rp = (round_robin_profile_t*)profile; /* FIXME: precompute request_t in profile_init */ r = apr_pcalloc(rp->pool, sizeof(request_t)); r->pool = rp->pool; if (rp->url[rp->current_url].requesttemplate) { r->uri = parse_param_string(rp, rp->url[rp->current_url].requesttemplate); } else r->uri = rp->url[rp->current_url].url; r->method = rp->url[rp->current_url].method; /* We're created by calloc, so no need to set payload to be null or * payloadsize to be 0. */ if (rp->url[rp->current_url].payload) { r->payload = rp->url[rp->current_url].payload; r->payloadsize = strlen(rp->url[rp->current_url].payload); } else if (rp->url[rp->current_url].payloadtemplate) { r->payload = parse_param_string(rp, rp->url[rp->current_url].payloadtemplate); r->payloadsize = strlen(r->payload); } if (rp->url[rp->current_url].contenttype) { r->contenttype = parse_param_string(rp, rp->url[rp->current_url].contenttype); r->contenttypesize = strlen(r->contenttype); } /* If they want a sleep, do it now. */ if (rp->url[rp->current_url].predelay) { apr_int64_t real_predelay = rp->url[rp->current_url].predelay; /* If the delay has a precision, adjust the * delay by some random fraction of the precision here */ if (rp->url[rp->current_url].predelayprecision) { /* FIXME: this should be more portable, like apr_generate_random_bytes() */ float factor = -1.0 + (2.0*rand()/(RAND_MAX+1.0)); real_predelay += rp->url[rp->current_url].predelayprecision * factor; } /* we can only delay positive times, can't go back in time :( */ if (real_predelay < 0) real_predelay = 0; /* only bother going to sleep if we generated a delay */ if (real_predelay > 0) apr_sleep(real_predelay); } r->parsed_uri = apr_palloc(rp->pool, sizeof(apr_uri_t)); if (rp->baseurl != NULL) { r->uri = apr_pstrcat(rp->pool, rp->baseurl, r->uri, NULL); } apr_uri_parse(rp->pool, r->uri, r->parsed_uri); if (r->parsed_uri->scheme == NULL || r->parsed_uri->hostname == NULL) { apr_file_printf(local_stderr, "Misformed URL '%s'\n", r->uri); exit (APR_EGENERAL); } if (r->parsed_uri->hostname[0] == '\0') { apr_file_printf(local_stderr, "Misformed URL '%s' -- can't find valid hostname.\n", r->uri); exit (APR_EGENERAL); } /* this schouldn't be hardcoded, but... :) */ if (apr_strnatcmp (r->parsed_uri->scheme, "http") != APR_SUCCESS && apr_strnatcmp (r->parsed_uri->scheme, "https") != APR_SUCCESS) { apr_file_printf(local_stderr, "Wrong URL scheme '%s' -- only 'http' and 'https' schemes are supported.\n", r->parsed_uri->scheme); exit (APR_EGENERAL); } if (r->parsed_uri->user != NULL || r->parsed_uri->password != NULL) { apr_file_printf(local_stderr, "Misformed URL -- auth data schould be outside URL -- please see docs.\n"); exit (APR_EGENERAL); } if (!r->parsed_uri->port) { r->parsed_uri->port = apr_uri_port_of_scheme(r->parsed_uri->scheme); } if (!r->parsed_uri->path) /* If / is not there, be nice. */ r->parsed_uri->path = "/"; r->parsed_proxy_uri = rp->proxy_url; #ifdef PROFILE_DEBUG apr_file_printf(local_stdout, "Generating request to: %s\n", r->uri); #endif /* PROFILE_DEBUG */ *request = r; return APR_SUCCESS; } apr_status_t round_robin_postprocess(profile_t *profile, request_t *req, response_t *resp) { round_robin_profile_t *rp; char *cookieheader, *cookievalue, *cookieend; rp = (round_robin_profile_t*)profile; /* FIXME: This algorithm sucks. I need to be shot for writing such * atrocious code. Grr. */ cookieheader = strstr(resp->rbuf, "Set-Cookie: "); if (cookieheader) { /* Point to the value */ cookieheader += 12; cookievalue = (char*) memchr(cookieheader, '=', resp->rbufsize - (int)(cookieheader - (int)(resp->rbuf))); if (cookievalue) { cookie_t * cookie = apr_pcalloc(rp->pool, sizeof(cookie_t)); ++cookievalue; cookie->name = apr_palloc(rp->pool, cookievalue - cookieheader); apr_cpystrn(cookie->name, cookieheader, cookievalue - cookieheader); cookieheader = cookievalue; cookieend = (char*) memchr(cookieheader, '\r', resp->rbufsize - (int)(cookieheader - (int)(resp->rbuf))); cookievalue = (char*) memchr(cookieheader, ';', cookieend - cookieheader); if (!cookievalue) cookievalue = cookieend; ++cookievalue; cookie->value = apr_palloc(rp->pool, cookievalue - cookieheader); apr_cpystrn(cookie->value, cookieheader, cookievalue - cookieheader); cookie->next = rp->cookie; rp->cookie = cookie; } } if (rp->url[rp->current_url].responsetemplate) { int status, size; char *expanded, *newValue; regmatch_t match[10]; regex_t re; expanded = expand_param_string(rp, rp->url[rp->current_url].responsetemplate); regcomp(&re, expanded, REG_EXTENDED); status = regexec(&re, resp->rbuf, 10, match, 0); if (status != REG_OK) { apr_file_printf(local_stderr, "Regular expression match failed (%s)\n", rp->url[rp->current_url].responsetemplate); return APR_EGENERAL; } size = match[1].rm_eo - match[1].rm_so + 1; newValue = apr_palloc(rp->pool, size); apr_cpystrn(newValue, resp->rbuf + match[1].rm_so, size); apr_hash_set(rp->state, rp->url[rp->current_url].responsename, rp->url[rp->current_url].responselen, newValue); regfree(&re); } if (rp->url[rp->current_url].responsescript) { int exitcode = 0; apr_status_t rv; apr_proc_t *proc; apr_pollfd_t pipeout; apr_pollset_t *pollset; apr_procattr_t *procattr; apr_size_t nbytes, wbytes; char buf[255]; char **args; const char *progname; if ((rv = apr_procattr_create(&procattr, rp->pool)) != APR_SUCCESS) { apr_file_printf(local_stderr, "apr_procattr_create failed for '%s': %s\n", rp->url[rp->current_url].responsescript, apr_strerror(rv, buf, sizeof(buf))); return rv; } if ((rv = apr_procattr_io_set(procattr, APR_FULL_BLOCK, APR_NO_PIPE, APR_NO_PIPE)) != APR_SUCCESS) { apr_file_printf(local_stderr, "apr_procattr_io_set failed for '%s': %s\n", rp->url[rp->current_url].responsescript, apr_strerror(rv, buf, sizeof(buf))); return rv; } if ((rv = apr_procattr_error_check_set(procattr, 1)) != APR_SUCCESS) { apr_file_printf(local_stderr, "apr_procattr_error_check_set failed " "for '%s': %s\n", rp->url[rp->current_url].responsescript, apr_strerror(rv, buf, sizeof(buf))); return rv; } apr_tokenize_to_argv(rp->url[rp->current_url].responsescript, &args, rp->pool); progname = apr_pstrdup(rp->pool, args[0]); proc = (apr_proc_t *)apr_pcalloc(rp->pool, sizeof(*proc)); /* create process */ if ((rv = apr_proc_create(proc, progname, (const char * const *)args, NULL, procattr, rp->pool)) != APR_SUCCESS) { apr_file_printf(local_stderr, "Can't spawn postprocess script '%s': %s\n", rp->url[rp->current_url].responsescript, apr_strerror(rv, buf, sizeof(buf))); return rv; } if ((rv = apr_file_pipe_timeout_set(proc->in, apr_time_from_sec(10))) != APR_SUCCESS) { apr_file_printf(local_stderr, "apr_file_pipe_timeout_set failed for '%s': %s\n", rp->url[rp->current_url].responsescript, apr_strerror(rv, buf, sizeof(buf))); return rv; } apr_pollset_create(&pollset, 1, rp->pool, 0); pipeout.desc_type = APR_POLL_FILE; pipeout.reqevents = APR_POLLOUT; pipeout.desc.f = proc->in; pipeout.client_data = NULL; apr_pollset_add(pollset, &pipeout); wbytes = 0; nbytes = strlen(resp->rbuf); while (wbytes < nbytes) { apr_size_t bytes; apr_int32_t nrdes; const apr_pollfd_t *ardes = NULL; const apr_pollfd_t *rdes; if ((rv = apr_pollset_poll(pollset, apr_time_from_sec(10), &nrdes, &ardes)) != APR_SUCCESS) { apr_file_printf(local_stderr, "error writing data to script '%s': %s\n", rp->url[rp->current_url].responsescript, apr_strerror(rv, buf, sizeof(buf))); return rv; } /* there can be only one descriptor... */ rdes = &(ardes[0]); bytes = nbytes; apr_file_write(rdes->desc.f, resp->rbuf, &bytes); wbytes += bytes; } apr_pollset_remove(pollset, &pipeout); apr_file_close(proc->in); if ((rv = apr_proc_wait(proc, &exitcode, NULL, APR_WAIT)) != APR_CHILD_DONE) { apr_file_printf(local_stderr, "apr_proc_wait failed for '%s': %s\n", rp->url[rp->current_url].responsescript, apr_strerror(rv, buf, sizeof(buf))); return rv; } if (exitcode != 0) { apr_file_printf(local_stderr, "Postprocess script '%s' failed, exit code '%i'\n", rp->url[rp->current_url].responsescript, exitcode); return APR_EGENERAL; } } return APR_SUCCESS; } apr_status_t verify_200(int *verified, profile_t *profile, request_t *req, response_t *resp) { round_robin_profile_t *rp; int res; rp = (round_robin_profile_t*) profile; res = memcmp(resp->rbuf, "HTTP/1.1 2", 10); if (!res) *verified = FLOOD_VALID; else if (memcmp(resp->rbuf + 9, "3", 1) == 0) /* Accept 3xx as okay. */ *verified = FLOOD_VALID; else if (memcmp(resp->rbuf, "HTTP/1.0 2", 10) == 0) /* HTTP/1.0 is ok. */ *verified = FLOOD_VALID; else *verified = FLOOD_INVALID; return APR_SUCCESS; } apr_status_t verify_status_code(int *verified, profile_t *profile, request_t *req, response_t *resp) { const char delimiter = ' '; char *state, *protocol, *scode; protocol = apr_strtok(resp->rbuf, &delimiter, &state); scode = apr_strtok(NULL, &delimiter, &state); if (scode[0] == '2' || scode[0] == '3') { *verified = FLOOD_VALID; } else { *verified = FLOOD_INVALID; } return APR_SUCCESS; } int round_robin_loop_condition(profile_t *profile) { round_robin_profile_t *rp; int real_current_url; rp = (round_robin_profile_t*)profile; real_current_url = rp->current_url; /* save the real one before we try to increment */ rp->current_url++; /* Adjust counters for profile */ if (rp->current_url >= rp->urls) { rp->current_url = 0; /* Loop cond tells us when to stop. */ rp->current_round++; } #ifdef PROFILE_DEBUG apr_file_printf(local_stdout, "Round %d of %d, %s.\n", rp->current_round, rp->execute_rounds, (rp->current_round < rp->execute_rounds ? "Continuing" : "Finished")); #endif /* PROFILE_DEBUG */ if (rp->current_round >= rp->execute_rounds) return 0; else { /* we'll continue, so do delay stuff now if necessary */ /* If they want a sleep, do it now. */ if (rp->url[real_current_url].postdelay) { apr_int64_t real_postdelay = rp->url[real_current_url].postdelay; /* If the delay has a precision, adjust the * delay by some random fraction of the precision here */ if (rp->url[real_current_url].postdelayprecision) { /* FIXME: this should be more portable, like apr_generate_random_bytes() */ float factor = -1.0 + (2.0*rand()/(RAND_MAX+1.0)); real_postdelay += rp->url[real_current_url].postdelayprecision * factor; } /* we can only delay positive times, can't go back in time :( */ if (real_postdelay < 0) real_postdelay = 0; /* only bother going to sleep if we generated a delay */ if (real_postdelay > 0) apr_sleep(real_postdelay); } return 1; } } apr_status_t round_robin_profile_destroy(profile_t *profile) { /* FIXME: free() the memory used by this profile, or reset() the pool * (or whatever semantics apr uses, I dunno...) -aaron */ return APR_SUCCESS; }