WIP: add support for upstream command "/path/to/command"

This commit is contained in:
Timo Kluck 2019-03-01 17:33:29 +01:00
parent b131f45cbb
commit c27cf3c79c
7 changed files with 251 additions and 12 deletions

View File

@ -134,7 +134,7 @@ AC_FUNC_LSTAT_FOLLOWS_SLASHED_SYMLINK
AC_FUNC_MALLOC AC_FUNC_MALLOC
AC_FUNC_REALLOC AC_FUNC_REALLOC
AC_CHECK_FUNCS([inet_ntoa strdup]) AC_CHECK_FUNCS([inet_ntoa strdup fdopen])
AC_CHECK_FUNCS([strlcpy strlcat setgroups]) AC_CHECK_FUNCS([strlcpy strlcat setgroups])
dnl Enable extra warnings dnl Enable extra warnings

View File

@ -46,6 +46,7 @@ tinyproxy_SOURCES = \
utils.c utils.h \ utils.c utils.h \
vector.c vector.h \ vector.c vector.h \
upstream.c upstream.h \ upstream.c upstream.h \
upstream-command.c upstream-command.h \
basicauth.c basicauth.h \ basicauth.c basicauth.h \
base64.c base64.h \ base64.c base64.h \
connect-ports.c connect-ports.h connect-ports.c connect-ports.h

View File

