mirror of
https://github.com/darkk/redsocks.git
synced 2025-08-25 11:15:30 +00:00
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.
558 lines
13 KiB
C
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={,}: */
|