tinyproxy/src/conffile.c

683 lines
20 KiB
C
Raw Normal View History

/* $Id: conffile.c,v 1.4 2004-08-24 16:34:22 rjkaes Exp $
*
* Parses the configuration file and sets up the config_s structure for
* use by the application. This file replaces the old grammar.y and
* scanner.l files. It takes up less space and _I_ think is easier to
* add new directives to. Who knows if I'm right though.
*
* Copyright (C) 2004 Robert James Kaes (rjkaes@users.sourceforge.net)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2, or (at your option) any
* later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*/
#include "tinyproxy.h"
#include "conffile.h"
#include "acl.h"
#include "anonymous.h"
#include "child.h"
#include "filter.h"
#include "heap.h"
#include "htmlerror.h"
#include "log.h"
#include "reqs.h"
/*
* The configuration directives are defined in the structure below. Each
* directive requires a regular expression to match against, and a
* function to call when the regex is matched.
*
* Below are defined certain constant regular expression strings that
* can (and likely should) be used when building the regex for the
* given directive.
*/
#define WS "[[:space:]]+"
#define STR "\"([^\"]+)\""
#define BOOL "(yes|on|no|off)"
#define INT "((0x)?[[:digit:]]+)"
#define ALNUM "([-a-z0-9._]+)"
#define IP "((([0-9]{1,3})\\.){3}[0-9]{1,3})"
#define IPMASK "(" IP "(/[[:digit:]]+)?)"
#define BEGIN "^[[:space:]]*"
#define END "[[:space:]]*$"
/*
* Limit the maximum number of substring matches to a reasonably high
* number. Given the usual structure of the configuration file, sixteen
* substring matches should be plenty.
*/
#define RE_MAX_MATCHES 16
/*
* All configuration handling functions are REQUIRED to be defined
* with the same function template as below.
*/
typedef int (*CONFFILE_HANDLER)(struct config_s*, const char*, regmatch_t[]);
/*
* Define the pattern used by any directive handling function. The
* following arguments are defined:
*
* struct config_s* conf pointer to the current configuration structure
* const char* line full line matched by the regular expression
* regmatch_t match[] offsets to the substrings matched
*
* The handling function must return 0 if the directive was processed
* properly. Any errors are reported by returning a non-zero value.
*/
#define HANDLE_FUNC(func) int func(struct config_s* conf, const char* line, regmatch_t match[])
/*
* List all the handling functions. These are defined later, but they need
* to be in-scope before the big structure below.
*/
static HANDLE_FUNC(handle_nop) { return 0; } /* do nothing function */
static HANDLE_FUNC(handle_allow);
static HANDLE_FUNC(handle_anonymous);
static HANDLE_FUNC(handle_bind);
static HANDLE_FUNC(handle_bindsame);
static HANDLE_FUNC(handle_connectport);
static HANDLE_FUNC(handle_defaulterrorfile);
static HANDLE_FUNC(handle_deny);
static HANDLE_FUNC(handle_errorfile);
static HANDLE_FUNC(handle_filter);
static HANDLE_FUNC(handle_filtercasesensitive);
static HANDLE_FUNC(handle_filterdefaultdeny);
static HANDLE_FUNC(handle_filterextended);
static HANDLE_FUNC(handle_filterurls);
static HANDLE_FUNC(handle_group);
static HANDLE_FUNC(handle_listen);
static HANDLE_FUNC(handle_logfile);
static HANDLE_FUNC(handle_loglevel);
static HANDLE_FUNC(handle_maxclients);
static HANDLE_FUNC(handle_maxrequestsperchild);
static HANDLE_FUNC(handle_maxspareservers);
static HANDLE_FUNC(handle_minspareservers);
static HANDLE_FUNC(handle_pidfile);
static HANDLE_FUNC(handle_port);
static HANDLE_FUNC(handle_reversebaseurl);
static HANDLE_FUNC(handle_reversemagic);
static HANDLE_FUNC(handle_reverseonly);
static HANDLE_FUNC(handle_reversepath);
static HANDLE_FUNC(handle_startservers);
static HANDLE_FUNC(handle_statfile);
static HANDLE_FUNC(handle_stathost);
static HANDLE_FUNC(handle_syslog);
static HANDLE_FUNC(handle_timeout);
//static HANDLE_FUNC(handle_upstream);
static HANDLE_FUNC(handle_user);
static HANDLE_FUNC(handle_viaproxyname);
static HANDLE_FUNC(handle_xtinyproxy);
/*
* This macro can be used to make standard directives in the form:
* directive arguments [arguments ...]
*
* The directive itself will be the first matched substring.
*
* Note that this macro is not required. As you can see below, the
* comment and blank line elements are defined explicitly since they
* do not follow the pattern above. This macro is for convenience
* only.
*/
#define STDCONF(d, re, func) { BEGIN "(" d ")" WS re END, func, NULL }
/*
* Holds the regular expression used to match the configuration directive,
* the function pointer to the routine to handle the directive, and
* for internal use, a pointer to the compiled regex so it only needs
* to be compiled one.
*/
struct {
const char* re;
CONFFILE_HANDLER handler;
regex_t* cre;
} directives[] = {
/* comments */
{ BEGIN "#", handle_nop },
/* blank lines */
{ "^[[:space:]]+$", handle_nop },
/* string arguments */
STDCONF("logfile", STR, handle_logfile),
STDCONF("pidfile", STR, handle_pidfile),
STDCONF("anonymous", STR, handle_anonymous),
STDCONF("viaproxyname", STR, handle_viaproxyname),
STDCONF("defaulterrorfile", STR, handle_defaulterrorfile),
STDCONF("statfile", STR, handle_statfile),
STDCONF("stathost", STR, handle_stathost),
STDCONF("xtinyproxy", STR, handle_xtinyproxy),
/* boolean arguments */
STDCONF("syslog", BOOL, handle_syslog),
STDCONF("bindsame", BOOL, handle_bindsame),
/* integer arguments */
STDCONF("port", INT, handle_port),
STDCONF("maxclients", INT, handle_maxclients),
STDCONF("maxspareservers", INT, handle_maxspareservers),
STDCONF("minspareservers", INT, handle_minspareservers),
STDCONF("startservers", INT, handle_startservers),
STDCONF("maxrequestsperchild", INT, handle_maxrequestsperchild),
STDCONF("timeout", INT, handle_timeout),
STDCONF("connectport", INT, handle_connectport),
/* alphanumeric arguments */
STDCONF("user", ALNUM, handle_user),
STDCONF("group", ALNUM, handle_group),
/* ip arguments */
STDCONF("listen", IP, handle_listen),
STDCONF("allow", "(" IPMASK "|" ALNUM ")", handle_allow),
STDCONF("deny", "(" IPMASK "|" ALNUM ")", handle_deny),
STDCONF("bind", IP, handle_bind),
/* error files */
STDCONF("errorfile", INT WS STR, handle_errorfile),
/* filtering */
STDCONF("filter", STR, handle_filter),
STDCONF("filterurls", BOOL, handle_filterurls),
STDCONF("filterextended", BOOL, handle_filterextended),
STDCONF("filterdefaultdeny", BOOL, handle_filterdefaultdeny),
STDCONF("filtercasesensitive", BOOL, handle_filtercasesensitive),
/* Reverse proxy arguments */
STDCONF("reversebaseurl", STR, handle_reversebaseurl),
STDCONF("reverseonly", BOOL, handle_reverseonly),
STDCONF("reversemagic", BOOL, handle_reversemagic),
STDCONF("reversepath", STR WS "(" STR ")?", handle_reversepath),
/* upstream is rather complicated */
// { BEGIN "no" WS "upstream" WS STR END, handle_no_upstream },
// { BEGIN "upstream" WS IP ":" INT "(" WS STR ")" END, handle_upstream },
/* loglevel */
STDCONF("loglevel", "(critical|error|warning|notice|connect|info)", handle_loglevel)
};
const unsigned int ndirectives = sizeof(directives)/sizeof(directives[0]);
/*
* Compiles the regular expressions used by the configuration file. This
* routine MUST be called before trying to parse the configuration file.
*
* Returns 0 on success; negative upon failure.
*/
int
config_compile(void)
{
int i, r;
for (i = 0; i != ndirectives; ++i) {
assert(directives[i].handler);
assert(!directives[i].cre);
directives[i].cre = safemalloc(sizeof(regex_t));
if (!directives[i].cre)
return -1;
r = regcomp(directives[i].cre,
directives[i].re,
REG_EXTENDED | REG_ICASE | REG_NEWLINE);
if (r) return r;
}
return 0;
}
/*
* Attempt to match the supplied line with any of the configuration
* regexes defined above. If a match is found, call the handler
* function to process the directive.
*
* Returns 0 if a match was found and successfully processed; otherwise,
* a negative number is returned.
*/
static int
check_match(struct config_s* conf, const char* line)
{
regmatch_t match[RE_MAX_MATCHES];
unsigned int i;
assert(ndirectives > 0);
for (i = 0; i != ndirectives; ++i) {
assert(directives[i].cre);
if (!regexec(directives[i].cre, line, RE_MAX_MATCHES, match, 0))
return (*directives[i].handler)(conf, line, match);
}
return -1;
}
/*
* Parse the previously opened configuration stream.
*/
int
config_parse(struct config_s* conf, FILE* f)
{
char buffer[1024]; /* 1KB lines should be plenty */
unsigned long lineno = 1;
while (fgets(buffer, sizeof(buffer), f)) {
if (check_match(conf, buffer)) {
printf("Syntax error on line %ld\n", lineno);
return 1;
}
++lineno;
}
return 0;
}
/***********************************************************************
*
* The following are basic data extraction building blocks that can
* be used to simplify the parsing of a directive.
*
***********************************************************************/
static char*
get_string_arg(const char* line, regmatch_t* match)
{
char *p;
const unsigned int len = match->rm_eo - match->rm_so;
assert(line);
assert(len > 0);
p = safemalloc(len + 1);
if (!p)
return NULL;
memcpy(p, line + match->rm_so, len);
p[len] = '\0';
return p;
}
static int
set_string_arg(char** var, const char* line, regmatch_t* match)
{
char* arg = get_string_arg(line, match);
if (!arg)
return -1;
*var = safestrdup(arg);
safefree(arg);
return *var ? 0 : -1;
}
static int
get_bool_arg(const char* line, regmatch_t* match)
{
const char* p = line + match->rm_so;
assert(line);
assert(match && match->rm_so != -1);
/* "y"es or o"n" map as true, otherwise it's false. */
if (tolower(p[0]) == 'y' || tolower(p[1]) == 'n')
return 1;
else
return 0;
}
static int
set_bool_arg(unsigned int* var, const char* line, regmatch_t* match)
{
assert(var);
assert(line);
assert(match && match->rm_so != -1);
*var = get_bool_arg(line, match);
return 0;
}
static inline long int
get_int_arg(const char* line, regmatch_t* match)
{
assert(line);
assert(match && match->rm_so != -1);
return strtol(line + match->rm_so, NULL, 0);
}
static int
set_int_arg(int long* var, const char* line, regmatch_t* match)
{
assert(var);
assert(line);
assert(match);
*var = get_int_arg(line, match);
return 0;
}
/***********************************************************************
*
* Below are all the directive handling functions. You will notice
* that most of the directives delegate to one of the basic data
* extraction routines. This is deliberate. To add a new directive
* to tinyproxy only requires you to define the regular expression
* above and then figure out what data extract routine to use.
*
* However, you will also notice that more complicated directives are
* possible. You can make your directive as complicated as you require
* to express a solution to the problem you're tackling.
*
* See the definition/comment about the HANDLE_FUNC() macro to learn
* what arguments are supplied to the handler, and to determine what
* values to return.
*
***********************************************************************/
static HANDLE_FUNC(handle_logfile)
{
return set_string_arg(&conf->logf_name, line, &match[2]);
}
static HANDLE_FUNC(handle_pidfile)
{
return set_string_arg(&conf->pidpath, line, &match[2]);
}
static HANDLE_FUNC(handle_anonymous)
{
char *arg = get_string_arg(line, &match[2]);
if (!arg)
return -1;
anonymous_insert(arg);
safefree(arg);
return 0;
}
static HANDLE_FUNC(handle_viaproxyname)
{
int r = set_string_arg(&conf->via_proxy_name, line, &match[2]);
if (r) return r;
log_message(LOG_INFO,
"Setting \"Via\" header proxy to %s",
conf->via_proxy_name);
return 0;
}
static HANDLE_FUNC(handle_defaulterrorfile)
{
return set_string_arg(&conf->errorpage_undef, line, &match[2]);
}
static HANDLE_FUNC(handle_statfile)
{
return set_string_arg(&conf->statpage, line, &match[2]);
}
static HANDLE_FUNC(handle_stathost)
{
int r = set_string_arg(&conf->stathost, line, &match[2]);
if (r) return r;
log_message(LOG_INFO,
"Stathost set to \"%s\"",
conf->stathost);
return 0;
}
static HANDLE_FUNC(handle_xtinyproxy)
{
#ifdef XTINYPROXY_ENABLE
return set_string_arg(&conf->my_domain, line, &match[2]);
#else
fprintf(stderr,
"XTinyproxy NOT Enabled! Recompile with --enable-xtinyproxy\n");
return 1;
#endif
}
static HANDLE_FUNC(handle_syslog)
{
#ifdef HAVE_SYSLOG_H
return set_bool_arg(&conf->syslog, line, &match[2]);
#else
fprintf(stderr,
"Syslog support not compiled in executable.\n");
return 1;
#endif
}
static HANDLE_FUNC(handle_bindsame)
{
int r = set_bool_arg(&conf->bindsame, line, &match[2]);
if (r) return r;
log_message(LOG_INFO, "Binding outgoing connection to incoming IP");
return 0;
}
static HANDLE_FUNC(handle_port)
{
return set_int_arg((long int*)&conf->port, line, &match[2]);
}
static HANDLE_FUNC(handle_maxclients)
{
child_configure(CHILD_MAXCLIENTS, get_int_arg(line, &match[2]));
return 0;
}
static HANDLE_FUNC(handle_maxspareservers)
{
child_configure(CHILD_MAXSPARESERVERS, get_int_arg(line, &match[2]));
return 0;
}
static HANDLE_FUNC(handle_minspareservers)
{
child_configure(CHILD_MINSPARESERVERS, get_int_arg(line, &match[2]));
return 0;
}
static HANDLE_FUNC(handle_startservers)
{
child_configure(CHILD_STARTSERVERS, get_int_arg(line, &match[2]));
return 0;
}
static HANDLE_FUNC(handle_maxrequestsperchild)
{
child_configure(CHILD_MAXREQUESTSPERCHILD, get_int_arg(line, &match[2]));
return 0;
}
static HANDLE_FUNC(handle_timeout)
{
return set_int_arg((long int*)&conf->idletimeout, line, &match[2]);
}
static HANDLE_FUNC(handle_connectport)
{
add_connect_port_allowed(get_int_arg(line, &match[2]));
return 0;
}
static HANDLE_FUNC(handle_user)
{
return set_string_arg(&conf->username, line, &match[2]);
}
static HANDLE_FUNC(handle_group)
{
return set_string_arg(&conf->group, line, &match[2]);
}
static HANDLE_FUNC(handle_allow)
{
char* arg = get_string_arg(line, &match[2]);
insert_acl(arg, ACL_ALLOW);
safefree(arg);
return 0;
}
static HANDLE_FUNC(handle_deny)
{
char *arg = get_string_arg(line, &match[2]);
insert_acl(arg, ACL_DENY);
safefree(arg);
return 0;
}
static HANDLE_FUNC(handle_bind)
{
#ifndef TRANSPARENT_PROXY
int r = set_string_arg(&conf->bind_address, line, &match[2]);
if (r) return r;
log_message(LOG_INFO,
"Outgoing connections bound to IP %s",
conf->bind_address);
return 0;
#else
fprintf(stderr,
"\"Bind\" cannot be used with transparent support enabled.\n");
return 1;
#endif
}
static HANDLE_FUNC(handle_listen)
{
int r = set_string_arg(&conf->ipAddr, line, &match[2]);
if (r) return r;
log_message(LOG_INFO, "Listing on IP %s", conf->ipAddr);
return 0;
}
static HANDLE_FUNC(handle_errorfile)
{
/*
* Because an integer is defined as ((0x)?[[:digit:]]+) _two_
* match places are used. match[2] matches the full digit
* string, while match[3] matches only the "0x" part if
* present. This is why the "string" is located at
* match[4] (rather than the more intuitive match[3].
*/
long int err = get_int_arg(line, &match[2]);
char *page = get_string_arg(line, &match[4]);
add_new_errorpage(page, err);
safefree(page);
return 0;
}
/*
* Log level's strings.
*/
struct log_levels_s {
const char* string;
int level;
};
static struct log_levels_s log_levels[] = {
{ "critical", LOG_CRIT },
{ "error", LOG_ERR },
{ "warning", LOG_WARNING },
{ "notice", LOG_NOTICE },
{ "connect", LOG_CONN },
{ "info", LOG_INFO }
};
static HANDLE_FUNC(handle_loglevel)
{
static const unsigned int nlevels = sizeof(log_levels)/sizeof(log_levels[0]);
unsigned int i;
char *arg = get_string_arg(line, &match[2]);
for (i = 0; i != nlevels; ++i) {
if (!strcasecmp(arg, log_levels[i].string)) {
set_log_level(log_levels[i].level);
return 0;
}
}
return -1;
}
#ifdef FILTER_ENABLE
static HANDLE_FUNC(handle_filter)
{
return set_string_arg(&conf->filter, line, &match[2]);
}
static HANDLE_FUNC(handle_filterurls)
{
return set_bool_arg(&conf->filter_url, line, &match[2]);
}
static HANDLE_FUNC(handle_filterextended)
{
return set_bool_arg(&conf->filter_extended, line, &match[2]);
}
static HANDLE_FUNC(handle_filterdefaultdeny)
{
assert(match[2].rm_so != -1);
if (get_bool_arg(line, &match[2]))
filter_set_default_policy(FILTER_DEFAULT_DENY);
return 0;
}
static HANDLE_FUNC(handle_filtercasesensitive)
{
return set_bool_arg(&conf->filter_casesensitive, line, &match[2]);
}
#else
static int
no_filter_support(void)
{
fprintf(stderr, "Filter NOT Enabled! Recompile with --enable-filter\n");
return -1;
}
static HANDLE_FUNC(handle_filter) { return no_filter_support(); }
static HANDLE_FUNC(handle_filtercasesensitive) { return no_filter_support(); }
static HANDLE_FUNC(handle_filterdefaultdeny) { return no_filter_support(); }
static HANDLE_FUNC(handle_filterextended) { return no_filter_support(); }
static HANDLE_FUNC(handle_filterurls) { return no_filter_support(); }
#endif
#ifdef REVERSE_SUPPORT
static HANDLE_FUNC(handle_reverseonly)
{
return set_bool_arg(&conf->reverseonly, line, &match[2]);
}
static HANDLE_FUNC(handle_reversemagic)
{
return set_bool_arg(&conf->reversemagic, line, &match[2]);
}
static HANDLE_FUNC(handle_reversebaseurl)
{
return set_string_arg(&conf->reversebaseurl, line, &match[2]);
}
static HANDLE_FUNC(handle_reversepath)
{
/*
* The second string argument is optional.
*/
char *arg1, *arg2;
arg1 = get_string_arg(line, &match[2]);
if (!arg1) return -1;
if (match[3].rm_so != -1) {
arg2 = get_string_arg(line, &match[3]);
if (!arg2) {
safefree(arg1);
return -1;
}
reversepath_add(arg1, arg2);
safefree(arg1);
safefree(arg2);
} else {
reversepath_add(NULL, arg1);
safefree(arg1);
}
return 0;
}
#else
static int
no_reverse_support(void)
{
fprintf(stderr,
"Reverse Proxy NOT Enabled! Recompile with --enable-reverse\n");
return -1;
}
static HANDLE_FUNC(handle_reversebaseurl) { return no_reverse_support(); }
static HANDLE_FUNC(handle_reversemagic) { return no_reverse_support(); }
static HANDLE_FUNC(handle_reverseonly) { return no_reverse_support(); }
static HANDLE_FUNC(handle_reversepath) { return no_reverse_support(); }
#endif