@ -165,6 +165,7 @@ static HANDLE_FUNC (handle_xtinyproxy);
#ifdef UPSTREAM_SUPPORT #ifdef UPSTREAM_SUPPORT
static HANDLE_FUNC (handle_upstream); static HANDLE_FUNC (handle_upstream);
static HANDLE_FUNC (handle_upstream_no); static HANDLE_FUNC (handle_upstream_no);
static HANDLE_FUNC (handle_upstream_command);
#endif #endif
static void config_free_regex (void); static void config_free_regex (void);
@ -264,6 +265,12 @@ struct {
":" INT "(" WS STR ")?" ":" INT "(" WS STR ")?"
END, handle_upstream, NULL END, handle_upstream, NULL
}, },
{
BEGIN "(upstream)" WS "(command)" WS
STR /* path to script */
"(" WS STR ")?" /* host filter */
END, handle_upstream_command, NULL
},
#endif #endif
/* loglevel */ /* loglevel */
STDCONF ("loglevel", "(critical|error|warning|notice|connect|info)", STDCONF ("loglevel", "(critical|error|warning|notice|connect|info)",
@ -1135,7 +1142,7 @@ static HANDLE_FUNC (handle_upstream)
if (match[mi].rm_so != -1) if (match[mi].rm_so != -1)
domain = get_string_arg (line, &match[mi]); 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 (user);
safefree (pass); safefree (pass);
@ -1153,9 +1160,31 @@ static HANDLE_FUNC (handle_upstream_no)
if (!domain) if (!domain)
return -1; 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); safefree (domain);
return 0; 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
src/upstream-command.c Normal file
View File

@ -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 > std->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
src/upstream-command.h Normal file
View File

@ -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_ */

View File

@ -47,7 +47,7 @@ proxy_type_name(proxy_type type)
* Construct an upstream struct from input data. * Construct an upstream struct from input data.
*/ */
static struct upstream *upstream_build (const char *host, int port, const char *domain, 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) proxy_type type)
{ {
char *ptr; char *ptr;
@ -63,6 +63,9 @@ static struct upstream *upstream_build (const char *host, int port, const char *
up->type = type; up->type = type;
up->host = up->domain = up->ua.user = up->pass = NULL; up->host = up->domain = up->ua.user = up->pass = NULL;
up->ip = up->mask = 0; up->ip = up->mask = 0;
up->command = NULL;
up->cmdstate.in = up->cmdstate.out = NULL;
up->cmdstate.pid = 0;
if (user) { if (user) {
if (type == PT_HTTP) { if (type == PT_HTTP) {
char b[BASE64ENC_BYTES((256+2)-1) + 1]; char b[BASE64ENC_BYTES((256+2)-1) + 1];
@ -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 (domain == NULL) {
if (!host || host[0] == '\0' || port < 1) { if (!host || host[0] == '\0' || port < 1) {
log_message (LOG_WARNING, log_message (LOG_WARNING,
@ -154,12 +172,12 @@ fail:
* Add an entry to the upstream list * Add an entry to the upstream list
*/ */
void upstream_add (const char *host, int port, const char *domain, 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) proxy_type type, struct upstream **upstream_list)
{ {
struct upstream *up; 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) { if (up == NULL) {
return; return;
} }
@ -203,32 +221,58 @@ upstream_cleanup:
struct upstream *upstream_get (char *host, struct upstream *up) struct upstream *upstream_get (char *host, struct upstream *up)
{ {
in_addr_t my_ip = INADDR_NONE; in_addr_t my_ip = INADDR_NONE;
int maskfail;
int res;
while (up) { while (up) {
maskfail = (up->domain || up->ip) ? 1 : 0;
if (up->domain) { if (up->domain) {
if (strcasecmp (host, up->domain) == 0) if (strcasecmp (host, up->domain) == 0)
break; /* exact match */ maskfail = 0; /* exact match */
if (up->domain[0] == '.') { if (up->domain[0] == '.') {
char *dot = strchr (host, '.'); char *dot = strchr (host, '.');
if (!dot && !up->domain[1]) if (!dot && !up->domain[1])
break; /* local host matches "." */ maskfail = 0; /* local host matches "." */
while (dot && strcasecmp (dot, up->domain)) while (dot && strcasecmp (dot, up->domain))
dot = strchr (dot + 1, '.'); dot = strchr (dot + 1, '.');
if (dot) if (dot)
break; /* subdomain match */ maskfail = 0; /* subdomain match */
} }
} else if (up->ip) { } else if (up->ip) {
if (my_ip == INADDR_NONE) if (my_ip == INADDR_NONE)
my_ip = ntohl (inet_addr (host)); my_ip = ntohl (inet_addr (host));
if ((my_ip & up->mask) == up->ip) 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; break;
} else { } else if (UPSTREAM_FALLTHROUGH == res) {
break; /* No domain or IP, default upstream */ /* 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; up = up->next;
@ -251,6 +295,7 @@ void free_upstream_list (struct upstream *up)
while (up) { while (up) {
struct upstream *tmp = up; struct upstream *tmp = up;
up = up->next; up = up->next;
safefree (tmp->command);
safefree (tmp->domain); safefree (tmp->domain);
safefree (tmp->host); safefree (tmp->host);
safefree (tmp); safefree (tmp);

View File

@ -26,6 +26,7 @@
#define _TINYPROXY_UPSTREAM_H_ #define _TINYPROXY_UPSTREAM_H_
#include "common.h" #include "common.h"
#include "upstream-command.h"
/* /*
* Even if upstream support is not compiled into tinyproxy, this * Even if upstream support is not compiled into tinyproxy, this
@ -50,15 +51,19 @@ struct upstream {
int port; int port;
in_addr_t ip, mask; in_addr_t ip, mask;
proxy_type type; proxy_type type;
char *command;
struct upstream_command_state cmdstate;
}; };
#ifdef UPSTREAM_SUPPORT #ifdef UPSTREAM_SUPPORT
const char *proxy_type_name(proxy_type type); const char *proxy_type_name(proxy_type type);
extern void upstream_add (const char *host, int port, const char *domain, extern 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); proxy_type type, struct upstream **upstream_list);
extern struct upstream *upstream_get (char *host, struct upstream *up); extern struct upstream *upstream_get (char *host, struct upstream *up);
extern void free_upstream_list (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 /* UPSTREAM_SUPPORT */
#endif /* _TINYPROXY_UPSTREAM_H_ */ #endif /* _TINYPROXY_UPSTREAM_H_ */