Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ AC_FUNC_LSTAT_FOLLOWS_SLASHED_SYMLINK
AC_FUNC_MALLOC
AC_FUNC_REALLOC

AC_CHECK_FUNCS([inet_ntoa strdup])
AC_CHECK_FUNCS([inet_ntoa strdup fdopen])
AC_CHECK_FUNCS([strlcpy strlcat setgroups])

dnl Enable extra warnings
Expand Down
1 change: 1 addition & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ tinyproxy_SOURCES = \
utils.c utils.h \
vector.c vector.h \
upstream.c upstream.h \
upstream-command.c upstream-command.h \
basicauth.c basicauth.h \
base64.c base64.h \
connect-ports.c connect-ports.h
Expand Down
35 changes: 32 additions & 3 deletions src/conf.c
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ static HANDLE_FUNC (handle_xtinyproxy);
#ifdef UPSTREAM_SUPPORT
static HANDLE_FUNC (handle_upstream);
static HANDLE_FUNC (handle_upstream_no);
static HANDLE_FUNC (handle_upstream_command);
#endif

static void config_free_regex (void);
Expand Down Expand Up @@ -264,6 +265,12 @@ struct {
":" INT "(" WS STR ")?"
END, handle_upstream, NULL
},
{
BEGIN "(upstream)" WS "(command)" WS
STR /* path to script */
"(" WS STR ")?" /* host filter */
END, handle_upstream_command, NULL
},
#endif
/* loglevel */
STDCONF ("loglevel", "(critical|error|warning|notice|connect|info)",
Expand Down Expand Up @@ -1135,7 +1142,7 @@ static HANDLE_FUNC (handle_upstream)
if (match[mi].rm_so != -1)
domain = get_string_arg (line, &match[mi]);

upstream_add (ip, port, domain, user, pass, pt, &conf->upstream_list);
upstream_add (ip, port, domain, user, pass, NULL, pt, &conf->upstream_list);

safefree (user);
safefree (pass);
Expand All @@ -1153,9 +1160,31 @@ static HANDLE_FUNC (handle_upstream_no)
if (!domain)
return -1;

upstream_add (NULL, 0, domain, 0, 0, PT_NONE, &conf->upstream_list);
upstream_add (NULL, 0, domain, 0, 0, NULL, PT_NONE, &conf->upstream_list);
safefree (domain);

return 0;
}
#endif

static HANDLE_FUNC (handle_upstream_command)
{
int mi = 3;
char *command = 0, *domain = 0;

command = get_string_arg (line, &match[mi]);
mi++;

if (match[mi].rm_so != -1)
domain = get_string_arg (line, &match[mi]);
mi++;

upstream_add (NULL, 0, domain, 0, 0, command, PT_NONE, &conf->upstream_list);

safefree (command);
safefree (domain);

return 0;
}


#endif /* UPSTREAM_SUPPORT */
140 changes: 140 additions & 0 deletions src/upstream-command.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/* TODO(tkluck): this is needed for fdopen. Does it have portability
* downsides? */
#define _POSIX_C_SOURCE 1
#include <stdio.h>

#include "upstream-command.h"
#include "upstream.h"

#define MAX_RESULT_MESSAGE_SIZE 1024

/*
* ensure that st->in is writable (according to select() ). Use select()
* to wait at most UPSTREAM_COMMAND_WRITABLE_TIMOUT_MS milliseconds,
* and respawn otherwise.
* Returns a negative error code if we do not succeed in respawning.
*/
static int ensure_upstream_command_writable(const char *cmd, struct upstream_command_state *st)
{
int err;
int parent2child[2];
int child2parent[2];

assert(st != NULL);

if (0 == st->pid) {
err = pipe(parent2child);
if (0 != err)
return -1;
err = pipe(child2parent);
if (0 != err)
return -1;

st->pid = fork();
if (0 > st->pid)
return -1;
if (0 == st->pid) {
/* I'm the child; connect stdin/stdout to the pipes */
if (0 > close(parent2child[1]))
exit(1);
if (0 > close(child2parent[0]))
exit(1);
if (0 > dup2(parent2child[0], 0))
exit(1);
if (0 > dup2(child2parent[1], 1))
exit(1);

/* TODO(tkluck): do we need to worry about installed
* signal handlers (e.g. for SIGCHLD)?
*/

err = execl("/bin/sh", "sh", "-c", cmd, (char *) 0);

/* unreachable unless something is wrong with the command */
exit(1);
} else {
/* I'm the parent */
sleep(1);
close(parent2child[0]);
close(child2parent[1]);
st->in = fdopen(parent2child[1], "w");
st->out = fdopen(child2parent[0], "r");
}
}

/* TODO(tkluck): use select(2) to ensure that writing to
* the process stdin doesn't block (for sufficiently small
* message). Otherwise, fork a new copy and kill the old one. */

return 0;
}

