blob: 097373fa21ef59b00bc5648bfe0be45a67d81603 [file] [log] [blame]
/* 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;
}