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
internal_readsock_open(client_conn_ctx * cctx,const uschar * sspec,int timeout,BOOL do_tls,uschar ** errmsg)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 *
readsock_open(const uschar * filename,uschar ** errmsg)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
readsock_find(void * handle,const uschar * filename,const uschar * keystring,int length,uschar ** result,uschar ** errmsg,uint * do_cache,const uschar * opts)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
readsock_close(void * handle)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 */
326