Expansions: Fix ${readsocket } to do nicer TLS close
[exim.git] / src / src / lookups / readsock.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
5 /* Copyright (c) The Exim Maintainers 2021 - 2022 */
6 /* Copyright (c) Jeremy Harris 2020 */
7 /* See the file NOTICE for conditions of use and distribution. */
8 /* SPDX-License-Identifier: GPL-2.0-or-later */
9
10 #include "../exim.h"
11 #include "lf_functions.h"
12
13
14 static int
15 internal_readsock_open(client_conn_ctx * cctx, const uschar * sspec,
16   int timeout, uschar * do_tls, uschar ** errmsg)
17 {
18 const uschar * server_name;
19 host_item host;
20
21 if (Ustrncmp(sspec, "inet:", 5) == 0)
22   {
23   int port;
24   uschar * port_name;
25
26   DEBUG(D_lookup)
27     debug_printf_indent("  new inet socket needed for readsocket\n");
28
29   server_name = sspec + 5;
30   port_name = Ustrrchr(server_name, ':');
31
32   /* Sort out the port */
33
34   if (!port_name)
35     {
36     /* expand_string_message results in an EXPAND_FAIL, from our
37     only caller.  Lack of it gets a SOCK_FAIL; we feed back via errmsg
38     for that, which gets copied to search_error_message. */
39
40     expand_string_message =
41       string_sprintf("missing port for readsocket %s", sspec);
42     return FAIL;
43     }
44   *port_name++ = 0;           /* Terminate server name */
45
46   if (isdigit(*port_name))
47     {
48     uschar *end;
49     port = Ustrtol(port_name, &end, 0);
50     if (end != port_name + Ustrlen(port_name))
51       {
52       expand_string_message =
53         string_sprintf("invalid port number %s", port_name);
54       return FAIL;
55       }
56     }
57   else
58     {
59     struct servent *service_info = getservbyname(CS port_name, "tcp");
60     if (!service_info)
61       {
62       expand_string_message = string_sprintf("unknown port \"%s\"",
63         port_name);
64       return FAIL;
65       }
66     port = ntohs(service_info->s_port);
67     }
68
69   /* Not having the request-string here in the open routine means
70   that we cannot do TFO; a pity */
71
72   cctx->sock = ip_connectedsocket(SOCK_STREAM, server_name, port, port,
73           timeout, &host, errmsg, NULL);
74   callout_address = NULL;
75   if (cctx->sock < 0)
76     return FAIL;
77   }
78
79 else
80   {
81   struct sockaddr_un sockun;         /* don't call this "sun" ! */
82   int rc;
83
84   DEBUG(D_lookup)
85     debug_printf_indent("  new unix socket needed for readsocket\n");
86
87   if ((cctx->sock = socket(PF_UNIX, SOCK_STREAM, 0)) == -1)
88     {
89     *errmsg = string_sprintf("failed to create socket: %s", strerror(errno));
90     return FAIL;
91     }
92
93   sockun.sun_family = AF_UNIX;
94   sprintf(sockun.sun_path, "%.*s", (int)(sizeof(sockun.sun_path)-1),
95     sspec);
96   server_name = US sockun.sun_path;
97
98   sigalrm_seen = FALSE;
99   ALARM(timeout);
100   rc = connect(cctx->sock, (struct sockaddr *) &sockun, sizeof(sockun));
101   ALARM_CLR(0);
102   if (sigalrm_seen)
103     {
104     *errmsg = US "socket connect timed out";
105     goto bad;
106     }
107   if (rc < 0)
108     {
109     *errmsg = string_sprintf("failed to connect to socket "
110       "%s: %s", sspec, strerror(errno));
111     goto bad;
112     }
113   host.name = server_name;
114   host.address = US"";
115   }
116
117 #ifndef DISABLE_TLS
118 if (do_tls)
119   if (!tls_client_adjunct_start(&host, cctx, do_tls, errmsg))
120     goto bad;
121 #endif
122
123 DEBUG(D_expand|D_lookup) debug_printf_indent("  connected to socket %s\n", sspec);
124 return OK;
125
126 bad:
127   close(cctx->sock);
128   return FAIL;
129 }
130
131 /* All use of allocations will be done against the POOL_SEARCH memory,
132 which is freed once by search_tidyup(). */
133
134 /*************************************************
135 *              Open entry point                  *
136 *************************************************/
137
138 /* See local README for interface description */
139 /* We just create a placeholder record with a closed socket, so
140 that connection cacheing at the framework layer works. */
141
142 static void *
143 readsock_open(const uschar * filename, uschar ** errmsg)
144 {
145 client_conn_ctx * cctx = store_get(sizeof(*cctx), GET_UNTAINTED);
146 cctx->sock = -1;
147 cctx->tls_ctx = NULL;
148 DEBUG(D_lookup) debug_printf_indent("readsock: allocated context\n");
149 return cctx;
150 }
151
152
153
154
155
156 /*************************************************
157 *         Find entry point for lsearch           *
158 *************************************************/
159
160 /* See local README for interface description */
161
162 static int
163 readsock_find(void * handle, const uschar * filename, const uschar * keystring,
164   int length, uschar ** result, uschar ** errmsg, uint * do_cache,
165   const uschar * opts)
166 {
167 client_conn_ctx * cctx = handle;
168 int sep = ',';
169 struct {
170         BOOL do_shutdown:1;
171         BOOL cache:1;
172         uschar * do_tls;        /* NULL, empty-string, or SNI */
173 } lf = {.do_shutdown = TRUE};
174 uschar * eol = NULL;
175 int timeout = 5;
176 gstring * yield;
177 int ret = DEFER;
178
179 DEBUG(D_lookup)
180   debug_printf_indent("readsock: file=\"%s\" key=\"%s\" len=%d opts=\"%s\"\n",
181     filename, keystring, length, opts);
182
183 /* Parse options */
184
185 if (opts) for (uschar * s; s = string_nextinlist(&opts, &sep, NULL, 0); )
186   if (Ustrncmp(s, "timeout=", 8) == 0)
187     timeout = readconf_readtime(s + 8, 0, FALSE);
188   else if (Ustrncmp(s, "shutdown=", 9) == 0)
189     lf.do_shutdown = Ustrcmp(s + 9, "no") != 0;
190 #ifndef DISABLE_TLS
191   else if (Ustrncmp(s, "tls=", 4) == 0 && Ustrcmp(s + 4, US"no") != 0 && !lf.do_tls)
192     lf.do_tls = US"";
193   else if (Ustrncmp(s, "sni=", 4) == 0)
194     lf.do_tls = s + 4;
195 #endif
196   else if (Ustrncmp(s, "eol=", 4) == 0)
197     eol = string_unprinting(s + 4);
198   else if (Ustrcmp(s, "cache=yes") == 0)
199     lf.cache = TRUE;
200   else if (Ustrcmp(s, "send=no") == 0)
201     length = 0;
202
203 if (!filename) return FAIL;     /* Server spec is required */
204
205 /* Open the socket, if not cached */
206
207 if (cctx->sock == -1)
208   if (internal_readsock_open(cctx, filename, timeout, lf.do_tls, errmsg) != OK)
209     return ret;
210
211 testharness_pause_ms(100);      /* Allow sequencing of test actions */
212
213 /* Write the request string, if not empty or already done */
214
215 if (length)
216   {
217   if ((
218 #ifndef DISABLE_TLS
219       cctx->tls_ctx ? tls_write(cctx->tls_ctx, keystring, length, FALSE) :
220 #endif
221                       write(cctx->sock, keystring, length)) != length)
222     {
223     *errmsg = string_sprintf("request write to socket "
224       "failed: %s", strerror(errno));
225     goto out;
226     }
227   }
228
229 /* Shut down the sending side of the socket. This helps some servers to
230 recognise that it is their turn to do some work. Just in case some
231 system doesn't have this function, make it conditional. */
232
233 #ifdef SHUT_WR
234 if (!cctx->tls_ctx && lf.do_shutdown)
235   shutdown(cctx->sock, SHUT_WR);
236 #endif
237
238 testharness_pause_ms(100);
239
240 /* Now we need to read from the socket, under a timeout. The function
241 that reads a file can be used.  If we're using a stdio buffered read,
242 and might need later write ops on the socket, the stdio must be in
243 writable mode or the underlying socket goes non-writable. */
244
245 sigalrm_seen = FALSE;
246 #ifdef DISABLE_TLS
247 if (TRUE)
248 #else
249 if (!cctx->tls_ctx)
250 #endif
251   {
252   FILE * fp = fdopen(cctx->sock, "rb");
253   if (!fp)
254     {
255     log_write(0, LOG_MAIN|LOG_PANIC, "readsock fdopen: %s\n", strerror(errno));
256     goto out;
257     }
258   ALARM(timeout);
259   yield = cat_file(fp, NULL, eol);
260   }
261 else
262   {
263   ALARM(timeout);
264   yield = cat_file_tls(cctx->tls_ctx, NULL, eol);
265   }
266
267 ALARM_CLR(0);
268
269 if (sigalrm_seen)
270   { *errmsg = US "socket read timed out"; goto out; }
271
272 *result = yield ? string_from_gstring(yield) : US"";
273 ret = OK;
274 if (!lf.cache) *do_cache = 0;
275
276 out:
277
278 #ifndef DISABLE_TLS
279 if (cctx->tls_ctx) tls_close(cctx->tls_ctx, TLS_SHUTDOWN_NOWAIT);
280 #endif
281
282 (void) close(cctx->sock);
283 cctx->sock = -1;
284 return ret;
285 }
286
287
288
289 /*************************************************
290 *              Close entry point                 *
291 *************************************************/
292
293 /* See local README for interface description */
294
295 static void
296 readsock_close(void * handle)
297 {
298 client_conn_ctx * cctx = handle;
299 if (cctx->sock < 0) return;
300 #ifndef DISABLE_TLS
301 if (cctx->tls_ctx) tls_close(cctx->tls_ctx, TLS_SHUTDOWN_NOWAIT);
302 #endif
303 close(cctx->sock);
304 cctx->sock = -1;
305 }
306
307
308
309 static lookup_info readsock_lookup_info = {
310   .name = US"readsock",                 /* lookup name */
311   .type = lookup_querystyle,
312   .open = readsock_open,                /* open function */
313   .check = NULL,
314   .find = readsock_find,                /* find function */
315   .close = readsock_close,
316   .tidy = NULL,
317   .quote = NULL,                        /* no quoting function */
318   .version_report = NULL
319 };
320
321
322 #ifdef DYNLOOKUP
323 #define readsock_lookup_module_info _lookup_module_info
324 #endif
325
326 static lookup_info *_lookup_list[] = { &readsock_lookup_info };
327 lookup_module_info readsock_lookup_module_info = { LOOKUP_MODULE_INFO_MAGIC, _lookup_list, 1 };
328
329 /* End of lookups/readsock.c */