summaryrefslogtreecommitdiffstats
path: root/src/lookups/readsock.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lookups/readsock.c')
-rw-r--r--src/lookups/readsock.c341
1 files changed, 341 insertions, 0 deletions
diff --git a/src/lookups/readsock.c b/src/lookups/readsock.c
new file mode 100644
index 0000000..22179c9
--- /dev/null
+++ b/src/lookups/readsock.c
@@ -0,0 +1,341 @@
+/*************************************************
+* Exim - an Internet mail transport agent *
+*************************************************/
+
+/* Copyright (c) The Exim Maintainers 2021 - 2022 */
+/* Copyright (c) Jeremy Harris 2020 */
+/* See the file NOTICE for conditions of use and distribution. */
+
+#include "../exim.h"
+#include "lf_functions.h"
+
+
+static int
+internal_readsock_open(client_conn_ctx * cctx, const uschar * sspec,
+ int timeout, BOOL do_tls, uschar ** errmsg)
+{
+const uschar * server_name;
+host_item host;
+
+if (Ustrncmp(sspec, "inet:", 5) == 0)
+ {
+ int port;
+ uschar * port_name;
+
+ DEBUG(D_lookup)
+ debug_printf_indent(" new inet socket needed for readsocket\n");
+
+ server_name = sspec + 5;
+ port_name = Ustrrchr(server_name, ':');
+
+ /* Sort out the port */
+
+ if (!port_name)
+ {
+ /* expand_string_message results in an EXPAND_FAIL, from our
+ only caller. Lack of it gets a SOCK_FAIL; we feed back via errmsg
+ for that, which gets copied to search_error_message. */
+
+ expand_string_message =
+ string_sprintf("missing port for readsocket %s", sspec);
+ return FAIL;
+ }
+ *port_name++ = 0; /* Terminate server name */
+
+ if (isdigit(*port_name))
+ {
+ uschar *end;
+ port = Ustrtol(port_name, &end, 0);
+ if (end != port_name + Ustrlen(port_name))
+ {
+ expand_string_message =
+ string_sprintf("invalid port number %s", port_name);
+ return FAIL;
+ }
+ }
+ else
+ {
+ struct servent *service_info = getservbyname(CS port_name, "tcp");
+ if (!service_info)
+ {
+ expand_string_message = string_sprintf("unknown port \"%s\"",
+ port_name);
+ return FAIL;
+ }
+ port = ntohs(service_info->s_port);
+ }
+
+ /* Not having the request-string here in the open routine means
+ that we cannot do TFO; a pity */
+
+ cctx->sock = ip_connectedsocket(SOCK_STREAM, server_name, port, port,
+ timeout, &host, errmsg, NULL);
+ callout_address = NULL;
+ if (cctx->sock < 0)
+ return FAIL;
+ }
+
+else
+ {
+ struct sockaddr_un sockun; /* don't call this "sun" ! */
+ int rc;
+
+ DEBUG(D_lookup)
+ debug_printf_indent(" new unix socket needed for readsocket\n");
+
+ if ((cctx->sock = socket(PF_UNIX, SOCK_STREAM, 0)) == -1)
+ {
+ *errmsg = string_sprintf("failed to create socket: %s", strerror(errno));
+ return FAIL;
+ }
+
+ sockun.sun_family = AF_UNIX;
+ sprintf(sockun.sun_path, "%.*s", (int)(sizeof(sockun.sun_path)-1),
+ sspec);
+ server_name = US sockun.sun_path;
+
+ sigalrm_seen = FALSE;
+ ALARM(timeout);
+ rc = connect(cctx->sock, (struct sockaddr *) &sockun, sizeof(sockun));
+ ALARM_CLR(0);
+ if (sigalrm_seen)
+ {
+ *errmsg = US "socket connect timed out";
+ goto bad;
+ }
+ if (rc < 0)
+ {
+ *errmsg = string_sprintf("failed to connect to socket "
+ "%s: %s", sspec, strerror(errno));
+ goto bad;
+ }
+ host.name = server_name;
+ host.address = US"";
+ }
+
+#ifndef DISABLE_TLS
+if (do_tls)
+ {
+ union sockaddr_46 interface_sock;
+ EXIM_SOCKLEN_T size = sizeof(interface_sock);
+ smtp_connect_args conn_args = {.host = &host };
+ tls_support tls_dummy = { .sni = NULL };
+ uschar * errstr;
+
+ if (getsockname(cctx->sock, (struct sockaddr *) &interface_sock, &size) == 0)
+ conn_args.sending_ip_address = host_ntoa(-1, &interface_sock, NULL, NULL);
+ else
+ {
+ *errmsg = string_sprintf("getsockname failed: %s", strerror(errno));
+ goto bad;
+ }
+
+ if (!tls_client_start(cctx, &conn_args, NULL, &tls_dummy, &errstr))
+ {
+ *errmsg = string_sprintf("TLS connect failed: %s", errstr);
+ goto bad;
+ }
+ }
+#endif
+
+DEBUG(D_expand|D_lookup) debug_printf_indent(" connected to socket %s\n", sspec);
+return OK;
+
+bad:
+ close(cctx->sock);
+ return FAIL;
+}
+
+/* All use of allocations will be done against the POOL_SEARCH memory,
+which is freed once by search_tidyup(). */
+
+/*************************************************
+* Open entry point *
+*************************************************/
+
+/* See local README for interface description */
+/* We just create a placeholder record with a closed socket, so
+that connection cacheing at the framework layer works. */
+
+static void *
+readsock_open(const uschar * filename, uschar ** errmsg)
+{
+client_conn_ctx * cctx = store_get(sizeof(*cctx), GET_UNTAINTED);
+cctx->sock = -1;
+cctx->tls_ctx = NULL;
+DEBUG(D_lookup) debug_printf_indent("readsock: allocated context\n");
+return cctx;
+}
+
+
+
+
+
+/*************************************************
+* Find entry point for lsearch *
+*************************************************/
+
+/* See local README for interface description */
+
+static int
+readsock_find(void * handle, const uschar * filename, const uschar * keystring,
+ int length, uschar ** result, uschar ** errmsg, uint * do_cache,
+ const uschar * opts)
+{
+client_conn_ctx * cctx = handle;
+int sep = ',';
+struct {
+ BOOL do_shutdown:1;
+ BOOL do_tls:1;
+ BOOL cache:1;
+} lf = {.do_shutdown = TRUE};
+uschar * eol = NULL;
+int timeout = 5;
+gstring * yield;
+int ret = DEFER;
+
+DEBUG(D_lookup)
+ debug_printf_indent("readsock: file=\"%s\" key=\"%s\" len=%d opts=\"%s\"\n",
+ filename, keystring, length, opts);
+
+/* Parse options */
+
+if (opts) for (uschar * s; s = string_nextinlist(&opts, &sep, NULL, 0); )
+ if (Ustrncmp(s, "timeout=", 8) == 0)
+ timeout = readconf_readtime(s + 8, 0, FALSE);
+ else if (Ustrncmp(s, "shutdown=", 9) == 0)
+ lf.do_shutdown = Ustrcmp(s + 9, "no") != 0;
+#ifndef DISABLE_TLS
+ else if (Ustrncmp(s, "tls=", 4) == 0 && Ustrcmp(s + 4, US"no") != 0)
+ lf.do_tls = TRUE;
+#endif
+ else if (Ustrncmp(s, "eol=", 4) == 0)
+ eol = string_unprinting(s + 4);
+ else if (Ustrcmp(s, "cache=yes") == 0)
+ lf.cache = TRUE;
+ else if (Ustrcmp(s, "send=no") == 0)
+ length = 0;
+
+if (!filename) return FAIL; /* Server spec is required */
+
+/* Open the socket, if not cached */
+
+if (cctx->sock == -1)
+ if (internal_readsock_open(cctx, filename, timeout, lf.do_tls, errmsg) != OK)
+ return ret;
+
+testharness_pause_ms(100); /* Allow sequencing of test actions */
+
+/* Write the request string, if not empty or already done */
+
+if (length)
+ {
+ if ((
+#ifndef DISABLE_TLS
+ cctx->tls_ctx ? tls_write(cctx->tls_ctx, keystring, length, FALSE) :
+#endif
+ write(cctx->sock, keystring, length)) != length)
+ {
+ *errmsg = string_sprintf("request write to socket "
+ "failed: %s", strerror(errno));
+ goto out;
+ }
+ }
+
+/* Shut down the sending side of the socket. This helps some servers to
+recognise that it is their turn to do some work. Just in case some
+system doesn't have this function, make it conditional. */
+
+#ifdef SHUT_WR
+if (!cctx->tls_ctx && lf.do_shutdown)
+ shutdown(cctx->sock, SHUT_WR);
+#endif
+
+testharness_pause_ms(100);
+
+/* Now we need to read from the socket, under a timeout. The function
+that reads a file can be used. If we're using a stdio buffered read,
+and might need later write ops on the socket, the stdio must be in
+writable mode or the underlying socket goes non-writable. */
+
+sigalrm_seen = FALSE;
+#ifdef DISABLE_TLS
+if (TRUE)
+#else
+if (!cctx->tls_ctx)
+#endif
+ {
+ FILE * fp = fdopen(cctx->sock, "rb");
+ if (!fp)
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC, "readsock fdopen: %s\n", strerror(errno));
+ goto out;
+ }
+ ALARM(timeout);
+ yield = cat_file(fp, NULL, eol);
+ }
+else
+ {
+ ALARM(timeout);
+ yield = cat_file_tls(cctx->tls_ctx, NULL, eol);
+ }
+
+ALARM_CLR(0);
+
+if (sigalrm_seen)
+ { *errmsg = US "socket read timed out"; goto out; }
+
+*result = yield ? string_from_gstring(yield) : US"";
+ret = OK;
+if (!lf.cache) *do_cache = 0;
+
+out:
+
+(void) close(cctx->sock);
+cctx->sock = -1;
+return ret;
+}
+
+
+
+/*************************************************
+* Close entry point *
+*************************************************/
+
+/* See local README for interface description */
+
+static void
+readsock_close(void * handle)
+{
+client_conn_ctx * cctx = handle;
+if (cctx->sock < 0) return;
+#ifndef DISABLE_TLS
+if (cctx->tls_ctx) tls_close(cctx->tls_ctx, TRUE);
+#endif
+close(cctx->sock);
+cctx->sock = -1;
+}
+
+
+
+static lookup_info readsock_lookup_info = {
+ .name = US"readsock", /* lookup name */
+ .type = lookup_querystyle,
+ .open = readsock_open, /* open function */
+ .check = NULL,
+ .find = readsock_find, /* find function */
+ .close = readsock_close,
+ .tidy = NULL,
+ .quote = NULL, /* no quoting function */
+ .version_report = NULL
+};
+
+
+#ifdef DYNLOOKUP
+#define readsock_lookup_module_info _lookup_module_info
+#endif
+
+static lookup_info *_lookup_list[] = { &readsock_lookup_info };
+lookup_module_info readsock_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 };
+
+/* End of lookups/readsock.c */