0
0
mirror of https://github.com/darkk/redsocks.git synced 2025-08-25 11:15:30 +00:00
redsocks/parser.c
Leonid Evdokimov 7963de73d4 Add on_proxy_fail to inform user's browser about sort of failure
Use the feature with care, enable it only for HTTP port to avoid
confusion, no client protocol detection is done at the moment.
2016-04-13 02:30:08 +03:00

642 lines
15 KiB
C

/* redsocks - transparent TCP-to-proxy redirector
* Copyright (C) 2007-2011 Leonid Evdokimov <leon@darkk.net.ru>
*
* 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
*
* 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.
*/
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <assert.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#include "utils.h"
#include "parser.h"
#include "log.h"
#define FREE(ptr) do { free(ptr); ptr = NULL; } while (0)
typedef int (*value_parser)(parser_context *context, void *addr, const char *token);
struct parser_context_t {
FILE *fd;
parser_section *sections;
int line;
int error;
struct {
size_t size;
size_t filled;
char *data;
} buffer;
};
void parser_error(parser_context *context, const char *fmt, ...)
{
va_list ap;
struct evbuffer *buff = evbuffer_new();
const char *msg;
va_start(ap, fmt);
if (buff) {
evbuffer_add_vprintf(buff, fmt, ap);
msg = (const char*)evbuffer_pullup(buff, -1);
}
else
msg = error_lowmem;
va_end(ap);
context->error = 1;
fprintf(stderr, "file parsing error at line %u: %s\n", context->line, msg);
if (buff)
evbuffer_free(buff);
}
parser_context* parser_start(FILE *fd)
{
parser_context *ret = calloc(1, sizeof(parser_context));
if (!ret)
return NULL;
ret->fd = fd;
ret->buffer.size = 128; // should be big enough to fetch whole ``line``
ret->buffer.data = malloc(ret->buffer.size);
if (!ret->buffer.data) {
free(ret);
return NULL;
}
return ret;
}
void parser_add_section(parser_context *context, parser_section *section)
{
section->next = context->sections;
context->sections = section;
section->context = context;
}
void parser_stop(parser_context *context)
{
free(context->buffer.data);
free(context);
}
static char unescape(int c)
{
switch (c) {
case 'n': return '\n';
case 't': return '\t';
case 'r': return '\r';
case '\\': return '\\';
case '\'': return '\'';
case '\"': return '\"';
default: return 0;
}
}
/** returns NULL on invalid OR incomplete token
*/
static char *gettoken(parser_context *context, char **iter)
{
char *ret = NULL;
size_t len = 0;
enum {
gt_cstr,
gt_plainstr
} copytype;
// skip spaces
while (**iter && isspace(**iter))
(*iter)++;
if ( !**iter )
return NULL;
// count strlen() of output buffer
if ( **iter == '\"' ) { // string with escapes and spaces
char *p = *iter + 1;
copytype = gt_cstr;
while ( 1 ) {
if (*p == '\0')
return NULL;
if (*p == '\"')
break;
if (*p == '\\') {
if ( p[1] != '\0') {
if ( unescape(p[1]) )
p++;
else {
parser_error(context, "unknown escaped char after \\");
return NULL;
}
}
else {
return NULL;
}
}
len++;
p++;
}
}
else if ( isdigit(**iter) ) { // integer OR IP/NETMASK
char *p = *iter;
copytype = gt_plainstr;
while ( 1 ) {
if ( *p == '\0' )
return NULL;
else if ( isdigit(*p) || *p == '.' )
p++;
else if ( *p == '/' ) {
if (isdigit(p[1]))
p++;
else if (p[1] == '/' || p[1] == '*') // comment token is coming!
break;
else
return NULL;
}
else
break;
}
len = p - *iter;
}
else if ( isalpha(**iter) ) { // simple-string
char *p = *iter;
copytype = gt_plainstr;
while ( 1 ) {
if ( *p == '\0' )
return NULL;
else if (isalnum(*p) || *p == '_' || *p == '.' || *p == '-') // for domain-names
p++;
else
break;
}
len = p - *iter;
}
else if ( **iter == '{' || **iter == '}' || **iter == '=' || **iter == ';' ) { // config punctuation
copytype = gt_plainstr;
len = 1;
}
else if ( **iter == '/' && ( (*iter)[1] == '/' || (*iter)[1] == '*' ) ) { // comment-start
copytype = gt_plainstr;
len = 2;
}
else {
parser_error(context, "unexpected char");
return NULL;
}
ret = malloc(len + 1);
if (!ret) {
parser_error(context, "malloc failed");
return NULL;
}
if (copytype == gt_cstr) {
char *p = ret;
(*iter)++;
while ( 1 ) {
if (**iter == '\"') {
(*iter)++;
break;
}
if (**iter == '\\') {
*p = unescape(*(*iter + 1));
(*iter)++;
}
else {
*p = **iter;
}
(*iter)++;
p++;
}
*p = 0;
}
else if (copytype == gt_plainstr) {
memcpy(ret, *iter, len);
*iter += len;
ret[len] = 0;
}
return ret;
}
static void context_filled_add(parser_context *context, int shift)
{
context->buffer.filled += shift;
context->buffer.data[context->buffer.filled] = 0;
}
static void context_filled_set(parser_context *context, size_t val)
{
context->buffer.filled = val;
context->buffer.data[context->buffer.filled] = 0;
}
static int vp_pbool(parser_context *context, void *addr, const char *token)
{
char *strtrue[] = { "ok", "on", "yes", "true" };
char *strfalse[] = { "off", "no", "false" };
char **tpl;
FOREACH(tpl, strtrue)
if (strcmp(token, *tpl) == 0) {
*(bool*)addr = true;
return 0;
}
FOREACH(tpl, strfalse)
if (strcmp(token, *tpl) == 0) {
*(bool*)addr = false;
return 0;
}
parser_error(context, "boolean is not parsed");
return -1;
}
static int vp_disclose_src(parser_context *context, void *addr, const char *token)
{
enum disclose_src_e *dst = addr;
struct { char *name; enum disclose_src_e value; } opt[] = {
{ "off", DISCLOSE_NONE },
{ "no", DISCLOSE_NONE },
{ "false", DISCLOSE_NONE },
{ "X-Forwarded-For", DISCLOSE_X_FORWARDED_FOR },
{ "Forwarded_ip", DISCLOSE_FORWARDED_IP },
{ "Forwarded_ipport", DISCLOSE_FORWARDED_IPPORT },
};
for (int i = 0; i < SIZEOF_ARRAY(opt); ++i) {
if (strcmp(token, opt[i].name) == 0) {
*dst = opt[i].value;
return 0;
}
}
parser_error(context, "disclose_src <%s> is not parsed", token);
return -1;
}
static int vp_on_proxy_fail(parser_context *context, void *addr, const char *token)
{
enum on_proxy_fail_e *dst = addr;
struct { char *name; enum on_proxy_fail_e value; } opt[] = {
{ "close", ONFAIL_CLOSE },
{ "forward_http_err", ONFAIL_FORWARD_HTTP_ERR },
};
for (int i = 0; i < SIZEOF_ARRAY(opt); ++i) {
if (strcmp(token, opt[i].name) == 0) {
*dst = opt[i].value;
return 0;
}
}
parser_error(context, "on_proxy_fail <%s> is not parsed", token);
return -1;
}
static int vp_pchar(parser_context *context, void *addr, const char *token)
{
char *p = strdup(token);
if (!p) {
parser_error(context, "strdup failed");
return -1;
}
*(char**)addr = p;
return 0;
}
static int vp_uint16(parser_context *context, void *addr, const char *token)
{
char *end;
unsigned long int uli = strtoul(token, &end, 0);
if (uli > 0xFFFF) {
parser_error(context, "integer out of 16bit range");
return -1;
}
if (*end != '\0') {
parser_error(context, "integer is not parsed");
return -1;
}
*(uint16_t*)addr = (uint16_t)uli;
return 0;
}
static int vp_in_addr(parser_context *context, void *addr, const char *token)
{
struct in_addr ia;
if (inet_aton(token, &ia)) {
memcpy(addr, &ia, sizeof(ia));
}
else {
struct addrinfo *ainfo, hints;
int err;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET; /* IPv4-only */
hints.ai_socktype = SOCK_STREAM; /* I want to have one address once and ONLY once, that's why I specify socktype and protocol */
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_ADDRCONFIG; /* I don't need IPv4 addrs without IPv4 connectivity */
err = getaddrinfo(token, NULL, &hints, &ainfo);
if (err == 0) {
int count, taken;
struct addrinfo *iter;
struct sockaddr_in *resolved_addr;
for (iter = ainfo, count = 0; iter; iter = iter->ai_next, ++count)
;
taken = red_randui32() % count;
for (iter = ainfo; taken > 0; iter = iter->ai_next, --taken)
;
resolved_addr = (struct sockaddr_in*)iter->ai_addr;
assert(resolved_addr->sin_family == iter->ai_family && iter->ai_family == AF_INET);
if (count != 1)
log_error(LOG_WARNING, "%s resolves to %d addresses, using %s",
token, count, inet_ntoa(resolved_addr->sin_addr));
memcpy(addr, &resolved_addr->sin_addr, sizeof(ia));
freeaddrinfo(ainfo);
}
else {
if (err == EAI_SYSTEM)
parser_error(context, "unable to resolve %s, error %d (%s)", token, errno, strerror(errno));
else
parser_error(context, "unable to resolve %s, getaddrinfo error %d (%s)", token, err, gai_strerror(err));
return -1;
}
}
return 0;
}
static int vp_in_addr2(parser_context *context, void *addr, const char *token)
{
char *host = NULL, *mask = NULL;
struct in_addr ia;
int retval = 0;
host = strdup(token);
if (!host) {
parser_error(context, "strdup failed");
return -1;
}
mask = strchr(host, '/');
if (mask) {
*mask = '\0';
mask++;
}
if (inet_aton(host, &ia)) {
memcpy(addr, &ia, sizeof(ia));
}
else {
parser_error(context, "invalid IP address");
retval = -1;
}
if (mask) {
struct in_addr *pinmask = ((struct in_addr*)addr) + 1;
char *end;
unsigned long int uli = strtoul(mask, &end, 0);;
if (*end == '.') {
if (inet_aton(mask, &ia)) {
memcpy(pinmask , &ia, sizeof(ia));
}
else {
parser_error(context, "invalid IP address");
retval = -1;
}
}
else if (0 < uli && uli < 32) {
pinmask->s_addr = htonl((INADDR_BROADCAST << (32 - uli)));
}
else {
parser_error(context, "number of netmask bits out of range");
retval = -1;
}
}
free(host);
return retval;
}
static value_parser value_parser_by_type[] =
{
[pt_bool] = vp_pbool,
[pt_pchar] = vp_pchar,
[pt_uint16] = vp_uint16,
[pt_in_addr] = vp_in_addr,
[pt_in_addr2] = vp_in_addr2,
[pt_disclose_src] = vp_disclose_src,
[pt_on_proxy_fail] = vp_on_proxy_fail,
};
int parser_run(parser_context *context)
{
char *section_token = NULL, *key_token = NULL, *value_token = NULL;
parser_section *section = NULL;
bool in_comment = false;
bool need_more_space = false;
bool need_more_data = true;
while ( !context->error && !feof(context->fd) ) {
assert(context->buffer.filled < context->buffer.size); // ``<`` and not ``<=``
if (need_more_space) {
char *new = realloc(context->buffer.data, context->buffer.size * 2);
if (!new) {
parser_error(context, "realloc failure");
return -1;
}
context->buffer.data = new;
context->buffer.size *= 2;
need_more_space = false;
}
if (need_more_data) { // read one line per call
char *sbegin = context->buffer.data + context->buffer.filled;
int len;
if (fgets(sbegin, context->buffer.size - context->buffer.filled, context->fd) == NULL) {
if (ferror(context->fd)) {
parser_error(context, "file read failure");
return -1;
}
else
continue;
}
len = strlen(sbegin);
context_filled_add(context, +len);
if (len > 0 && sbegin[len - 1] == '\n') {
context->line++;
need_more_data = false;
}
else {
need_more_space = true;
continue;
}
}
if ( in_comment ) {
char *endc = strstr(context->buffer.data, "*/");
if (endc) {
endc += 2;
int comment_len = endc - context->buffer.data;
memmove(context->buffer.data, endc, context->buffer.filled - comment_len);
context_filled_add(context, -comment_len);
in_comment = false;
}
else {
context_filled_set(context, 0);
need_more_data = true;
continue;
}
}
char *token = NULL, *iter = context->buffer.data;
while ( !context->error && !in_comment && (token = gettoken(context, &iter)) ) {
if (strcmp(token, "//") == 0) {
char *endc = strchr(iter, '\n');
iter -= 2;
endc += 1;
// |*data |*iter |*endc -->|<--.filled
int moved_len = context->buffer.filled - (endc - context->buffer.data);
memmove(iter, endc, moved_len);
context_filled_add(context, -(endc - iter));
}
else if (strcmp(token, "/*") == 0) {
int moved_len = iter - context->buffer.data;
memmove(context->buffer.data, iter, context->buffer.filled - moved_len);
context_filled_add(context, -moved_len);
iter = context->buffer.data;
in_comment = true;
}
else if (strcmp(token, "{") == 0) { // } - I love folding
if (section) {
parser_error(context, "section-in-section is invalid");
}
else if (!section_token) {
parser_error(context, "expected token before ``{''"); // } - I love folding
}
else {
for (parser_section *p = context->sections; p; p = p->next) {
if (strcmp(p->name, section_token) == 0) {
section = p;
break;
}
}
if (section) {
if (section->onenter)
if ( section->onenter(section) == -1 )
parser_error(context, "section->onenter failed");
}
else {
parser_error(context, "unknown section <%s>", section_token);
}
FREE(section_token);
}
}
else if (strcmp(token, "}") == 0) { // { - I love folding
if (section) {
if (section->onexit)
if ( section->onexit(section) == -1 )
parser_error(context, "section->onexit failed");
section = NULL;
}
else {
parser_error(context, "can't close non-opened section");
}
}
else if (strcmp(token, "=") == 0) {
if (!section) {
parser_error(context, "assignment used outside of any section");
}
else if (!key_token) {
parser_error(context, "assignment used without key");
}
else {
; // What can I do? :)
}
}
else if (strcmp(token, ";") == 0) {
if (!section) {
parser_error(context, "assignment termination outside of any section");
}
else if (key_token && !value_token) {
parser_error(context, "assignment has only key but no value");
}
else if (key_token && value_token) {
parser_entry *e;
for (e = section->entries; e->key; e++)
if (strcmp(e->key, key_token) == 0)
break;
if (e->key) {
if ( (value_parser_by_type[e->type])(context, e->addr, value_token) == -1 )
parser_error(context, "value can't be parsed");
}
else {
parser_error(context, "assignment with unknown key <%s>", key_token);
}
}
else {
assert(false);
}
FREE(key_token);
FREE(value_token);
}
else {
if (!section && !section_token) {
section_token = token;
token = 0;
}
else if (section && !key_token) {
key_token = token;
token = 0;
}
else if (section && key_token && !value_token) {
value_token = token;
token = 0;
}
else {
parser_error(context, "invalid token order");
}
}
free(token);
}
int moved_len = iter - context->buffer.data;
memmove(context->buffer.data, iter, context->buffer.filled - moved_len);
context_filled_add(context, -moved_len);
if (!token)
need_more_data = true;
}
// file-error goes is thrown here
if (section)
parser_error(context, "unclosed section");
if (section_token) {
parser_error(context, "stale section_token");
free(section_token);
}
if (key_token) {
parser_error(context, "stale key_token");
free(key_token);
}
if (value_token) {
parser_error(context, "stale value_token");
free(value_token);
}
return context->error ? -1 : 0;
}
/* vim:set tabstop=4 softtabstop=4 shiftwidth=4: */
/* vim:set foldmethod=marker foldlevel=32 foldmarker={,}: */