/**
* Licensed 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
*
*
* 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 written @ BBC by Graham Leggett
* Copyright 2009-2011 British Broadcasting Corporation
*
*/
#include "apr.h"
#include "apr_lib.h"
#include "apr_buckets.h"
#include "apr_file_io.h"
#include "apr_file_info.h"
#include "apr_hash.h"
#include "apr_poll.h"
#include "apr_portable.h"
#include "apr_getopt.h"
#include "apr_signal.h"
#include "apr_strings.h"
#include "apr_uuid.h"
#include <stdlib.h>
#endif
#include <string.h>
#endif
#include "ap_release.h"
#define DEFAULT_MAXLINES 0
#define DEFAULT_MAXSIZE 0
#define DEFAULT_PREFIX 0
#define DEFAULT_NONBLOCK 0
typedef struct file_rec
{
const char *directory;
int limit;
} file_rec;
typedef struct uuid_rec
{
const char *uuid;
int direction;
} uuid_rec;
typedef struct filter_rec
{
const char *prefix;
} filter_rec;
typedef struct header_rec
{
int direction;
} header_rec;
static const apr_getopt_option_t
cmdline_opts[] =
{
/* commands */
{
"file",
'f',
1,
" --file, -f <name>\t\t\tFile to read the firehose from.\n\t\t\t\t\tDefaults to stdin." },
{
"output-directory",
'd',
1,
" --output-directory, -o <name>\tDirectory to write demuxed connections\n\t\t\t\t\tto." },
{
"uuid",
'u',
1,
" --uuid, -u <uuid>\t\t\tThe UUID of the connection to\n\t\t\t\t\tdemultiplex. Can be specified more\n\t\t\t\t\tthan once." },
/* { "output-host", 'h', 1,
" --output-host, -h <hostname>\tHostname to write demuxed connections to." },*/
/* {
"speed",
's',
1,
" --speed, -s <factor>\tSpeed up or slow down demuxing\n\t\t\t\tby the given factor." },*/
{ "help", 258, 0, " --help, -h\t\t\t\tThis help text." },
{ "version", 257, 0,
" --version\t\t\t\tDisplay the version of the program." },
{ NULL } };
"Firehose demultiplexes the given stream of multiplexed connections, and\n" \
"writes each connection to a file, or to a socket as appropriate.\n" \
"\n" \
"When writing to files, each connection is placed into a dedicated file\n" \
"named after the UUID of the connection within the stream. Separate files\n" \
"will be created if requests and responses are found in the stream.\n" \
"\n" \
"If an optional prefix is specified as a parameter, connections that start\n" \
"with the given prefix will be included. The prefix needs to fit completely\n" \
"within the first fragment for a successful match to occur.\n" \
"\n"
/* "When writing to a socket, new connections\n"
* "are opened for each connection in the stream, allowing it to be possible to\n"
* "'replay' traffic recorded by one server to other server.\n"
* "\n\n"
*/
/**
* Who are we again?
*/
{
}
/**
* Help the long suffering end user.
*/
const apr_getopt_option_t opts[])
{
int i = 0;
if (header) {
}
i++;
}
if (footer) {
}
}
/**
* Cleanup a uuid record. Removes the record from the uuid hashtable in files.
*/
{
NULL);
}
NULL);
}
return APR_SUCCESS;
}
/**
* Create a uuid record, register a cleanup for it's destruction.
*/
{
}
}
return APR_SUCCESS;
}
/**
* Process the end of the fragment body.
*
* This function renames the completed stream to it's final name.
*/
{
: ".request", NULL);
if (APR_SUCCESS == status) {
if (APR_SUCCESS == status) {
"Could not rename file '%s' to '%s' for fragment write: %pm\n",
}
}
else {
}
}
else {
"Could not merge directory '%s' with file '%s': %pm\n",
}
}
else {
"Could not merge directory '%s' with file '%s': %pm\n",
}
return status;
}
/**
* Check if the fragment matches on of the prefixes.
*/
{
void *val;
match = 1;
break;
}
match = 0;
}
return match;
}
/**
* Process part of the fragment body, given the header parameters.
*
* Currently, we append it to a file named after the UUID of the connection.
*
* The file is opened on demand and closed when done, so that we are
* guaranteed never to hit a file handle limit (within reason).
*/
{
}
: ".request.part", NULL);
if (APR_SUCCESS == status) {
NULL))) {
"Could not write fragment body to file '%s': %pm\n",
}
}
else {
"Could not open file '%s' for fragment write: %pm\n",
}
}
else {
"Could not merge directory '%s' with file '%s': %pm\n",
}
return status;
}
/**
* Parse a chunk extension, detect overflow.
* There are two error cases:
* 1) If the conversion would require too many bits, a -1 is returned.
* 2) If the conversion used the correct number of bits, but an overflow
* caused only the sign bit to flip, then that negative number is
* returned.
* In general, any negative number can be considered an overflow error.
*/
{
const char *b = *buf;
if (!apr_isxdigit(*b)) {
return APR_EGENERAL;
}
/* Skip leading zeros */
while (*b == '0') {
++b;
}
while (apr_isxdigit(*b) && (chunkbits > 0)) {
int xvalue = 0;
if (*b >= '0' && *b <= '9') {
xvalue = *b - '0';
}
else if (*b >= 'A' && *b <= 'F') {
}
else if (*b >= 'a' && *b <= 'f') {
}
chunkbits -= 4;
++b;
}
*buf = b;
if (apr_isxdigit(*b) && (chunkbits <= 0)) {
/* overflow */
return APR_EGENERAL;
}
return APR_SUCCESS;
}
/**
* Parse what might be a fragment header line.
*
* If the parse doesn't match for any reason, an error is returned, otherwise
* APR_SUCCESS.
*
* The header structure will be filled with the header values as parsed.
*/
{
int i;
return status;
}
if (!apr_isspace(*(str++))) {
return APR_EGENERAL;
}
return status;
}
if (!apr_isspace(*(str++))) {
return APR_EGENERAL;
}
return APR_EGENERAL;
}
str++;
if (!apr_isspace(*(str++))) {
return APR_EGENERAL;
}
for (i = 0; str[i] && i < APR_UUID_FORMATTED_LENGTH; i++) {
}
return APR_EGENERAL;
}
str += i;
if (!apr_isspace(*(str++))) {
return APR_EGENERAL;
}
return status;
}
if ((*(str++) != '\r')) {
return APR_EGENERAL;
}
if ((*(str++) != '\n')) {
return APR_EGENERAL;
}
return APR_EGENERAL;
}
return APR_SUCCESS;
}
/**
*
* If EOF is detected, this function returns.
*/
{
apr_bucket *b, *e;
int footer = 0;
const char *buf;
do {
/* when the pipe is closed, the pipe disappears from the brigade */
if (APR_BRIGADE_EMPTY(bb)) {
break;
}
if (len == HUGE_STRING_LEN) {
continue;
}
else if (footer) {
footer = 0;
continue;
}
}
else if (len > 0) {
if (APR_SUCCESS != status) {
continue;
}
else {
int ignore = 0;
}
}
/* does the count match what is expected? */
ignore = 1;
}
}
else {
/* must we ignore unknown uuids? */
ignore = 1;
}
/* is the counter not what we expect? */
ignore = 1;
}
/* otherwise, make a new uuid */
else {
}
}
"Could not read fragment body from input file: %pm\n", &status);
break;
}
while ((b = APR_BRIGADE_FIRST(bb)) && e != b) {
ignore = 1;
}
if (!ignore) {
}
continue;
}
}
if (!ignore) {
}
footer = 1;
continue;
}
else {
/* an empty header means end-of-connection */
if (!ignore) {
}
}
}
}
}
}
}
else {
"Could not read fragment header from input file: %pm\n", &status);
break;
}
} while (1);
return status;
}
/**
* Start the application.
*/
{
int optch;
const char *optarg;
/* lets get APR off the ground, and make sure it terminates cleanly */
return 1;
}
return 1;
}
#ifdef SIGPIPE
#endif
== APR_SUCCESS) {
switch (optch) {
case 'f': {
if (status != APR_SUCCESS) {
return 1;
}
break;
}
case 'd': {
if (status != APR_SUCCESS) {
return 1;
}
"Path '%s' isn't a directory\n", optarg);
return 1;
}
break;
}
case 'u': {
break;
}
case 257: {
return 0;
}
case 258: {
return 0;
}
}
}
return 1;
}
/* read filters from the command line */
filter);
}
/* warn people if any non blocking writes failed */
}
if (APR_SUCCESS != status) {
return 1;
}
return 0;
}