WIP: add support for upstream command "/path/to/command"
This commit is contained in:
parent
b131f45cbb
commit
c27cf3c79c
@ -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
|
||||||
|
@ -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
|
||||||
|
35
src/conf.c
35
src/conf.c
@ -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
140
src/upstream-command.c
Normal 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
19
src/upstream-command.h
Normal 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_ */
|
@ -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);
|
||||||
|
@ -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_ */
|
||||||
|
Loading…
Reference in New Issue
Block a user