/*
* Communicate with the configured command to obtain a proxy
* specification, and _replace_ the fields in *up with the appropriate
* spec.
*
* In case of success, returns one of the following positive constants:
* UPSTREAM_DIRECT - no proxy
* UPSTREAM_FALLTHROUGH - use subsequent rules
* UPSTREAM_PROXY - use upstream as configured in *up
*
* Returns a negative error code if we do not succeed in communicating
* with the subprocess.
*/
int upstream_get_from_command(const char *host, struct upstream *up)
{
int err;
char msg[MAX_RESULT_MESSAGE_SIZE];
char *msgres;

assert(up != NULL);
assert(up->command != NULL);
if (NULL != strchr(host, '\n'))
return -1;

err = ensure_upstream_command_writable(up->command, &up->cmdstate);
if (err < 0)
return -2;

/* TODO(tkluck): replace by version with timeout. */
err = fprintf(up->cmdstate.in, "HOST %s\n", host);
if (err < 0)
return -3;
err = fflush(up->cmdstate.in);
if (err != 0)
return -4;

/* TODO(tkluck): replace by version with timeout */
msgres = fgets(msg, MAX_RESULT_MESSAGE_SIZE, up->cmdstate.out);
if (NULL == msgres)
return -5;

if (strncmp(msg, "DIRECT", 6)) {
return UPSTREAM_DIRECT;
} else if
(strncmp(msg, "FALLTHROUGH", 11)) {
return UPSTREAM_FALLTHROUGH;
} else if
(strncmp(msg, "HTTP ", 5)) {
/* TODO(tkluck): parse and fill the struct here.
* use same/similar format as the configuration file */
return UPSTREAM_PROXY;
} else if
(strncmp(msg, "SOCKS4 ", 7)) {
/* TODO(tkluck): parse and fill the struct here.
* use same/similar format as the configuration file */
return UPSTREAM_PROXY;
} else if
(strncmp(msg, "SOCKS5 ", 7)) {
/* TODO(tkluck): parse and fill the struct here.
* use same/similar format as the configuration file */
return UPSTREAM_PROXY;
} else {
goto parsefail;
}

parsefail:
return -6;

}
19 changes: 19 additions & 0 deletions src/upstream-command.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#ifndef _TINYPROXY_UPSTREAM_COMMAND_H_
#define _TINYPROXY_UPSTREAM_COMMAND_H_

#include <stdio.h>

typedef enum upstream_command_result {
UPSTREAM_DIRECT = 1,
UPSTREAM_FALLTHROUGH,
UPSTREAM_PROXY,
} upstream_command_result;

struct upstream_command_state {
int pid;
FILE *in;
FILE *out;
};


