diff options
| author | Laurent Bercot <ska-skaware@skarnet.org> | 2025-06-25 19:49:01 +0000 |
|---|---|---|
| committer | Laurent Bercot <ska@appnovation.com> | 2025-06-25 19:49:01 +0000 |
| commit | 9acee22dd82472bf408204a066476334c3568e87 (patch) | |
| tree | 2ba82c4cba39e633dcbf422f67cc3fe27f87a8df | |
| parent | 592c13a20f5ece4a596635c75bf096ccd3dfa6b8 (diff) | |
| download | tipidee-9acee22dd82472bf408204a066476334c3568e87.tar.gz | |
Add rproxy
Signed-off-by: Laurent Bercot <ska@appnovation.com>
| -rw-r--r-- | doc/tipidee.conf.html | 37 | ||||
| -rw-r--r-- | package/deps.mak | 3 | ||||
| -rw-r--r-- | src/config/lexparse.c | 65 | ||||
| -rw-r--r-- | src/include/tipidee/conf.h | 6 | ||||
| -rw-r--r-- | src/libtipidee/tipidee_conf_get_redirection.c | 5 | ||||
| -rw-r--r-- | src/tipideed/deps-exe/tipideed | 1 | ||||
| -rw-r--r-- | src/tipideed/redirection.c | 7 | ||||
| -rw-r--r-- | src/tipideed/rproxy.c | 97 | ||||
| -rw-r--r-- | src/tipideed/tipideed-internal.h | 5 | ||||
| -rw-r--r-- | src/tipideed/tipideed.c | 5 |
10 files changed, 218 insertions, 13 deletions
diff --git a/doc/tipidee.conf.html b/doc/tipidee.conf.html index a9b5063..f7753da 100644 --- a/doc/tipidee.conf.html +++ b/doc/tipidee.conf.html @@ -847,6 +847,41 @@ requests received on port 443). But if you declare a redirection under the <tt>example.com</tt> domain, it will apply to requests received on <em>any</em> port. </li> </ul> +<div id="rproxy"> +<h4> <tt>rproxy</tt> </h4> +</div> + +<p> + <code> rproxy <em>resource</em> unix <em>socketpath</em> </code> <br> + <code> rproxy <em>resource</em> tcp <em>ip</em> <em>port</em> </code> +</p> + +<ul> + <li> The <em>rproxy</em> directive tells tipidee that when a client hits a +given URL, the HTTP exchange should be passed as is to another server. For this +exchange, tipideed will act as a proxy, transmitting client data to the real +server and vice-versa. It is handled by the same mechanism as redirection, but +unlike a redirection, it will be transparent to the client. </li> + <li> <em>resource</em> is the URI to redirect, relative to the current domain. +For instance, if the current domain is <tt>example.com</tt> and <em>resource</em> +is <tt>foobar.html</tt>, then a request for <tt>http://example.com/foobar.html</tt> +will be reverse-proxied to <em>socketpath</em> or <em>ip</em>:<em>port</em>. </li> + <li> The second argument must be <code>unix</code> or <code>tcp</code>. + <ul> + <li> <code>unix</code> means that the real server is listening on a Unix domain +socket reachable in the filesystem at <em>socketpath</em>. Note that +<em>socketpath</em> will be understood relative to tipideed's current filesystem +root, if it runs chrooted; this is generally not what you want, and <code>unix</code> +should generally only be used when tipideed is <em>not</em> chrooted. </li> + <li> <code>tcp</code> means that the real server is listening on an Internet +domain socket at address <em>ip</em>, port <em>port</em>. <em>ip</em> can be IPv4 +or IPv6. </li> + </ul> </li> + <li> Just like with the <code>redirection</code> directive, +<em>resource</em> does not need to exist in the filesystem. The same caveats apply. </li> +</ul> + + <div id="noredirect"> <h4> <tt>noredirect</tt> </h4> </div> @@ -860,6 +895,8 @@ requests received on port 443). But if you declare a redirection under the useful to carve exceptions to a generic redirection policy: if you have a <code>redirect</code> directive for directory A and a <code>noredirect</code> directive for resource B, and A is a prefix of B, then B will not be redirected, but everything else under A will. </li> + <li> You can also use <code>noredirect</code> to prevent <em>resource</em> from being +proxied by a more generic <code>rproxy</code> directive. </ul> <div id="custom-response"> diff --git a/package/deps.mak b/package/deps.mak index 2733f38..5c91a0c 100644 --- a/package/deps.mak +++ b/package/deps.mak @@ -71,6 +71,7 @@ src/tipideed/harden.o src/tipideed/harden.lo: src/tipideed/harden.c src/tipideed src/tipideed/options.o src/tipideed/options.lo: src/tipideed/options.c src/include/tipidee/log.h src/include/tipidee/response.h src/tipideed/tipideed-internal.h src/tipideed/redirection.o src/tipideed/redirection.lo: src/tipideed/redirection.c src/include/tipidee/log.h src/include/tipidee/response.h src/include/tipidee/util.h src/tipideed/tipideed-internal.h src/tipideed/regular.o src/tipideed/regular.lo: src/tipideed/regular.c src/include/tipidee/log.h src/include/tipidee/method.h src/include/tipidee/response.h src/tipideed/tipideed-internal.h +src/tipideed/rproxy.o src/tipideed/rproxy.lo: src/tipideed/rproxy.c src/include/tipidee/tipidee.h src/tipideed/tipideed-internal.h src/tipideed/send_file.o src/tipideed/send_file.lo: src/tipideed/send_file.c src/tipideed/tipideed-internal.h src/tipideed/stream.o src/tipideed/stream.lo: src/tipideed/stream.c src/tipideed/tipideed-internal.h src/tipideed/tipideed.o src/tipideed/tipideed.lo: src/tipideed/tipideed.c src/include/tipidee/tipidee.h src/tipideed/tipideed-internal.h @@ -94,5 +95,5 @@ libtipidee.dylib.xyzzy:src/libtipidee/tipidee_conf_free.lo src/libtipidee/tipide ls.cgi: EXTRA_LIBS := ls.cgi: src/misc/ls.cgi.o libtipidee.a.xyzzy -lskarnet tipideed: EXTRA_LIBS := -tipideed: src/tipideed/tipideed.o src/tipideed/cgi.o src/tipideed/errors.o src/tipideed/harden.o src/tipideed/options.o src/tipideed/regular.o src/tipideed/redirection.o src/tipideed/send_file.o src/tipideed/stream.o src/tipideed/trace.o src/tipideed/util.o libtipidee.a.xyzzy -lskarnet +tipideed: src/tipideed/tipideed.o src/tipideed/cgi.o src/tipideed/errors.o src/tipideed/harden.o src/tipideed/options.o src/tipideed/regular.o src/tipideed/redirection.o src/tipideed/rproxy.o src/tipideed/send_file.o src/tipideed/stream.o src/tipideed/trace.o src/tipideed/util.o libtipidee.a.xyzzy -lskarnet INTERNAL_LIBS := diff --git a/src/config/lexparse.c b/src/config/lexparse.c index ad22762..e87c501 100644 --- a/src/config/lexparse.c +++ b/src/config/lexparse.c @@ -6,6 +6,7 @@ #include <errno.h> #include <skalibs/uint32.h> +#include <skalibs/fmtscan.h> #include <skalibs/buffer.h> #include <skalibs/strerr.h> #include <skalibs/stralloc.h> @@ -45,6 +46,7 @@ enum directivevalue_e T_NPHPREFIX, T_REDIRECT, T_NOREDIRECT, + T_RPROXY, T_CGI, T_NONCGI, T_NPH, @@ -262,7 +264,7 @@ static inline void parse_noredirect (char const *s, size_t const *word, size_t n memcpy(key + 2, domain, domainlen) ; memcpy(key + 2 + domainlen, s + *word, urlen) ; key[2 + domainlen + urlen] = 0 ; - add_unique(key, " ", 2, md) ; + add_unique(key, "\200", 2, md) ; } } @@ -304,6 +306,63 @@ static inline void parse_redirect (char const *s, size_t const *word, size_t n, } } +static inline void parse_rproxy (char const *s, size_t const *word, size_t n, char const *domain, size_t domainlen, mdt const *md) +{ + uint8_t type ; + if (n < 3 || n > 4) + strerr_dief8x(1, "too ", n > 4 ? "many" : "few", " arguments to directive ", "rproxy", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + if (!domain) + strerr_dief6x(1, "rproxy redirection", " without a domain directive", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + if (s[word[0]] != '/') + strerr_dief6x(1, "redirected resource", " must start with /", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + if (!strcmp(s + word[1], "unix")) + type = 48 ; + else if (!strcmp(s + word[1], "tcp")) + type = 32 ; + else + strerr_dief7x(1, "rproxy", " directive", " needs to specify unix or tcp", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + + { + node node ; + size_t urlen = strlen(s + word[0]) ; + char key[3 + domainlen + urlen] ; + if (s[word[0] + urlen - 1] == '/') { key[0] = 'r' ; urlen-- ; } else key[0] = 'R' ; + key[1] = ':' ; + memcpy(key + 2, domain, domainlen) ; + memcpy(key + 2 + domainlen, s + word[0], urlen) ; + key[2 + domainlen + urlen] = 0 ; + conftree_checkunique(key, md) ; + confnode_start(&node, key, md->filepos, md->line) ; + + if (type == 48) + { + if (n != 3) + strerr_dief8x(1, "too ", "many" , " arguments to directive ", "rproxy", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + key[0] = '@' | type ; + confnode_add(&node, &key[0], 1) ; + confnode_add(&node, s + word[2], strlen(s + word[2]) + 1) ; + } + else + { + char ip[16] ; + uint16_t port ; + if (n != 4) + strerr_dief8x(1, "too ", "few" , " arguments to directive ", "rproxy", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + if (ip6_scan(s + word[2], ip)) type |= 8 ; + else if (!ip4_scan(s + word[2], ip)) + strerr_dief8x(1, "invalid ip address ", "in directive ", "rproxy", " tcp", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + if (!uint160_scan(s + word[3], &port)) + strerr_dief8x(1, "invalid port ", "in directive ", "rproxy", " tcp", " in file ", g.storage.s + md->filepos, " line ", md->linefmt) ; + key[0] = '@' | type ; + uint16_pack_big(key+1, port) ; + confnode_add(&node, key, 3) ; + confnode_add(&node, ip, type & 8 ? 16 : 4) ; + } + confnode_add(&node, "", 1) ; + conftree_add(&node) ; + } +} + static void parse_bitattr (char const *s, size_t const *word, size_t n, char const *domain, size_t domainlen, mdt const *md, uint8_t bit, int h) { static char const *const attr[3][2] = { { "noncgi", "cgi" }, { "nonnph", "nph", }, { "noauth", "basic-auth" } } ; @@ -450,6 +509,7 @@ static inline void process_line (char const *s, size_t const *word, size_t n, st { .name = "nph", .value = T_NPH }, { .name = "nph-prefix", .value = T_NPHPREFIX }, { .name = "redirect", .value = T_REDIRECT }, + { .name = "rproxy", .value = T_RPROXY }, } ; struct namevalue_s const *directive ; char const *word0 ; @@ -514,6 +574,9 @@ static inline void process_line (char const *s, size_t const *word, size_t n, st case T_REDIRECT : parse_redirect(s, word, n, domain->s, domain->len, md) ; break ; + case T_RPROXY : + parse_rproxy(s, word, n, domain->s, domain->len, md) ; + break ; case T_NOREDIRECT : parse_noredirect(s, word, n, domain->s, domain->len, md) ; break ; diff --git a/src/include/tipidee/conf.h b/src/include/tipidee/conf.h index aa8cb45..a173d6a 100644 --- a/src/include/tipidee/conf.h +++ b/src/include/tipidee/conf.h @@ -25,11 +25,11 @@ struct tipidee_conf_s typedef struct tipidee_redirection_s tipidee_redirection, *tipidee_redirection_ref ; struct tipidee_redirection_s { - char const *location ; char const *sub ; - uint32_t type : 2 ; + char const *location ; + uint8_t type ; } ; -#define TIPIDEE_REDIRECTION_ZERO { .location = 0, .sub = 0, .type = 0 } +#define TIPIDEE_REDIRECTION_ZERO { .sub = 0, .location = 0, .type = 0 } extern void tipidee_conf_free (tipidee_conf *) ; extern int tipidee_conf_init (tipidee_conf *, char const *) ; diff --git a/src/libtipidee/tipidee_conf_get_redirection.c b/src/libtipidee/tipidee_conf_get_redirection.c index 9554227..a8397ec 100644 --- a/src/libtipidee/tipidee_conf_get_redirection.c +++ b/src/libtipidee/tipidee_conf_get_redirection.c @@ -22,9 +22,8 @@ static int get_redir (tipidee_conf const *conf, size_t minl, char *key, size_t l v = tipidee_conf_get_string(conf, key) ; key[0] = 'r' ; } - if (!v || v[0] == ' ') return 0 ; - if (v[0] < '@' || v[0] > 'C') return (errno = EPROTO, -1) ; - r->type = v[0] & ~'@' ; + if (!v || ((unsigned char)v[0] & 128)) return 0 ; + r->type = v[0] ; r->location = v+1 ; r->sub = path + (l - minl + 1) ; return 1 ; diff --git a/src/tipideed/deps-exe/tipideed b/src/tipideed/deps-exe/tipideed index a47d715..67d619a 100644 --- a/src/tipideed/deps-exe/tipideed +++ b/src/tipideed/deps-exe/tipideed @@ -4,6 +4,7 @@ harden.o options.o regular.o redirection.o +rproxy.o send_file.o stream.o trace.o diff --git a/src/tipideed/redirection.c b/src/tipideed/redirection.c index e525e66..ce32e44 100644 --- a/src/tipideed/redirection.c +++ b/src/tipideed/redirection.c @@ -15,14 +15,15 @@ void respond_30x (tipidee_rql const *rql, tipidee_redirection const *rd) static unsigned int const status[4] = { 307, 308, 302, 301 } ; tipidee_defaulttext dt ; tain deadline ; - tipidee_util_defaulttext(status[rd->type], &dt) ; - tipidee_response_status(buffer_1, rql, status[rd->type], dt.reason) ; + unsigned int type = rd->type & 3 ; + tipidee_util_defaulttext(status[type], &dt) ; + tipidee_response_status(buffer_1, rql, status[type], dt.reason) ; tipidee_response_header_writeall_G(buffer_1, g.rhdr, g.rhdrn, 0) ; buffer_putsnoflush(buffer_1, "Content-Length: 0\r\nLocation: ") ; buffer_putsnoflush(buffer_1, rd->location) ; if (rd->sub) buffer_putsnoflush(buffer_1, rd->sub) ; buffer_putnoflush(buffer_1, "\r\n\r\n", 4) ; - tipidee_log_answer(g.logv, rql, status[rd->type], 0) ; + tipidee_log_answer(g.logv, rql, status[type], 0) ; tain_add_g(&deadline, &g.writetto) ; if (!buffer_timed_flush_g(buffer_1, &deadline)) strerr_diefu1sys(111, "write to stdout") ; diff --git a/src/tipideed/rproxy.c b/src/tipideed/rproxy.c new file mode 100644 index 0000000..5b0ec29 --- /dev/null +++ b/src/tipideed/rproxy.c @@ -0,0 +1,97 @@ +/* ISC license. */ + +#include <stdint.h> +#include <unistd.h> +#include <strings.h> + +#include <skalibs/uint16.h> +#include <skalibs/fmtscan.h> +#include <skalibs/tai.h> +#include <skalibs/buffer.h> +#include <skalibs/djbunix.h> +#include <skalibs/socket.h> +#include <skalibs/ip46.h> +#include <skalibs/unix-timed.h> + +#include <tipidee/tipidee.h> +#include "tipideed-internal.h" + +#define putit(b, s, deadline) if (buffer_timed_puts_g(b, s, deadline) == -1) die500sys(rql, 111, docroot, "write to ", fn) + +static void do_rproxy (tipidee_rql const *rql, int fd, char const *sub, char const *docroot, tipidee_headers const *hdr, char const *body, size_t bodylen, tain const *deadline, char const *fn) +{ + char buf[4096] ; + buffer b = BUFFER_INIT(&buffer_write, fd, buf, 4096) ; + putit(&b, tipidee_method_tostr(rql->m), deadline) ; + putit(&b, " ", deadline) ; + putit(&b, rql->uri.path, deadline) ; + if (rql->uri.query) + { + putit(&b, "?", deadline) ; + putit(&b, rql->uri.query, deadline) ; + } + putit(&b, " HTTP/1.", deadline) ; + putit(&b, rql->http_minor == 1 ? "1" : "0", deadline) ; + putit(&b, "\r\nConnection: close\r\n", deadline) ; + for (uint32_t i = 0 ; i < hdr->n ; i++) + { + if (!strcasecmp(hdr->buf + hdr->list[i].left, "Connection")) continue ; + putit(&b, hdr->buf + hdr->list[i].left, deadline) ; + putit(&b, ": ", deadline) ; + putit(&b, hdr->buf + hdr->list[i].right, deadline) ; + putit(&b, "\r\n", deadline) ; + } + putit(&b, "\r\n", deadline) ; + if (!buffer_timed_flush_g(&b, deadline)) die500sys(rql, 111, docroot, "write to ", fn) ; + if (bodylen) + { + if (timed_write_g(fd, body, bodylen, deadline) < bodylen) die500sys(rql, 111, docroot, "write to ", fn) ; + } + fd_shutdown(fd, 1) ; + if (ndelay_off(fd) == -1) die500sys(rql, 111, docroot, "set socket parameters for ", fn) ; + fd_cat(fd, 1) ; + _exit(0) ; /* expensive but the only way to avoid implementing a full proxy */ +} + +static void rproxy_unix (tipidee_rql const *rql, char const *socketpath, char const *sub, char const *docroot, tipidee_headers const *hdr, char const *body, size_t bodylen) +{ + tain deadline ; + int fd = ipc_stream_nbcoe() ; + if (fd == -1) die500sys(rql, 111, docroot, "create socket") ; + tain_add_g(&deadline, &g.readtto) ; + if (!ipc_timed_connect_g(fd, socketpath, &deadline)) die500sys(rql, 111, docroot, "connect to ", socketpath) ; + do_rproxy(rql, fd, sub, docroot, hdr, body, bodylen, &deadline, socketpath) ; + fd_close(fd) ; +} + +static void rproxy_tcp (tipidee_rql const *rql, char const *ip, uint16_t port, int is6, char const *sub, char const *docroot, tipidee_headers const *hdr, char const *body, size_t bodylen) +{ + tain deadline ; + size_t m = 0 ; + int fd = socket_tcp46_nbcoe(is6) ; + char fmt[IP6_FMT + UINT16_FMT + 2] ; + if (fd == -1) die500sys(rql, 111, docroot, "create socket") ; + tain_add_g(&deadline, &g.readtto) ; + if (is6) fmt[m++] = '[' ; + m += is6 ? ip6_fmt(fmt + m, ip) : ip4_fmt(fmt + m, ip) ; + if (is6) fmt[m++] = ']' ; + fmt[m++] = ':' ; + m += uint16_fmt(fmt + m, port) ; + fmt[m++] = 0 ; + + if (!(is6 ? socket_deadlineconnstamp6_g(fd, ip, port, &deadline) : socket_deadlineconnstamp4_g(fd, ip, port, &deadline))) + die500sys(rql, 111, docroot, "connect to ", fmt) ; + do_rproxy(rql, fd, sub, docroot, hdr, body, bodylen, &deadline, fmt) ; + fd_close(fd) ; +} + +void rproxy (tipidee_rql const *rql, tipidee_redirection const *rd, char const *docroot, tipidee_headers const *hdr, char const *body, size_t bodylen) +{ + if (rd->type & 16) rproxy_unix(rql, rd->location, rd->sub, docroot, hdr, body, bodylen) ; + else + { + uint16_t port ; + uint16_unpack_big(rd->location, &port) ; + rproxy_tcp(rql, rd->location + 2, port, !!(rd->type & 8), rd->sub, docroot, hdr, body, bodylen) ; + } +} diff --git a/src/tipideed/tipideed-internal.h b/src/tipideed/tipideed-internal.h index e8cf102..d3a66f6 100644 --- a/src/tipideed/tipideed-internal.h +++ b/src/tipideed/tipideed-internal.h @@ -124,6 +124,11 @@ extern void respond_416 (tipidee_rql const *, char const *, uint64_t) ; extern void respond_30x (tipidee_rql const *, tipidee_redirection const *) ; + /* rproxy */ + +extern void rproxy (tipidee_rql const *, tipidee_redirection const *, char const *, tipidee_headers const *, char const *, size_t) ; + + /* trace */ extern int respond_trace (tipidee_rql const *, tipidee_headers const *) ; diff --git a/src/tipideed/tipideed.c b/src/tipideed/tipideed.c index c32cd2c..6cfd4ec 100644 --- a/src/tipideed/tipideed.c +++ b/src/tipideed/tipideed.c @@ -233,7 +233,7 @@ static inline int serve (tipidee_rql *rql, char const *docroot, char *uribuf, ti memcpy(fn + docrootlen, rql->uri.path, pathlen) ; fn[docrootlen + pathlen] = 0 ; - /* Redirection */ + /* Redirection or reverse proxy */ if (rql->m != TIPIDEE_METHOD_OPTIONS) { @@ -242,7 +242,8 @@ static inline int serve (tipidee_rql *rql, char const *docroot, char *uribuf, ti if (e == -1) die500sys(rql, 111, docroot, "get redirection data for ", fn) ; if (e) { - respond_30x(rql, &rd) ; + if (rd.type & 32) rproxy(rql, &rd, docroot, hdr, body, bodylen) ; + else respond_30x(rql, &rd) ; return 0 ; } } |
