diff --git a/README.rst b/README.rst index 158c5b7..e2e65da 100644 --- a/README.rst +++ b/README.rst @@ -23,7 +23,7 @@ Key features and capabilities include: * File transfers via FTP, SFTP, Gopher, and HTTP/HTTPS -* HTTP 1.1 web server, with WebSocket support +* HTTP 1.1 web server, with WebSocket and forward-proxy support * User home directories @@ -42,6 +42,7 @@ Key features and capabilities include: * Mailbox quotas * Shared mailboxes and ACL controls * Multi-domain support + * Relay support * IMAP NOTIFY support * RFC 4468 BURL IMAP and server-side proxied append support, for more efficient (bandwidth saving) email submission * Remote mailboxes (IMAP proxy) diff --git a/bbs/variables.c b/bbs/variables.c index 9451373..a38e6ee 100644 --- a/bbs/variables.c +++ b/bbs/variables.c @@ -75,6 +75,22 @@ const char *bbs_vars_peek_head(struct bbs_vars *vars, char **value) return NULL; } +const char *bbs_varlist_next(struct bbs_vars *vars, struct bbs_var **v, const char **key) +{ + struct bbs_var *vnext; + if (!*v) { + vnext = RWLIST_FIRST(vars); + } else { + vnext = RWLIST_NEXT(*v, entry); + } + *v = vnext; /* Set iterator to next item */ + if (*v) { + *key = vnext->key; + return vnext->value; + } + return NULL; +} + static int load_config(void) { struct bbs_config_section *section = NULL; diff --git a/configs/mod_http_proxy.conf b/configs/mod_http_proxy.conf new file mode 100644 index 0000000..b7b155a --- /dev/null +++ b/configs/mod_http_proxy.conf @@ -0,0 +1,5 @@ +; mod_http_proxy.conf + +[clients] ; One entry for each IP, CIDR range, or hostname authorized to proxy requests. Proxying using CONNECT is only allowed to ports 80 and 443. +;10.1.1.1 = * ; allow proxying to all destinations +;10.1.1.2 = example.com ; allow proxying only to example.com diff --git a/include/mod_http.h b/include/mod_http.h index edb9d93..908600d 100644 --- a/include/mod_http.h +++ b/include/mod_http.h @@ -27,6 +27,8 @@ enum http_method { HTTP_METHOD_CONNECT = (1 << 7), }; +const char *http_method_name(enum http_method method); + enum http_version { HTTP_VERSION_UNKNOWN = 0, HTTP_VERSION_0_9 = (1 << 0), @@ -103,6 +105,7 @@ struct http_request { unsigned char *body; struct tm modsince; int numheaders; + unsigned int hostport; /* Pointers to allocated data */ const char *host; const char *querystring; @@ -113,6 +116,7 @@ struct http_request { unsigned int chunked:1; /*!< Request uses chunked transfer encoding */ unsigned int expect100:1; /*!< Expecting 100-continue */ unsigned int parsedbody:1; + unsigned int absolute:1; /*!< Absolute host used in request */ }; struct http_response { @@ -136,11 +140,15 @@ struct http_session { struct http_response resstack; struct bbs_node *node; struct readline_data *rldata; + char *buf; /*!< Stack-allocated readline data buffer */ int rfd; int wfd; unsigned int secure:1; }; +/*! \brief Send just the HTTP response code (initial line) */ +void http_send_response_status(struct http_session *http, enum http_response_code code); + /*! * \brief Parse an HTTP request that is pending on an http_session's node's file descriptor * \note Do not use this function directly unless needed; this is primarily internal and only used externally by net_wss. @@ -199,6 +207,9 @@ void http_write(struct http_session *http, const char *buf, size_t len); /*! \brief Same as http_write, but accept printf-style arguments */ int __attribute__ ((format (gnu_printf, 2, 3))) http_writef(struct http_session *http, const char *fmt, ...); +/*! \brief Get string representation of an HTTP version number */ +const char *http_version_name(enum http_version version); + /*! * \brief Get an HTTP request header, if it exists * \param http @@ -258,6 +269,9 @@ const char *http_session_var(struct http_session *http, const char *name); */ int http_session_set_var(struct http_session *http, const char *name, const char *value); +/*! \brief Whether a request is a proxy request (either tunneled or regular) */ +int http_is_proxy_request(struct http_session *http); + /*! \brief Whether a websocket upgrade was requested by the client */ int http_websocket_upgrade_requested(struct http_session *http); @@ -359,3 +373,10 @@ int __http_register_route(const char *hostname, unsigned short int port, unsigne /*! \brief Unregister a route previously registered using __http_register_route */ int http_unregister_route(enum http_response_code (*handler)(struct http_session *http)); + +#define http_register_proxy_handler(port, methods, handler) __http_register_proxy_handler(port, methods, handler, BBS_MODULE_SELF) + +int __http_register_proxy_handler(unsigned short int port, enum http_method methods, enum http_response_code (*handler)(struct http_session *http), void *mod); + +/*! \brief Unregister a proxy handler */ +int http_unregister_proxy_handler(enum http_response_code (*handler)(struct http_session *http)); diff --git a/include/variables.h b/include/variables.h index 13ed55c..034eb4f 100644 --- a/include/variables.h +++ b/include/variables.h @@ -36,6 +36,15 @@ void bbs_vars_remove_first(struct bbs_vars *vars); */ const char *bbs_vars_peek_head(struct bbs_vars *vars, char **value); +/*! + * \brief Get the next variable in a variable list + * \param vars + * \param[out] Next variable for iterator + * \param[out] Variable key + * \return Next variable value, or NULL if no next value + */ +const char *bbs_varlist_next(struct bbs_vars *vars, struct bbs_var **v, const char **key); + /*! \brief Called during shutdown to free global variables */ void bbs_vars_cleanup(void); diff --git a/modules/mod_http.c b/modules/mod_http.c index 732d59e..9272ad9 100644 --- a/modules/mod_http.c +++ b/modules/mod_http.c @@ -71,7 +71,7 @@ #define http_debug(level, fmt, ...) bbs_debug(level, fmt, ## __VA_ARGS__) -static const char *http_method_name(enum http_method method) +const char *http_method_name(enum http_method method) { switch (method) { case HTTP_METHOD_OPTIONS: @@ -133,6 +133,11 @@ static RWLIST_HEAD_STATIC(listeners, http_listener); static RWLIST_HEAD_STATIC(routes, http_route); static RWLIST_HEAD_STATIC(sessions, session); +static enum http_response_code (*proxy_handler)(struct http_session *http) = NULL; +static void *proxy_handler_mod = NULL; +static unsigned short int proxy_port = 0; +static enum http_method proxy_methods = HTTP_METHOD_UNDEF; + #define http_send_header(http, fmt, ...) \ bbs_node_fd_writef(http->node, http->wfd, fmt, ## __VA_ARGS__); \ http_debug(5, "<= " fmt, ## __VA_ARGS__); @@ -175,15 +180,20 @@ static const char *http_response_code_name(enum http_response_code code) return ""; } +void http_send_response_status(struct http_session *http, enum http_response_code code) +{ + bbs_assert(!http->res->sentheaders); + http->res->sentheaders = 1; + http_send_header(http, "HTTP/1.1 %u %s\r\n", code, http_response_code_name(code)); +} + static void http_send_headers(struct http_session *http) { const char *key; char *value; enum http_response_code code = http->res->code ? http->res->code : HTTP_OK; - bbs_assert(!http->res->sentheaders); - - http_send_header(http, "HTTP/1.1 %u %s\r\n", code, http_response_code_name(code)); + http_send_response_status(http, code); /* Note: Headers sent here via http_send_header are not intended to be set by applications, * since they would be duped in the header list, and not override what is sent here. */ @@ -234,7 +244,6 @@ static void http_send_headers(struct http_session *http) bbs_vars_remove_first(&http->res->headers); } NODE_SWRITE(http->node, http->wfd, "\r\n"); /* CR LF to indicate end of headers */ - http->res->sentheaders = 1; } int http_set_header(struct http_session *http, const char *header, const char *value) @@ -407,7 +416,7 @@ int http_writef(struct http_session *http, const char *fmt, ...) return len; } -static const char *http_version_name(enum http_version version) +const char *http_version_name(enum http_version version) { switch (version) { case HTTP_VERSION_0_9: @@ -586,6 +595,7 @@ static int parse_request_line(struct http_session *restrict http, char *s) } else { char *uri; /* Ooh, an absolute URL. Uncommon but could happen. + * (One common use case is with HTTP proxying.) * Parse out the hostname and the URI from this. */ if (STARTS_WITH(tmp, "http://")) { tmp += STRLEN("http://"); @@ -606,6 +616,7 @@ static int parse_request_line(struct http_session *restrict http, char *s) *uri = '\0'; http->req->urihost = strdup(tmp); http->req->host = http->req->urihost; + http->req->absolute = 1; } if (ALLOC_FAILURE(http->req->uri)) { return HTTP_INTERNAL_SERVER_ERROR; @@ -646,9 +657,11 @@ static int process_headers(struct http_session *http) if (strlen_zero(portstr)) { bbs_warning("Malformed host: %s\n", value); } else { - unsigned int port = (unsigned int) atoi(portstr); - if (port != http->node->port) { - bbs_warning("Host port %u does not match actual port %u\n", port, http->node->port); + http->req->hostport = (unsigned int) atoi(portstr); + if (http->req->hostport != http->node->port && !(http->req->method & HTTP_METHOD_CONNECT) && !http->req->absolute) { + /* For proxy connections, the port could be anything arbitrary, + * but otherwise, it's not legitimate and we should reject it. */ + bbs_warning("Host port %u does not match actual port %u\n", http->req->hostport, http->node->port); return HTTP_BAD_REQUEST; } } @@ -767,15 +780,43 @@ static int process_headers(struct http_session *http) } } - if (http->req->method & HTTP_METHOD_CONNECT) { - /* The CONNECT method is for proxy servers, which we aren't one. - * This is almost certainly spam traffic. */ - bbs_event_dispatch(http->node, EVENT_NODE_BAD_REQUEST); - } - return 0; } +int http_is_proxy_request(struct http_session *http) +{ + /* There are two ways that clients establish proxy connections. + * + * A. The traditional way is to connect to a proxy (often on its own dedicated port) + * and simply make the request. The server then replays the request to the target, + * and relays the response. Regular methods, e.g. GET, POST, etc. are used. + * + * B. Another method, specified in RFC 7231 4.3.6, and always used for HTTPS, is to use the CONNECT method + * to the proxy server, establish a tunnel to the destination, and then set up + * TLS and make the actual HTTP requests on top of that. + * A client *could* do this for plain HTTP requests as well, but in practice + * most don't, unless you tell them to (e.g. cURL with the -p option, in addition to -x) + * Because the client could, theoretically, request connection to any arbitrary TCP port, + * servers generally restrict the connection to port 443 (and maybe 80) only. + * + * A. GET http://example.com/file.html HTTP/1.1 + * B. CONNECT example.com:443 HTTP/1.1 + * + * We support both, for maximum compatibility. For CONNECT requests, it's obvious + * that it's a proxy, but in the first case, it's not as clear cut if we're not + * running on a port dedicated for the proxy. There are two telltale signs: + * - Using an absolute URL in the request header (e.g. http://example.com). + * This is mandatory for proxy connections, but does not necessarily + * indicate a proxy connection (even if uncommon, otherwise) + * - Presence of the Proxy-Connection header. In contrast, this is a sure confirmation, + * but I'm not 100% sure this header will always be present, though it does seem + * fairly reliable, between cURL and browsers, and seems to be the only thing + * that CAN actually identify it as a proxy request. + * Obviously, this header should not be passed forward when replaying the request. + */ + return http->req->method & HTTP_METHOD_CONNECT || (http->req->absolute && http_request_header(http, "Proxy-Connection")); +} + int http_websocket_upgrade_requested(struct http_session *http) { const char *value; @@ -1708,6 +1749,34 @@ static int http_handle_request(struct http_session *http, char *buf) return res; } + /* Proxy requests really need to be handled before doing anything else, since they're fairly low level. + * We don't want to read or process the body. + * We don't care what the request is for, + * and we don't want to use any of the regular routes. */ + if (http_is_proxy_request(http)) { + /* Pass it off to the proxy handler, if one exists. + * Otherwise, just reject it as unauthorized. */ + if (!proxy_port && http_get_default_http_port() > 0) { + proxy_port = (unsigned short int) http_get_default_http_port(); + } + if (http->node->port != proxy_port) { + bbs_debug(3, "Node port %u does not match proxy port %u\n", http->node->port, proxy_port); + } else if (!(proxy_methods & http->req->method)) { + bbs_debug(3, "Proxy handler does not support %s\n", http_method_name(http->req->method)); + } else { + if (proxy_handler) { + bbs_debug(4, "Passing %s proxy request for %s to proxy handler\n", http_method_name(http->req->method), http->req->uri); + bbs_module_ref(proxy_handler_mod, 1); + code = proxy_handler(http); + bbs_module_unref(proxy_handler_mod, 1); + return code; + } + bbs_event_dispatch(http->node, EVENT_NODE_BAD_REQUEST); /* Likely spam traffic. */ + return HTTP_UNAUTHORIZED; + } + /* Fall through and treat as non proxy request */ + } + /* Search for a matching route, before processing the body. */ route = find_route(http->node->port, http->req->host, http->req->uri, http->req->method, &methodmismatch, http->req->httpsupgrade ? &secureport : NULL); if (!http->secure && http->req->httpsupgrade && secureport && secureport != http->node->port) { @@ -1810,6 +1879,7 @@ static void http_handler(struct bbs_node *node, int secure) } bbs_readline_init(&rldata, buf, sizeof(buf)); + http.buf = buf; do { res = http_handle_request(&http, buf); @@ -2124,7 +2194,7 @@ enum http_response_code http_static(struct http_session *http, const char *filen * Calculate overhead of the multipart headers. * THIS MUST BE DONE EXACTLY THE SAME WAY THE HEADERS ARE ACTUALLY GENERATED AT THE BOTTOM OF THIS FUNCTION! */ - overhead += STRLEN("--" RANGE_SEPARATOR "\r\n"); + overhead += STRLEN("--" RANGE_SEPARATOR "\r\n"); /* XXX What if RANGE_SEPARATOR appears in the content? */ overhead += STRLEN("Content-Range: "); overhead += (size_t) snprintf(bytes_list, sizeof(bytes_list), "bytes %ld-%ld", a, b); overhead += STRLEN("\r\n\r\n"); /* Content-Range CR LF, plus CR LF for end of headers */ @@ -2736,6 +2806,32 @@ int http_unregister_route(enum http_response_code (*handler)(struct http_session return removed ? 0 : -1; } +int __http_register_proxy_handler(unsigned short int port, enum http_method methods, enum http_response_code (*handler)(struct http_session *http), void *mod) +{ + if (proxy_handler) { + bbs_error("Proxy handler already registered\n"); + return -1; + } + proxy_handler_mod = mod; + proxy_handler = handler; + proxy_port = port; + proxy_methods = methods; + return 0; +} + +int http_unregister_proxy_handler(enum http_response_code (*handler)(struct http_session *http)) +{ + if (handler != proxy_handler) { + bbs_error("Proxy handler %p not currently registered\n", handler); + return -1; + } + proxy_handler = NULL; + proxy_handler_mod = NULL; + proxy_port = 0; + proxy_methods = HTTP_METHOD_UNDEF; + return 0; +} + static int cli_http_routes(struct bbs_cli_args *a) { struct http_route *r; diff --git a/modules/mod_http_proxy.c b/modules/mod_http_proxy.c new file mode 100644 index 0000000..67c316f --- /dev/null +++ b/modules/mod_http_proxy.c @@ -0,0 +1,319 @@ +/* + * LBBS -- The Lightweight Bulletin Board System + * + * Copyright (C) 2023, Naveen Albert + * + * Naveen Albert + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file + * + * \brief HTTP Forward Proxy Server + * + * \author Naveen Albert + */ + +#include "include/bbs.h" + +#include + +#include "include/module.h" +#include "include/config.h" +#include "include/node.h" +#include "include/utils.h" + +/* Needed for mod_http.h */ +#include "include/linkedlists.h" +#include "include/variables.h" + +#include "include/stringlist.h" + +#include "include/mod_http.h" + +/* The way http_proxy_client is used is almost identical to smtp_relay_host in net_smtp */ +struct http_proxy_client { + const char *source; + struct stringlist domains; + RWLIST_ENTRY(http_proxy_client) entry; + char data[]; +}; + +static RWLIST_HEAD_STATIC(proxy_clients, http_proxy_client); + +static void add_proxy_client(const char *source, const char *domains) +{ + struct http_proxy_client *c; + + if (STARTS_WITH(source, "0.0.0.0")) { + /* If someone wants to shoot him/herself in the foot, at least provide a warning */ + bbs_notice("This server is configured as an open proxy and may be abused!\n"); + } + + c = calloc(1, sizeof(*c) + strlen(source) + 1); + if (ALLOC_FAILURE(c)) { + return; + } + + strcpy(c->data, source); /* Safe */ + c->source = c->data; + stringlist_push_list(&c->domains, domains); + + /* Head insert, so later entries override earlier ones, in case multiple match */ + RWLIST_INSERT_HEAD(&proxy_clients, c, entry); +} + +static void proxy_client_free(struct http_proxy_client *c) +{ + stringlist_empty(&c->domains); + free(c); +} + +static int proxy_client_authorized(const char *srcip, const char *hostname) +{ + struct http_proxy_client *c; + + RWLIST_RDLOCK(&proxy_clients); + RWLIST_TRAVERSE(&proxy_clients, c, entry) { + if (bbs_ip_match_ipv4(srcip, c->source)) { + /* Just needs to be allowed by one matching entry, or wildcard allow. */ + if (stringlist_contains(&c->domains, hostname) || stringlist_contains(&c->domains, "*")) { + RWLIST_UNLOCK(&proxy_clients); + return 1; + } + } + } + RWLIST_UNLOCK(&proxy_clients); + return 0; +} + +/*! \brief Whether or not a header may be forwarded if via proxy + * \param h Header name + * \retval 1 OK to forward + * \retval 0 Must not be forwarded + */ +static int proxy_header_forwardable(const char *h) +{ + if (!strcasecmp(h, "Proxy-Connection")) { + return 0; + } + return 1; +} + +static int proxy_forward_headers(struct http_session *http, struct bbs_tcp_client *client) +{ + /* Replay the request headers, omitting Proxy-Connection. + * Some proxies also add other identifying headers like X-Forwarded-For here. */ + struct bbs_var *v = NULL; /* Iterate from beginning */ + const char *key, *val; + int sent = 0; + struct bbs_vars *headers = &http->req->headers; + + /* Write the initial request line */ + if (bbs_writef(client->wfd, "%s %s %s\r\n", http_method_name(http->req->method), http->req->uri, http_version_name(http->req->version)) < 0) { + bbs_debug(2, "Failed to write request line to proxy target\n"); + return -1; + } + + /* Just blindly relay all the headers the client sent, + * unless we're explicitly not supposed to send certain headers. */ + while ((val = bbs_varlist_next(headers, &v, &key))) { + if (!proxy_header_forwardable(key)) { + continue; + } + /* XXX Do something specific for Connection / Keep-Alive headers, + * to handle persistence via proxy? */ + if (bbs_writef(client->wfd, "%s: %s\r\n", key, val) < 0) { + bbs_debug(2, "Failed to write header %s to proxy target\n", key); + } else { + sent++; + } + } + if (SWRITE(client->wfd, "\r\n") < 0) { /* EOH */ + return -1; + } + bbs_debug(3, "Forward proxied %d header%s\n", sent, ESS(sent)); + return 0; +} + +static int proxy_relay(struct http_session *http, struct bbs_tcp_client *client, char *restrict buf, size_t len) +{ + struct pollfd pfds[2]; + + memset(pfds, 0, sizeof(pfds)); + pfds[0].fd = client->rfd; + pfds[0].events = POLLIN; + pfds[1].fd = http->node->rfd; + pfds[1].events = POLLIN; + + if (!(http->req->method & HTTP_METHOD_CONNECT)) { + /* For HTTP requests with a body (not using CONNECT), + * part of the body may still be in the readline buffer, + * and we need to flush the buffer to the proxy server. + * Afterwards, we can just relay directly. */ + size_t bytes = (size_t) readline_bytes_available(http->rldata, 1); + if (bytes > 0) { + bbs_debug(3, "Flushing %lu-byte body via proxy\n", bytes); + if (bbs_write(client->wfd, http->buf, bytes) < 0) { + return -1; + } + } + } + + bbs_debug(3, "Proxying 2 file descriptors (%d/%d) on node %d\n", pfds[0].fd, pfds[1].fd, http->node->id); + for (;;) { + ssize_t res; + pfds[0].revents = pfds[1].revents = 0; + res = poll(pfds, 2, -1); + if (res <= 0) { + /* If either side disconnects, terminate the proxy session. */ + bbs_debug(3, "Proxy session terminating on node %d (poll returned %ld)\n", http->node->id, res); + break; + } + if (pfds[0].revents) { + /* Data from server towards proxy client */ + res = read(client->rfd, buf, len); + if (res <= 0) { + bbs_debug(3, "Proxy session terminating on node %d (read returned %ld: %s)\n", http->node->id, res, strerror(errno)); + break; + } + if (bbs_write(http->node->wfd, buf, (size_t) res) < 0) { + bbs_debug(3, "Proxy session terminating on node %d (write returned %ld)\n", http->node->id, res); + break; + } + } else if (pfds[1].revents) { + /* Data from proxy client towards server. */ + res = read(http->node->rfd, buf, len); + if (res <= 0) { + bbs_debug(3, "Proxy session terminating on node %d (read returned %ld: %s)\n", http->node->id, res, strerror(errno)); + break; + } + if (bbs_write(client->wfd, buf, (size_t) res) < 0) { + bbs_debug(3, "Proxy session terminating on node %d (write returned %ld)\n", http->node->id, res); + break; + } + } + } + + return -1; +} + +static enum http_response_code proxy_handler(struct http_session *http) +{ + char buf[BUFSIZ]; + struct bbs_url url; + struct bbs_tcp_client client; + char hostbuf[512]; + const char *host; + unsigned int port; + + port = http->req->hostport; + if (port == 0 && !(http->req->method & HTTP_METHOD_CONNECT)) { + /* No port was specified explicitly, use the default for the protocol (However, in CONNECT, it must be specified explicitly) */ + port = http->secure ? 443 : 80; + } + + /* Want the host without the port attached */ + if (http->req->method & HTTP_METHOD_CONNECT) { + bbs_strncpy_until(hostbuf, http->req->host, sizeof(hostbuf), ':'); /* Strip : */ + host = hostbuf; + } else { + host = http->req->host; + } + + /* Determine if the client is authorized to be proxying at all. */ + if (!proxy_client_authorized(http->node->ip, host)) { + bbs_debug(2, "Client %s is not authorized to proxy to %s:%u\n", http->node->ip, host, port); + return HTTP_UNAUTHORIZED; + } + + /* If we're tunneling, only allow connections to ports 80 and 443, to avoid proxying arbitrary protocols. */ + if (http->req->method & HTTP_METHOD_CONNECT && (port != 80 && port != 443)) { + bbs_debug(2, "Rejecting proxy request to nonstandard HTTP/HTTPS port %u\n", port); + return HTTP_FORBIDDEN; + } + + bbs_debug(5, "Client %s is authorized to proxy traffic to %s:%u\n", http->node->ip, host, port); + + /* Set up the TCP connection to the target. */ + memset(&url, 0, sizeof(url)); + memset(&client, 0, sizeof(client)); + url.host = host; + url.port = (int) port; + /* Never set up TLS (even for HTTPS over CONNECT), since the proxy client is responsible for doing that. + * We'll be oblivious to the juicy details of what they're saying to each other. */ + if (bbs_tcp_client_connect(&client, &url, 0, buf, sizeof(buf))) { + bbs_debug(3, "Could not get connect to proxy destination %s:%u\n", host, port); + return HTTP_SERVICE_UNAVAILABLE; + } + + if (http->req->method & HTTP_METHOD_CONNECT) { + /* For CONNECT, this is it, we can just send 200 OK now and then relay everything hereafter. */ + http->res->code = HTTP_OK; /* Hasn't been set using the higher-level functions, so set manually */ + http_send_response_status(http, HTTP_OK); + NODE_SWRITE(http->node, http->wfd, "\r\n"); /* CR LF to indicate end of headers */ + } else { + if (proxy_forward_headers(http, &client)) { + bbs_warning("Failed to forward headers to proxy destination %s:%u\n", host, port); + return HTTP_BAD_GATEWAY; + } + } + + proxy_relay(http, &client, buf, sizeof(buf)); + bbs_tcp_client_cleanup(&client); + return -2; /* Disconnect now */ +} + +static unsigned short int proxy_port = 0; /* Check default port at runtime. Can't use http_get_default_http_port without also depending on net_http first to set that */ + +static int load_config(void) +{ + struct bbs_config *cfg; + struct bbs_config_section *section = NULL; + + cfg = bbs_config_load("mod_http_proxy.conf", 0); + if (!cfg) { + return -1; /* Decline to load if there is no config. */ + } + + /* Read in the authorized proxy clients and authorized destinations */ + while ((section = bbs_config_walk(cfg, section))) { + struct bbs_keyval *keyval = NULL; + const char *key, *val; + + if (!strcmp(bbs_config_section_name(section), "clients")) { + while ((keyval = bbs_config_section_walk(section, keyval))) { + key = bbs_keyval_key(keyval); + val = bbs_keyval_val(keyval); + add_proxy_client(key, val); + } + } else if (strcasecmp(bbs_config_section_name(section), "general")) { + bbs_warning("Invalid section name '%s'\n", bbs_config_section_name(section)); + } + } + + return 0; +} + +static int unload_module(void) +{ + int res = http_unregister_proxy_handler(proxy_handler); + RWLIST_WRLOCK_REMOVE_ALL(&proxy_clients, entry, proxy_client_free); + return res; +} + +static int load_module(void) +{ + if (load_config() || http_register_proxy_handler(proxy_port, + HTTP_METHOD_HEAD | HTTP_METHOD_GET | HTTP_METHOD_POST | HTTP_METHOD_PUT | HTTP_METHOD_DELETE | HTTP_METHOD_CONNECT, + proxy_handler)) { + RWLIST_WRLOCK_REMOVE_ALL(&proxy_clients, entry, proxy_client_free); + return -1; + } + return 0; +} + +BBS_MODULE_INFO_DEPENDENT("HTTP Forward Proxy Server", "mod_http.so"); diff --git a/nets/net_smtp.c b/nets/net_smtp.c index ae25caf..7795111 100644 --- a/nets/net_smtp.c +++ b/nets/net_smtp.c @@ -237,6 +237,7 @@ int smtp_relay_authorized(const char *srcip, const char *hostname) if (bbs_ip_match_ipv4(srcip, h->source)) { /* Just needs to be allowed by one matching entry */ if (stringlist_contains(&h->domains, hostname)) { + RWLIST_UNLOCK(&authorized_relays); return 1; } }