#endif /* _TINYPROXY_UPSTREAM_COMMAND_H_ */
61 changes: 53 additions & 8 deletions src/upstream.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ proxy_type_name(proxy_type type)
* Construct an upstream struct from input data.
*/
static struct upstream *upstream_build (const char *host, int port, const char *domain,
const char *user, const char *pass,
const char *user, const char *pass, const char *command,
proxy_type type)
{
char *ptr;
Expand All @@ -63,6 +63,9 @@ static struct upstream *upstream_build (const char *host, int port, const char *
up->type = type;
up->host = up->domain = up->ua.user = up->pass = NULL;
up->ip = up->mask = 0;
up->command = NULL;
up->cmdstate.in = up->cmdstate.out = NULL;
up->cmdstate.pid = 0;
if (user) {
if (type == PT_HTTP) {
char b[BASE64ENC_BYTES((256+2)-1) + 1];
Expand All @@ -80,6 +83,21 @@ static struct upstream *upstream_build (const char *host, int port, const char *
}
}

if (command) {
up->command = safestrdup(command);
if (domain && domain[0] != '\0')
up->domain = safestrdup (domain);

if (domain && domain[0] != '\0') {
log_message (LOG_INFO, "Added upstream command %s for %s",
up->command, up->domain);
} else {
log_message (LOG_INFO, "Added upstream command %s",
up->command);
}
return up;
}

if (domain == NULL) {
if (!host || host[0] == '\0' || port < 1) {
log_message (LOG_WARNING,
Expand Down Expand Up @@ -154,12 +172,12 @@ static struct upstream *upstream_build (const char *host, int port, const char *
* Add an entry to the upstream list
*/
void upstream_add (const char *host, int port, const char *domain,
const char *user, const char *pass,
const char *user, const char *pass, const char *command,
proxy_type type, struct upstream **upstream_list)
{
struct upstream *up;

up = upstream_build (host, port, domain, user, pass, type);
up = upstream_build (host, port, domain, user, pass, command, type);
if (up == NULL) {
return;
}
Expand Down Expand Up @@ -203,32 +221,58 @@ void upstream_add (const char *host, int port, const char *domain,
struct upstream *upstream_get (char *host, struct upstream *up)
{
in_addr_t my_ip = INADDR_NONE;
int maskfail;
int res;

while (up) {
maskfail = (up->domain || up->ip) ? 1 : 0;
if (up->domain) {
if (strcasecmp (host, up->domain) == 0)
break; /* exact match */
maskfail = 0; /* exact match */

if (up->domain[0] == '.') {
char *dot = strchr (host, '.');

if (!dot && !up->domain[1])
break; /* local host matches "." */
maskfail = 0; /* local host matches "." */

while (dot && strcasecmp (dot, up->domain))
dot = strchr (dot + 1, '.');

if (dot)
break; /* subdomain match */
maskfail = 0; /* subdomain match */
}
} else if (up->ip) {
if (my_ip == INADDR_NONE)
my_ip = ntohl (inet_addr (host));

if ((my_ip & up->mask) == up->ip)
maskfail = 0;
}

if (!maskfail) {
if (!up->command)
break;

log_message (LOG_INFO,
"Running command %s for host %s",
up->command, host);
res = upstream_get_from_command(host, up);
if (UPSTREAM_DIRECT == res || UPSTREAM_PROXY == res) {
break;
} else {
break; /* No domain or IP, default upstream */
} else if (UPSTREAM_FALLTHROUGH == res) {
/* continue */
} else {
/* command failure
* TODO(tkluck): we should propagate this failure
* and reject the request rather than forwarding
* the request to a 'random' location. E.g. if
* the user configures a proxy for privacy reasons,
* command failure should not compromise their
* privacy.
*/
return NULL;
}
}

up = up->next;
Expand All @@ -251,6 +295,7 @@ void free_upstream_list (struct upstream *up)
while (up) {
struct upstream *tmp = up;
up = up->next;
safefree (tmp->command);
safefree (tmp->domain);
safefree (tmp->host);
safefree (tmp);
Expand Down
5 changes: 5 additions & 0 deletions src/upstream.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#define _TINYPROXY_UPSTREAM_H_

#include "common.h"
#include "upstream-command.h"

/*
* Even if upstream support is not compiled into tinyproxy, this
Expand All @@ -50,15 +51,19 @@ struct upstream {
int port;
in_addr_t ip, mask;
proxy_type type;
char *command;
struct upstream_command_state cmdstate;
};

#ifdef UPSTREAM_SUPPORT
const char *proxy_type_name(proxy_type type);
extern void upstream_add (const char *host, int port, const char *domain,
const char *user, const char *pass,
const char *command,
proxy_type type, struct upstream **upstream_list);
extern struct upstream *upstream_get (char *host, struct upstream *up);
extern void free_upstream_list (struct upstream *up);
int upstream_get_from_command(const char *host, struct upstream *up);
#endif /* UPSTREAM_SUPPORT */

#endif /* _TINYPROXY_UPSTREAM_H_ */