Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: a3411f29ad
Fetching contributors…

Cannot retrieve contributors at this time

714 lines (618 sloc) 21.382 kb
/*
parser.c - Parsing routines for tsocks.conf
*/
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <pwd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <config.h>
#include "common.h"
#include "parser.h"
/* Global configuration variables */
#define MAXLINE BUFSIZ /* Max length of conf line */
static struct serverent *currentcontext = NULL;
static int handle_line(struct parsedfile *, char *, int);
static int check_server(struct serverent *);
static int tokenize(char *, int, char *[]);
static int handle_path(struct parsedfile *, int, int, char *[]);
static int handle_endpath(struct parsedfile *, int, int, char *[]);
static int handle_reaches(struct parsedfile *, int, char *);
static int handle_server(struct parsedfile *, int, char *);
static int handle_type(struct parsedfile *config, int, char *);
static int handle_port(struct parsedfile *config, int, char *);
static int handle_local(struct parsedfile *, int, char *);
static int handle_defuser(struct parsedfile *, int, char *);
static int handle_defpass(struct parsedfile *, int, char *);
static int make_netent(char *value, struct netent **ent);
static int handle_fallback(struct parsedfile *, int, char *);
char __attribute__ ((visibility ("hidden")))
*find_config(char *line) {
struct passwd* pw;
errno = 0;
pw = getpwuid(getuid());
if (errno) {
perror("getpwuid");
return NULL;
}
/* check for config in $HOME */
snprintf(line, MAXLINE - 1, "%s/.tsocks.conf", pw->pw_dir);
if (access(line, R_OK)) {
show_msg(MSGDEBUG, "Can't access %s, using " CONF_FILE " instead.\n", line);
strncpy(line, CONF_FILE, MAXLINE - 1);
}
/* Insure null termination */
line[MAXLINE - 1] = (char) 0;
return line;
}
int __attribute__ ((visibility ("hidden")))
read_config (char *filename, struct parsedfile *config) {
FILE *conf;
char line[MAXLINE];
int rc = 0;
int lineno = 1;
struct serverent *server;
/* Clear out the structure */
memset(config, 0x0, sizeof(*config));
/* Initialization */
currentcontext = &(config->defaultserver);
/* If a filename wasn't provided, use the default */
if (filename == NULL) {
filename = find_config(line);
}
show_msg(MSGDEBUG, "using %s as configuration file\n", line);
/* Read the configuration file */
if ((conf = fopen(filename, "r")) == NULL) {
show_msg(MSGERR, "Could not open socks configuration file "
"(%s), assuming all networks local\n", filename);
handle_local(config, 0, "0.0.0.0/0.0.0.0");
rc = 1; /* Severe errors reading configuration */
}
else {
memset(&(config->defaultserver), 0x0, sizeof(config->defaultserver));
while (NULL != fgets(line, MAXLINE, conf)) {
/* This line _SHOULD_ end in \n so we */
/* just chop off the \n and hand it on */
if (strlen(line) > 0)
line[strlen(line) - 1] = '\0';
handle_line(config, line, lineno);
lineno++;
}
fclose(conf);
/* Always add the 127.0.0.1/255.0.0.0 subnet to local */
handle_local(config, 0, "127.0.0.0/255.0.0.0");
/* Check default server */
check_server(&(config->defaultserver));
server = (config->paths);
while (server != NULL) {
check_server(server);
server = server->next;
}
}
return(rc);
}
/* Check server entries (and establish defaults) */
static int check_server(struct serverent *server) {
/* Default to the default SOCKS port */
if (server->port == 0) {
server->port = 1080;
}
/* Default to SOCKS V4 */
if (server->type == 0) {
server->type = 4;
}
return(0);
}
static int handle_line(struct parsedfile *config, char *line, int lineno) {
char *words[10];
static char savedline[MAXLINE];
int nowords = 0, i;
/* Save the input string */
strncpy(savedline, line, MAXLINE - 1);
savedline[MAXLINE - 1] = (char) 0;
/* Tokenize the input string */
nowords = tokenize(line, 10, words);
/* Set the spare slots to an empty string to simplify */
/* processing */
for (i = nowords; i < 10; i++)
words[i] = "";
if (nowords > 0) {
/* Now this can either be a "path" block starter or */
/* ender, otherwise it has to be a pair (<name> = */
/* <value>) */
if (!strcmp(words[0], "path")) {
handle_path(config, lineno, nowords, words);
} else if (!strcmp(words[0], "}")) {
handle_endpath(config, lineno, nowords, words);
} else {
/* Has to be a pair */
if ((nowords != 3) || (strcmp(words[1], "="))) {
show_msg(MSGERR, "Malformed configuration pair "
"on line %d in configuration "
"file, \"%s\"\n", lineno, savedline);
} else if (!strcmp(words[0], "reaches")) {
handle_reaches(config, lineno, words[2]);
} else if (!strcmp(words[0], "server")) {
handle_server(config, lineno, words[2]);
} else if (!strcmp(words[0], "server_port")) {
handle_port(config, lineno, words[2]);
} else if (!strcmp(words[0], "server_type")) {
handle_type(config, lineno, words[2]);
} else if (!strcmp(words[0], "default_user")) {
handle_defuser(config, lineno, words[2]);
} else if (!strcmp(words[0], "default_pass")) {
handle_defpass(config, lineno, words[2]);
} else if (!strcmp(words[0], "local")) {
handle_local(config, lineno, words[2]);
} else if (!strcmp(words[0], "fallback")) {
handle_fallback(config, lineno, words[2]);
} else {
show_msg(MSGERR, "Invalid pair type (%s) specified "
"on line %d in configuration file, "
"\"%s\"\n", words[0], lineno,
savedline);
}
}
}
return(0);
}
/* This routines breaks up input lines into tokens */
/* and places these tokens into the array specified */
/* by tokens */
static int tokenize(char *line, int arrsize, char *tokens[]) {
int tokenno = -1;
int finished = 0;
/* Whitespace is ignored before and after tokens */
while ((tokenno < (arrsize - 1)) &&
(line = line + strspn(line, " \t")) &&
(*line != (char) 0) &&
(!finished)) {
tokenno++;
tokens[tokenno] = line;
line = line + strcspn(line, " \t");
*line = (char) 0;
line++;
/* We ignore everything after a # */
if (*tokens[tokenno] == '#') {
finished = 1;
tokenno--;
}
}
return(tokenno + 1);
}
static int handle_path(struct parsedfile *config, int lineno, int nowords, char *words[]) {
struct serverent *newserver;
if ((nowords != 2) || (strcmp(words[1], "{"))) {
show_msg(MSGERR, "Badly formed path open statement on line %d "
"in configuration file (should look like "
"\"path {\")\n", lineno);
} else if (currentcontext != &(config->defaultserver)) {
/* You cannot nest path statements so check that */
/* the current context is defaultserver */
show_msg(MSGERR, "Path statements cannot be nested on line %d "
"in configuration file\n", lineno);
} else {
/* Open up a new serverent, put it on the list */
/* then set the current context */
if (((int) (newserver = (struct serverent *) malloc(sizeof(struct serverent)))) == -1)
exit(-1);
/* Initialize the structure */
show_msg(MSGDEBUG, "New server structure from line %d in configuration file going "
"to 0x%08x\n", lineno, newserver);
memset(newserver, 0x0, sizeof(*newserver));
newserver->next = config->paths;
newserver->lineno = lineno;
config->paths = newserver;
currentcontext = newserver;
}
return(0);
}
static int handle_endpath(struct parsedfile *config, int lineno, int nowords, char *words[]) {
if (nowords != 1) {
show_msg(MSGERR, "Badly formed path close statement on line "
"%d in configuration file (should look like "
"\"}\")\n", lineno);
} else {
currentcontext = &(config->defaultserver);
}
/* We could perform some checking on the validty of data in */
/* the completed path here, but thats what verifyconf is */
/* designed to do, no point in weighing down libtsocks */
return(0);
}
static int handle_reaches(struct parsedfile *config, int lineno, char *value) {
int rc;
struct netent *ent;
rc = make_netent(value, &ent);
switch(rc) {
case 1:
show_msg(MSGERR, "Local network specification (%s) is not validly "
"constructed in reach statement on line "
"%d in configuration "
"file\n", value, lineno);
return(0);
break;
case 2:
show_msg(MSGERR, "IP in reach statement "
"network specification (%s) is not valid on line "
"%d in configuration file\n", value, lineno);
return(0);
break;
case 3:
show_msg(MSGERR, "SUBNET in reach statement "
"network specification (%s) is not valid on "
"line %d in configuration file\n", value,
lineno);
return(0);
break;
case 4:
show_msg(MSGERR, "IP (%s) & ", inet_ntoa(ent->localip));
show_msg(MSGERR, "SUBNET (%s) != IP on line %d in "
"configuration file, ignored\n",
inet_ntoa(ent->localnet), lineno);
return(0);
break;
case 5:
show_msg(MSGERR, "Start port in reach statement "
"network specification (%s) is not valid on line "
"%d in configuration file\n", value, lineno);
return(0);
break;
case 6:
show_msg(MSGERR, "End port in reach statement "
"network specification (%s) is not valid on line "
"%d in configuration file\n", value, lineno);
return(0);
break;
case 7:
show_msg(MSGERR, "End port in reach statement "
"network specification (%s) is less than the start "
"port on line %d in configuration file\n", value,
lineno);
return(0);
break;
}
/* The entry is valid so add it to linked list */
ent -> next = currentcontext -> reachnets;
currentcontext -> reachnets = ent;
return(0);
}
static int handle_server(struct parsedfile *config, int lineno, char *value) {
char *ip;
ip = strsplit(NULL, &value, " ");
/* We don't verify this ip/hostname at this stage, */
/* its resolved immediately before use in tsocks.c */
if (currentcontext->address == NULL)
currentcontext->address = strdup(ip);
else {
if (currentcontext == &(config->defaultserver))
show_msg(MSGERR, "Only one default SOCKS server "
"may be specified at line %d in "
"configuration file\n", lineno);
else
show_msg(MSGERR, "Only one SOCKS server may be specified "
"per path on line %d in configuration "
"file. (Path begins on line %d)\n",
lineno, currentcontext->lineno);
}
return(0);
}
static int handle_port(struct parsedfile *config, int lineno, char *value) {
if (currentcontext->port != 0) {
if (currentcontext == &(config->defaultserver))
show_msg(MSGERR, "Server port may only be specified "
"once for default server, at line %d "
"in configuration file\n", lineno);
else
show_msg(MSGERR, "Server port may only be specified "
"once per path on line %d in configuration "
"file. (Path begins on line %d)\n",
lineno, currentcontext->lineno);
} else {
errno = 0;
currentcontext->port = (unsigned short int)
(strtol(value, (char **)NULL, 10));
if ((errno != 0) || (currentcontext->port == 0)) {
show_msg(MSGERR, "Invalid server port number "
"specified in configuration file "
"(%s) on line %d\n", value, lineno);
currentcontext->port = 0;
}
}
return(0);
}
static int handle_defuser(struct parsedfile *config, int lineno, char *value) {
if (currentcontext->defuser != NULL) {
if (currentcontext == &(config->defaultserver))
show_msg(MSGERR, "Default username may only be specified "
"once for default server, at line %d "
"in configuration file\n", lineno);
else
show_msg(MSGERR, "Default username may only be specified "
"once per path on line %d in configuration "
"file. (Path begins on line %d)\n",
lineno, currentcontext->lineno);
} else {
currentcontext->defuser = strdup(value);
}
return(0);
}
static int handle_defpass(struct parsedfile *config, int lineno, char *value) {
if (currentcontext->defpass != NULL) {
if (currentcontext == &(config->defaultserver))
show_msg(MSGERR, "Default password may only be specified "
"once for default server, at line %d "
"in configuration file\n", lineno);
else
show_msg(MSGERR, "Default password may only be specified "
"once per path on line %d in configuration "
"file. (Path begins on line %d)\n",
lineno, currentcontext->lineno);
} else {
currentcontext->defpass = strdup(value);
}
return(0);
}
static int handle_type(struct parsedfile *config, int lineno, char *value) {
if (currentcontext->type != 0) {
if (currentcontext == &(config->defaultserver))
show_msg(MSGERR, "Server type may only be specified "
"once for default server, at line %d "
"in configuration file\n", lineno);
else
show_msg(MSGERR, "Server type may only be specified "
"once per path on line %d in configuration "
"file. (Path begins on line %d)\n",
lineno, currentcontext->lineno);
} else {
errno = 0;
currentcontext->type = (int) strtol(value, (char **)NULL, 10);
if ((errno != 0) || (currentcontext->type == 0) ||
((currentcontext->type != 4) && (currentcontext->type != 5))) {
show_msg(MSGERR, "Invalid server type (%s) "
"specified in configuration file "
"on line %d, only 4 or 5 may be "
"specified\n", value, lineno);
currentcontext->type = 0;
}
}
return(0);
}
static int handle_local(struct parsedfile *config, int lineno, char *value) {
int rc;
struct netent *ent;
if (currentcontext != &(config->defaultserver)) {
show_msg(MSGERR, "Local networks cannot be specified in path "
"block at like %d in configuration file. "
"(Path block started at line %d)\n",
lineno, currentcontext->lineno);
return(0);
}
rc = make_netent(value, &ent);
switch(rc) {
case 1:
show_msg(MSGERR, "Local network specification (%s) is not validly "
"constructed on line %d in configuration "
"file\n", value, lineno);
return(0);
break;
case 2:
show_msg(MSGERR, "IP for local "
"network specification (%s) is not valid on line "
"%d in configuration file\n", value, lineno);
return(0);
break;
case 3:
show_msg(MSGERR, "SUBNET for "
"local network specification (%s) is not valid on "
"line %d in configuration file\n", value,
lineno);
return(0);
break;
case 4:
show_msg(MSGERR, "IP (%s) & ", inet_ntoa(ent->localip));
show_msg(MSGERR, "SUBNET (%s) != IP on line %d in "
"configuration file, ignored\n",
inet_ntoa(ent->localnet), lineno);
return(0);
case 5:
case 6:
case 7:
show_msg(MSGERR, "Port specification is invalid and "
"not allowed in local network specification "
"(%s) on line %d in configuration file\n",
value, lineno);
return(0);
break;
}
if (ent->startport || ent->endport) {
show_msg(MSGERR, "Port specification is "
"not allowed in local network specification "
"(%s) on line %d in configuration file\n",
value, lineno);
return(0);
}
/* The entry is valid so add it to linked list */
ent -> next = config->localnets;
(config->localnets) = ent;
return(0);
}
static int handle_fallback(struct parsedfile *config, int lineno, char *value) {
char *v = strsplit(NULL, &value, " ");
if (config->fallback !=0) {
show_msg(MSGERR, "Fallback may only be specified "
"once in configuration file.\n",
lineno, currentcontext->lineno);
} else {
if(!strcmp(v, "yes")) config->fallback = 1;
if(!strcmp(v, "no")) config->fallback = 0;
}
return(0);
}
/* Construct a netent given a string like */
/* "198.126.0.1[:portno[-portno]]/255.255.255.0" */
int make_netent(char *value, struct netent **ent) {
char *ip;
char *subnet;
char *startport = NULL;
char *endport = NULL;
char *badchar;
char separator;
static char buf[200];
char *split;
/* Get a copy of the string so we can modify it */
strncpy(buf, value, sizeof(buf) - 1);
buf[sizeof(buf) - 1] = (char) 0;
split = buf;
/* Now rip it up */
ip = strsplit(&separator, &split, "/:");
if (separator == ':') {
/* We have a start port */
startport = strsplit(&separator, &split, "-/");
if (separator == '-')
/* We have an end port */
endport = strsplit(&separator, &split, "/");
}
subnet = strsplit(NULL, &split, " \n");
if ((ip == NULL) || (subnet == NULL)) {
/* Network specification not validly constructed */
return(1);
}
/* Allocate the new entry */
if ((*ent = (struct netent *) malloc(sizeof(struct netent)))
== NULL) {
/* If we couldn't malloc some storage, leave */
exit(1);
}
show_msg(MSGDEBUG, "New network entry for %s going to 0x%08x\n", ip, *ent);
if (!startport)
(*ent)->startport = 0;
if (!endport)
(*ent)->endport = 0;
#ifdef HAVE_INET_ADDR
if (((*ent)->localip.s_addr = inet_addr(ip)) == -1) {
#elif defined(HAVE_INET_ATON)
if (!(inet_aton(ip, &((*ent)->localip)))) {
#endif
/* Badly constructed IP */
free(*ent);
return(2);
}
#ifdef HAVE_INET_ADDR
else if (((*ent)->localnet.s_addr = inet_addr(subnet)) == -1) {
#elif defined(HAVE_INET_ATON)
else if (!(inet_aton(subnet, &((*ent)->localnet)))) {
#endif
/* Badly constructed subnet */
free(*ent);
return(3);
} else if (((*ent)->localip.s_addr &
(*ent)->localnet.s_addr) !=
(*ent)->localip.s_addr) {
/* Subnet and Ip != Ip */
free(*ent);
return(4);
} else if (startport &&
(!((*ent)->startport = strtol(startport, &badchar, 10)) ||
(*badchar != 0) || ((*ent)->startport > 65535))) {
/* Bad start port */
free(*ent);
return(5);
} else if (endport &&
(!((*ent)->endport = strtol(endport, &badchar, 10)) ||
(*badchar != 0) || ((*ent)->endport > 65535))) {
/* Bad end port */
free(*ent);
return(6);
} else if (((*ent)->startport > (*ent)->endport) && !(startport && !endport)) {
/* End port is less than start port */
free(*ent);
return(7);
}
if (startport && !endport)
(*ent)->endport = (*ent)->startport;
return(0);
}
int __attribute__ ((visibility ("hidden")))
is_local(struct parsedfile *config, struct in_addr *testip) {
struct netent *ent;
for (ent = (config->localnets); ent != NULL; ent = ent -> next) {
if ((testip->s_addr & ent->localnet.s_addr) ==
(ent->localip.s_addr & ent->localnet.s_addr)) {
return(0);
}
}
return(1);
}
/* Find the appropriate server to reach an ip */
int __attribute__ ((visibility ("hidden")))
pick_server(struct parsedfile *config, struct serverent **ent,
struct in_addr *ip, unsigned int port) {
struct netent *net;
char ipbuf[64];
show_msg(MSGDEBUG, "Picking appropriate server for %s\n", inet_ntoa(*ip));
*ent = (config->paths);
while (*ent != NULL) {
/* Go through all the servers looking for one */
/* with a path to this network */
show_msg(MSGDEBUG, "Checking SOCKS server %s\n",
((*ent)->address ? (*ent)->address : "(No Address)"));
net = (*ent)->reachnets;
while (net != NULL) {
strcpy(ipbuf, inet_ntoa(net->localip));
show_msg(MSGDEBUG, "Server can reach %s/%s\n",
ipbuf, inet_ntoa(net->localnet));
if (((ip->s_addr & net->localnet.s_addr) ==
(net->localip.s_addr & net->localnet.s_addr)) &&
(!net->startport ||
((net->startport <= port) && (net->endport >= port))))
{
show_msg(MSGDEBUG, "This server can reach target\n");
/* Found the net, return */
return(0);
}
net = net->next;
}
(*ent) = (*ent)->next;
}
*ent = &(config->defaultserver);
return(0);
}
/* This function is very much like strsep, it looks in a string for */
/* a character from a list of characters, when it finds one it */
/* replaces it with a \0 and returns the start of the string */
/* (basically spitting out tokens with arbitrary separators). If no */
/* match is found the remainder of the string is returned and */
/* the start pointer is set to be NULL. The difference between */
/* standard strsep and this function is that this one will */
/* set *separator to the character separator found if it isn't null */
char __attribute__ ((visibility ("hidden")))
*strsplit(char *separator, char **text, const char *search) {
int len;
char *ret;
ret = *text;
if (*text == NULL) {
if (separator)
*separator = '\0';
return(NULL);
} else {
len = strcspn(*text, search);
if (len == strlen(*text)) {
if (separator)
*separator = '\0';
*text = NULL;
} else {
*text = *text + len;
if (separator)
*separator = **text;
**text = '\0';
*text = *text + 1;
}
}
return(ret);
}
Jump to Line
Something went wrong with that request. Please try again.