diff options
author | Omar Polo <op@omarpolo.com> | 2021-12-29 20:36:54 +0000 |
---|---|---|
committer | Omar Polo <op@omarpolo.com> | 2021-12-29 20:36:54 +0000 |
commit | 72b033ef18ae3f82922f6f11ce0f5194e95f667d (patch) | |
tree | 5f06b0c70851aa17f17251579adb65a66a8081ca /proxy.c | |
parent | 054387bb26e75cef12e8dc0f531e7ee42614edd7 (diff) |
add ability to proxy requests
Add to gmid the ability to forwad a request to another gemini server and
thus acting like a reverse proxy. The current syntax for the config
file is
server "example.com" {
...
proxy relay-to host:port
}
Further options (like the use of custom certificates) are planned.
cf. github issue #7
Diffstat (limited to 'proxy.c')
-rw-r--r-- | proxy.c | 318 |
1 files changed, 318 insertions, 0 deletions
@@ -0,0 +1,318 @@ +/* + * Copyright (c) 2021 Omar Polo <op@omarpolo.com> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "gmid.h" + +#include <ctype.h> +#include <errno.h> +#include <string.h> + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +static struct timeval handshake_timeout = { 5, 0 }; + +static void proxy_tls_readcb(int, short, void *); +static void proxy_tls_writecb(int, short, void *); +static void proxy_read(struct bufferevent *, void *); +static void proxy_write(struct bufferevent *, void *); +static void proxy_error(struct bufferevent *, short, void *); + +static void +proxy_tls_readcb(int fd, short event, void *d) +{ + struct bufferevent *bufev = d; + struct client *c = bufev->cbarg; + char buf[IBUF_READ_SIZE]; + int what = EVBUFFER_READ; + int howmuch = IBUF_READ_SIZE; + int res; + ssize_t ret; + size_t len; + + if (event == EV_TIMEOUT) { + what |= EVBUFFER_TIMEOUT; + goto err; + } + + if (bufev->wm_read.high != 0) + howmuch = MIN(sizeof(buf), bufev->wm_read.high); + + switch (ret = tls_read(c->proxyctx, buf, howmuch)) { + case TLS_WANT_POLLIN: + case TLS_WANT_POLLOUT: + goto retry; + case -1: + what |= EVBUFFER_ERROR; + goto err; + } + len = ret; + + if (len == 0) { + what |= EVBUFFER_EOF; + goto err; + } + + res = evbuffer_add(bufev->input, buf, len); + if (res == -1) { + what |= EVBUFFER_ERROR; + goto err; + } + + event_add(&bufev->ev_read, NULL); + + len = EVBUFFER_LENGTH(bufev->input); + if (bufev->wm_read.low != 0 && len < bufev->wm_read.low) + return; + + if (bufev->readcb != NULL) + (*bufev->readcb)(bufev, bufev->cbarg); + return; + +retry: + event_add(&bufev->ev_read, NULL); + return; + +err: + (*bufev->errorcb)(bufev, what, bufev->cbarg); +} + +static void +proxy_tls_writecb(int fd, short event, void *d) +{ + struct bufferevent *bufev = d; + struct client *c = bufev->cbarg; + ssize_t ret; + size_t len; + short what = EVBUFFER_WRITE; + + if (event & EV_TIMEOUT) { + what |= EVBUFFER_TIMEOUT; + goto err; + } + + if (EVBUFFER_LENGTH(bufev->output) != 0) { + ret = tls_write(c->proxyctx, EVBUFFER_DATA(bufev->output), + EVBUFFER_LENGTH(bufev->output)); + switch (ret) { + case TLS_WANT_POLLIN: + case TLS_WANT_POLLOUT: + goto retry; + case -1: + what |= EVBUFFER_ERROR; + goto err; + } + len = ret; + + evbuffer_drain(bufev->output, len); + } + + if (EVBUFFER_LENGTH(bufev->output) != 0) + event_add(&bufev->ev_write, NULL); + + if (bufev->writecb != NULL && + EVBUFFER_LENGTH(bufev->output) <= bufev->wm_write.low) + (*bufev->writecb)(bufev, bufev->cbarg); + return; + +retry: + event_add(&bufev->ev_write, NULL); + return; + +err: + (*bufev->errorcb)(bufev, what, bufev->cbarg); +} + +static void +proxy_read(struct bufferevent *bev, void *d) +{ + struct client *c = d; + struct evbuffer *src = EVBUFFER_INPUT(bev); + char *hdr; + size_t len; + int code; + + /* intercept the header */ + if (c->code == 0) { + hdr = evbuffer_readln(src, &len, EVBUFFER_EOL_CRLF_STRICT); + if (hdr == NULL) { + /* max reply + \r\n */ + if (EVBUFFER_LENGTH(src) > 1029) { + log_warn(c, "upstream server is trying to " + "send a header that's too long."); + proxy_error(bev, EVBUFFER_READ, c); + } + + /* wait a bit */ + return; + } + + if (len < 3 || len > 1029 || + !isdigit(hdr[0]) || + !isdigit(hdr[1]) || + !isspace(hdr[2])) { + free(hdr); + log_warn(c, "upstream server is trying to send a " + "header that's too long."); + proxy_error(bev, EVBUFFER_READ, c); + return; + } + + c->header = hdr; + code = (hdr[0] - '0') * 10 + (hdr[1] - '0'); + + if (code < 10 || code >= 70) { + log_warn(c, "upstream server is trying to send an " + "invalid reply code: %d", code); + proxy_error(bev, EVBUFFER_READ, c); + return; + } + + start_reply(c, code, hdr + 3); + + if (c->code < 20 || c->code > 29) { + proxy_error(bev, EVBUFFER_EOF, c); + return; + } + } + + bufferevent_write_buffer(c->bev, src); +} + +static void +proxy_write(struct bufferevent *bev, void *d) +{ + struct evbuffer *dst = EVBUFFER_OUTPUT(bev); + + /* request successfully sent */ + if (EVBUFFER_LENGTH(dst) == 0) + bufferevent_disable(bev, EV_WRITE); +} + +static void +proxy_error(struct bufferevent *bev, short error, void *d) +{ + struct client *c = d; + + /* + * If we're here it means that some kind of non-recoverable + * error appened. + */ + + bufferevent_free(bev); + c->proxybev = NULL; + + tls_free(c->proxyctx); + c->proxyctx = NULL; + + close(c->pfd); + c->pfd = -1; + + /* EOF and no header */ + if (c->code == 0) { + start_reply(c, PROXY_ERROR, "protocol error"); + return; + } + + c->type = REQUEST_DONE; + client_write(c->bev, c); +} + +static void +proxy_handshake(int fd, short event, void *d) +{ + struct client *c = d; + struct evbuffer *evb; + char iribuf[GEMINI_URL_LEN]; + + if (event == EV_TIMEOUT) { + start_reply(c, PROXY_ERROR, "timeout"); + return; + } + + switch (tls_handshake(c->proxyctx)) { + case TLS_WANT_POLLIN: + event_set(&c->proxyev, fd, EV_READ, proxy_handshake, c); + event_add(&c->proxyev, &handshake_timeout); + return; + case TLS_WANT_POLLOUT: + event_set(&c->proxyev, fd, EV_WRITE, proxy_handshake, c); + event_add(&c->proxyev, &handshake_timeout); + return; + case -1: + log_warn(c, "handshake with proxy failed: %s", + tls_error(c->proxyctx)); + start_reply(c, PROXY_ERROR, "handshake failed"); + return; + } + + c->proxybev = bufferevent_new(c->pfd, proxy_read, proxy_write, + proxy_error, c); + if (c->proxybev == NULL) + fatal("can't allocate bufferevent: %s", strerror(errno)); + + event_set(&c->proxybev->ev_read, c->pfd, EV_READ, + proxy_tls_readcb, c->proxybev); + event_set(&c->proxybev->ev_write, c->pfd, EV_WRITE, + proxy_tls_writecb, c->proxybev); + +#if HAVE_LIBEVENT2 + evbuffer_unfreeze(c->proxybev->input, 0); + evbuffer_unfreeze(c->proxybev->output, 1); +#endif + + serialize_iri(&c->iri, iribuf, sizeof(iribuf)); + + evb = EVBUFFER_OUTPUT(c->proxybev); + evbuffer_add_printf(evb, "%s\r\n", iribuf); + + bufferevent_enable(c->proxybev, EV_READ|EV_WRITE); +} + +int +proxy_init(struct client *c) +{ + struct tls_config *conf = NULL; + + c->type = REQUEST_PROXY; + + if ((conf = tls_config_new()) == NULL) + return -1; + + /* TODO: tls_config_set_protocols here */ + /* TODO: optionally load a client keypair here */ + tls_config_insecure_noverifycert(conf); + + if ((c->proxyctx = tls_client()) == NULL) + goto err; + + if (tls_configure(c->proxyctx, conf) == -1) + goto err; + + if (tls_connect_socket(c->proxyctx, c->pfd, c->domain) == -1) + goto err; + + event_set(&c->proxyev, c->pfd, EV_READ|EV_WRITE, proxy_handshake, c); + event_add(&c->proxyev, &handshake_timeout); + + tls_config_free(conf); + return 0; + +err: + tls_config_free(conf); + if (c->proxyctx != NULL) + tls_free(c->proxyctx); + return -1; +} |