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_REALLOC
|
||||
|
||||
AC_CHECK_FUNCS([inet_ntoa strdup])
|
||||
AC_CHECK_FUNCS([inet_ntoa strdup fdopen])
|
||||
AC_CHECK_FUNCS([strlcpy strlcat setgroups])
|
||||
|
||||
dnl Enable extra warnings
|
||||
|
@ -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
|
||||
|
35
src/conf.c
35
src/conf.c
@ -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
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.
|
||||
*/
|
||||
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);
|
||||
|
@ -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_ */
|
||||
|
Loading…
Reference in New Issue
Block a user