/* * Copyright (c) 2021-2024 Omar Polo * * 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 #include #include #include "log.h" #define MINIMUM(a, b) ((a) < (b) ? (a) : (b)) static const 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 = MINIMUM(sizeof(buf), bufev->wm_read.high); switch (ret = tls_read(c->proxyctx, buf, howmuch)) { case TLS_WANT_POLLIN: case TLS_WANT_POLLOUT: event_add(&bufev->ev_read, NULL); return; 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; 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: event_add(&bufev->ev_write, NULL); return; 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; 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_warnx("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((unsigned char)hdr[0]) || !isdigit((unsigned char)hdr[1]) || !isspace((unsigned char)hdr[2])) { free(hdr); log_warnx("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_warnx("upstream server is trying to send an " "invalid reply code: %d", code); proxy_error(bev, EVBUFFER_READ, c); return; } if (start_reply(c, code, hdr + 3) == -1 || 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 happened. */ 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_enqueue_req(struct client *c) { struct proxy *p = c->proxy; struct evbuffer *evb; char iribuf[GEMINI_URL_LEN]; c->proxybev = bufferevent_new(c->pfd, proxy_read, proxy_write, proxy_error, c); if (c->proxybev == NULL) fatal("can't allocate bufferevent"); evb = EVBUFFER_OUTPUT(c->proxybev); if (p->notls && p->proxy) evbuffer_add(evb, c->buf.data, c->buf.len); if (!p->notls) { 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)); evbuffer_add_printf(evb, "%s\r\n", iribuf); bufferevent_enable(c->proxybev, EV_READ|EV_WRITE); } static void proxy_handshake(int fd, short event, void *d) { struct client *c = d; 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_warnx("handshake with proxy failed: %s", tls_error(c->proxyctx)); start_reply(c, PROXY_ERROR, "handshake failed"); return; } c->proxyevset = 0; proxy_enqueue_req(c); } static ssize_t proxy_read_cb(struct tls *ctx, void *buf, size_t buflen, void *cb_arg) { struct client *c = cb_arg; ssize_t ret; ret = read(c->pfd, buf, buflen); if (ret == -1 && errno == EAGAIN) ret = TLS_WANT_POLLIN; return ret; } static ssize_t proxy_write_cb(struct tls *ctx, const void *buf, size_t len, void *cb_arg) { struct client *c = cb_arg; ssize_t ret; size_t left; while ((left = c->buf.len - c->buf.read_pos) > 0) { ret = write(c->pfd, c->buf.data + c->buf.read_pos, left); if (ret == -1 && errno == EAGAIN) return (TLS_WANT_POLLOUT); if (ret <= 0) return (ret); c->buf.read_pos += ret; } ret = write(c->pfd, buf, len); if (ret == -1 && errno == EAGAIN) ret = TLS_WANT_POLLOUT; return (ret); } static int proxy_setup_tls(struct client *c) { struct proxy *p = c->proxy; struct tls_config *conf = NULL; const char *hn; if ((conf = tls_config_new()) == NULL) return -1; if (p->noverifyname) tls_config_insecure_noverifyname(conf); tls_config_insecure_noverifycert(conf); tls_config_set_protocols(conf, p->protocols); if (p->cert != NULL) { int r; r = tls_config_set_cert_mem(conf, p->cert, p->certlen); if (r == -1) goto err; r = tls_config_set_key_mem(conf, p->key, p->keylen); if (r == -1) goto err; } if ((c->proxyctx = tls_client()) == NULL) goto err; if (tls_configure(c->proxyctx, conf) == -1) goto err; if (*(hn = p->sni) == '\0') hn = p->host; if (tls_connect_cbs(c->proxyctx, proxy_read_cb, proxy_write_cb, c, hn) == -1) goto err; c->proxyevset = 1; 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); c->proxyctx = NULL; } return -1; } int proxy_init(struct client *c) { struct proxy *p = c->proxy; if (p->notls) proxy_enqueue_req(c); else if (proxy_setup_tls(c) == -1) return -1; c->type = REQUEST_PROXY; return 0; }