0
0
mirror of https://github.com/darkk/redsocks.git synced 2025-08-25 11:15:30 +00:00
redsocks/parser.c
Przemyslaw Pawelczyk 39b2639bdb Fix logic error bugs.
scan-build result for socks5.c:256:

    Function call argument is an uninitialized value

nextstate can be undefined reply's addrtype field has unexpected value.
Fix by adding else case before to report error, drop client and return
from socks5_read_reply() function.

scan-build result for main.c:144, parser.c:193:

    Result of operation is garbage or undefined

If there is early error in main() in second FOREACH block, then
terminators array is not itialized before access in shutdown path.
Fix by moving memset() to precede this block.

gettoken() does not initialize copytype enum value, so can have a
garbage value at some point. Add else case to the chain of **iter tests
to report error and return from the function.
2011-01-27 23:05:45 +01:00

558 lines
13 KiB
C

/* redsocks - transparent TCP-to-proxy redirector
* Copyright (C) 2007-2008 Leonid Evdokimov <leon@darkk.net.ru>
*
* 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 3 of the License, 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <assert.h>
#include <stdbool.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "utils.h"
#include "parser.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;
parser_errhandler errhandler;
struct {
size_t size;
size_t filled;
char *data;
} buffer;
};
void parser_error(parser_context *context, const char *msg)
{
context->error = 1;
if (context->errhandler)
context->errhandler(msg, context->line);
else
fprintf(stderr, "file parsing error at line %u: %s\n", context->line, msg);
}
parser_context* parser_start(FILE *fd, parser_errhandler errhandler)
{
parser_context *ret = calloc(1, sizeof(parser_context));
if (!ret)
return NULL;
ret->fd = fd;
ret->errhandler = errhandler;
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_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));
return 0;
}
else {
parser_error(context, "invalid IP address");
return -1;
}
}
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,
};
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");
}
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) {
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");
}
}
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={,}: */