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