aboutsummaryrefslogtreecommitdiff
path: root/proxy.c
diff options
context:
space:
mode:
authorOmar Polo <op@omarpolo.com>2021-12-29 20:36:54 +0000
committerOmar Polo <op@omarpolo.com>2021-12-29 20:36:54 +0000
commit72b033ef18ae3f82922f6f11ce0f5194e95f667d (patch)
tree5f06b0c70851aa17f17251579adb65a66a8081ca /proxy.c
parent054387bb26e75cef12e8dc0f531e7ee42614edd7 (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.c318
1 files changed, 318 insertions, 0 deletions
diff --git a/proxy.c b/proxy.c
new file mode 100644
index 0000000..72ce9f7
--- /dev/null
+++ b/proxy.c
@@ -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;
+}