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_REALLOC
AC_CHECK_FUNCS([inet_ntoa strdup])
AC_CHECK_FUNCS([inet_ntoa strdup fdopen])
AC_CHECK_FUNCS([strlcpy strlcat setgroups])
dnl Enable extra warnings

View File

@ -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

View File

@ -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);
@ -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)",
@ -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);
@ -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
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.
*/
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;
@ -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];
@ -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,
@ -154,12 +172,12 @@ fail:
* 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;
}
@ -203,32 +221,58 @@ upstream_cleanup:
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 if (UPSTREAM_FALLTHROUGH == res) {
/* continue */
} else {
break; /* No domain or IP, default upstream */
/* 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;
@ -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);

View File

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