f367df1ad400679855689fe6a2db77c5a35f5314
[exim.git] / src / src / miscmods / dkim_transport.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
5 /* Copyright (c) The Exim Maintainers 2022 - 2024 */
6 /* Copyright (c) University of Cambridge 1995 - 2018 */
7 /* See the file NOTICE for conditions of use and distribution. */
8 /* SPDX-License-Identifier: GPL-2.0-or-later */
9
10 /* Transport shim for dkim signing */
11
12
13 #include "../exim.h"
14 #include "arc_api.h"
15
16 #ifndef DISABLE_DKIM    /* rest of file */
17
18
19 static BOOL
20 dkt_sign_fail(struct ob_dkim * dkim, int * errp)
21 {
22 GET_OPTION("dkim_strict");
23 if (dkim->dkim_strict)
24   {
25   uschar * dkim_strict_result = expand_string(dkim->dkim_strict);
26
27   if (dkim_strict_result)
28     if (  strcmpic(dkim_strict_result, US"1") == 0
29        || strcmpic(dkim_strict_result, US"true") == 0)
30       {
31       /* Set errno to something halfway meaningful */
32       *errp = EACCES;
33       log_write(0, LOG_MAIN, "DKIM: message could not be signed,"
34         " and dkim_strict is set. Deferring message delivery.");
35       return FALSE;
36       }
37   }
38 return TRUE;
39 }
40
41 /* Send the file at in_fd down the output fd */
42
43 static BOOL
44 dkt_send_file(int out_fd, int in_fd, off_t off
45 #ifdef OS_SENDFILE
46   , size_t size
47 #endif
48   )
49 {
50 #ifdef OS_SENDFILE
51 DEBUG(D_transport) debug_printf("send file fd=%d size=%u\n", out_fd, (unsigned)(size - off));
52 #else
53 DEBUG(D_transport) debug_printf("send file fd=%d\n", out_fd);
54 #endif
55
56 /*XXX should implement timeout, like transport_write_block_fd() ? */
57
58 #ifdef OS_SENDFILE
59 /* We can use sendfile() to shove the file contents
60    to the socket. However only if we don't use TLS,
61    as then there's another layer of indirection
62    before the data finally hits the socket. */
63 if (tls_out.active.sock != out_fd)
64   {
65   ssize_t copied = 0;
66
67   while(copied >= 0 && off < size)
68     copied = os_sendfile(out_fd, in_fd, &off, size - off);
69   if (copied < 0)
70     return FALSE;
71   }
72 else
73
74 #endif
75
76   {
77   int sread, wwritten;
78
79   /* Rewind file */
80   if (lseek(in_fd, off, SEEK_SET) < 0) return FALSE;
81
82   /* Send file down the original fd */
83   while((sread = read(in_fd, deliver_out_buffer, DELIVER_OUT_BUFFER_SIZE)) > 0)
84     {
85     uschar * p = deliver_out_buffer;
86     /* write the chunk */
87
88     while (sread)
89       {
90 #ifndef DISABLE_TLS
91       wwritten = tls_out.active.sock == out_fd
92         ? tls_write(tls_out.active.tls_ctx, p, sread, FALSE)
93         : write(out_fd, CS p, sread);
94 #else
95       wwritten = write(out_fd, CS p, sread);
96 #endif
97       if (wwritten == -1)
98         return FALSE;
99       p += wwritten;
100       sread -= wwritten;
101       }
102     }
103
104   if (sread == -1)
105     return FALSE;
106   }
107
108 return TRUE;
109 }
110
111
112
113 /* Prepend ARC-signing headers to given set of headers
114
115 Arguments:
116   signspec      Three-element colon-sep list: identity, selector, privkey.
117                 Optional fourth element: comma-sep list of options.
118                 Already expanded
119   sigheaders    Any signature headers already generated, eg. by DKIM, or NULL
120   errstr        Error string
121
122 Return value
123   Set of headers to prepend to the message, including the supplied sigheaders
124   but not the plainheaders.
125 */
126
127 static gstring *
128 dkt_arc_sign(const uschar * signspec, gstring * sigheaders, uschar ** errstr_p)
129 {
130 const misc_module_info * mi = misc_mod_findonly(US"arc");
131 typedef gstring * (*fn_t)(const uschar *, gstring *, uschar **);
132 if (mi)
133   return (((fn_t *) mi->functions)[ARC_SIGN]) (signspec, sigheaders, errstr_p);
134 *errstr_p = US"failed to find arc module";
135 return NULL;
136 }
137
138
139
140 /* This function is a wrapper around transport_write_message().
141    It is only called from the smtp transport if DKIM or Domainkeys support
142    is active and no transport filter is to be used.
143
144 Arguments:
145   As for transport_write_message() in transort.c, with additional arguments
146   for DKIM.
147
148 Returns:       TRUE on success; FALSE (with errno) for any failure
149 */
150
151 static BOOL
152 dkt_direct(transport_ctx * tctx, struct ob_dkim * dkim,
153   const uschar ** err)
154 {
155 int save_fd = tctx->u.fd;
156 int save_options = tctx->options;
157 BOOL save_wireformat = f.spool_file_wireformat;
158 uschar * hdrs;
159 gstring * dkim_signature;
160 int hsize;
161 const uschar * errstr;
162 BOOL rc;
163
164 DEBUG(D_transport) debug_printf("dkim signing direct-mode\n");
165
166 /* Get headers in string for signing and transmission.  Do CRLF
167 and dotstuffing (but no body nor dot-termination) */
168
169 tctx->u.msg = NULL;
170 tctx->options = tctx->options & ~(topt_end_dot | topt_use_bdat)
171   | topt_output_string | topt_no_body;
172
173 rc = transport_write_message(tctx, 0);
174 hdrs = string_from_gstring(tctx->u.msg);
175 hsize = tctx->u.msg->ptr;
176
177 tctx->u.fd = save_fd;
178 tctx->options = save_options;
179 if (!rc) return FALSE;
180
181 /* Get signatures for headers plus spool data file */
182
183 /* The dotstuffed status of the datafile depends on whether it was stored
184 in wireformat. */
185
186 dkim->dot_stuffed = f.spool_file_wireformat;
187 if (!(dkim_signature = dkim_exim_sign(deliver_datafile,
188               spool_data_start_offset(message_id), hdrs, dkim, &errstr)))
189   if (!(rc = dkt_sign_fail(dkim, &errno)))
190     {
191     *err = errstr;
192     return FALSE;
193     }
194
195 #ifdef EXPERIMENTAL_ARC
196 if (dkim->arc_signspec)                 /* Prepend ARC headers */
197   {
198   uschar * e = NULL;
199   if (!(dkim_signature = dkt_arc_sign(dkim->arc_signspec, dkim_signature, &e)))
200     {
201     *err = e;
202     return FALSE;
203     }
204   }
205 #endif
206
207 /* Write the signature and headers into the deliver-out-buffer.  This should
208 mean they go out in the same packet as the MAIL, RCPT and (first) BDAT commands
209 (transport_write_message() sizes the BDAT for the buffered amount) - for short
210 messages, the BDAT LAST command.  We want no dotstuffing expansion here, it
211 having already been done - but we have to say we want CRLF output format, and
212 temporarily set the marker for possible already-CRLF input. */
213
214 tctx->options &= ~topt_escape_headers;
215 f.spool_file_wireformat = TRUE;
216 transport_write_reset(0);
217 if (  (  dkim_signature
218       && dkim_signature->ptr > 0
219       && !write_chunk(tctx, dkim_signature->s, dkim_signature->ptr)
220       )
221    || !write_chunk(tctx, hdrs, hsize)
222    )
223   return FALSE;
224
225 f.spool_file_wireformat = save_wireformat;
226 tctx->options = save_options | topt_no_headers | topt_continuation;
227
228 if (!(transport_write_message(tctx, 0)))
229   return FALSE;
230
231 tctx->options = save_options;
232 return TRUE;
233 }
234
235
236 /* This function is a wrapper around transport_write_message().
237    It is only called from the smtp transport if DKIM or Domainkeys support
238    is active and a transport filter is to be used.  The function sets up a
239    replacement fd into a -K file, then calls the normal function. This way, the
240    exact bits that exim would have put "on the wire" will end up in the file
241    (except for TLS encapsulation, which is the very very last thing). When we
242    are done signing the file, send the signed message down the original fd (or
243    TLS fd).
244
245 Arguments:
246   As for transport_write_message() in transort.c, with additional arguments
247   for DKIM.
248
249 Returns:       TRUE on success; FALSE (with errno) for any failure
250 */
251
252 static BOOL
253 dkt_via_kfile(transport_ctx * tctx, struct ob_dkim * dkim, const uschar ** err)
254 {
255 int dkim_fd;
256 int save_errno = 0;
257 BOOL rc;
258 uschar * dkim_spool_name;
259 gstring * dkim_signature;
260 int options, dlen;
261 off_t k_file_size;
262 const uschar * errstr;
263
264 dkim_spool_name = spool_fname(US"input", message_subdir, message_id,
265                     string_sprintf("-%d-K", (int)getpid()));
266
267 DEBUG(D_transport) debug_printf("dkim signing via file %s\n", dkim_spool_name);
268
269 if ((dkim_fd = Uopen(dkim_spool_name, O_RDWR|O_CREAT|O_TRUNC, SPOOL_MODE)) < 0)
270   {
271   /* Can't create spool file. Ugh. */
272   rc = FALSE;
273   save_errno = errno;
274   *err = string_sprintf("dkim spoolfile create: %s", strerror(errno));
275   goto CLEANUP;
276   }
277
278 /* Call transport utility function to write the -K file; does the CRLF expansion
279 (but, in the CHUNKING case, neither dot-stuffing nor dot-termination). */
280
281   {
282   int save_fd = tctx->u.fd;
283   tctx->u.fd = dkim_fd;
284   options = tctx->options;
285   tctx->options &= ~topt_use_bdat;
286
287   rc = transport_write_message(tctx, 0);
288
289   tctx->u.fd = save_fd;
290   tctx->options = options;
291   }
292
293 /* Save error state. We must clean up before returning. */
294 if (!rc)
295   {
296   save_errno = errno;
297   goto CLEANUP;
298   }
299
300 /* Feed the file to the goats^W DKIM lib.  At this point the dotstuffed
301 status of the file depends on the output of transport_write_message() just
302 above, which should be the result of the end_dot flag in tctx->options. */
303
304 dkim->dot_stuffed = !!(options & topt_end_dot);
305 if (!(dkim_signature = dkim_exim_sign(dkim_fd, 0, NULL, dkim, &errstr)))
306   {
307   dlen = 0;
308   if (!(rc = dkt_sign_fail(dkim, &save_errno)))
309     {
310     *err = errstr;
311     goto CLEANUP;
312     }
313   }
314 else
315   dlen = dkim_signature->ptr;
316
317 #ifdef EXPERIMENTAL_ARC
318 if (dkim->arc_signspec)                         /* Prepend ARC headers */
319   {
320   if (!(dkim_signature = dkt_arc_sign(dkim->arc_signspec, dkim_signature,
321                                       USS err)))
322     goto CLEANUP;
323   dlen = dkim_signature->ptr;
324   }
325 #endif
326
327 #ifndef OS_SENDFILE
328 if (options & topt_use_bdat)
329 #endif
330   if ((k_file_size = lseek(dkim_fd, 0, SEEK_END)) < 0)
331     {
332     *err = string_sprintf("dkim spoolfile seek: %s", strerror(errno));
333     goto CLEANUP;
334     }
335
336 if (options & topt_use_bdat)
337   {
338   /* On big messages output a precursor chunk to get any pipelined
339   MAIL & RCPT commands flushed, then reap the responses so we can
340   error out on RCPT rejects before sending megabytes. */
341
342   if (  dlen + k_file_size > DELIVER_OUT_BUFFER_SIZE
343      && dlen > 0)
344     {
345     if (  tctx->chunk_cb(tctx, dlen, 0) != OK
346        || !transport_write_block(tctx,
347                     dkim_signature->s, dlen, FALSE)
348        || tctx->chunk_cb(tctx, 0, tc_reap_prev) != OK
349        )
350       goto err;
351     dlen = 0;
352     }
353
354   /* Send the BDAT command for the entire message, as a single LAST-marked
355   chunk. */
356
357   if (tctx->chunk_cb(tctx, dlen + k_file_size, tc_chunk_last) != OK)
358     goto err;
359   }
360
361 if(dlen > 0 && !transport_write_block(tctx, dkim_signature->s, dlen, TRUE))
362   goto err;
363
364 if (!dkt_send_file(tctx->u.fd, dkim_fd, 0
365 #ifdef OS_SENDFILE
366   , k_file_size
367 #endif
368   ))
369   {
370   save_errno = errno;
371   rc = FALSE;
372   }
373
374 CLEANUP:
375   /* unlink -K file */
376   if (dkim_fd >= 0) (void)close(dkim_fd);
377   Uunlink(dkim_spool_name);
378   errno = save_errno;
379   return rc;
380
381 err:
382   save_errno = errno;
383   rc = FALSE;
384   goto CLEANUP;
385 }
386
387
388
389 /***************************************************************************************************
390 *    External interface to write the message, while signing it with DKIM and/or Domainkeys         *
391 ***************************************************************************************************/
392
393 /* This function is a wrapper around transport_write_message().
394    It is only called from the smtp transport if DKIM or Domainkeys support
395    is compiled in.
396
397 Arguments:
398   As for transport_write_message() in transort.c, with additional arguments
399   for DKIM.
400
401 Returns:       TRUE on success; FALSE (with errno) for any failure
402 */
403
404 BOOL
405 dkim_transport_write_message(transport_ctx * tctx,
406   struct ob_dkim * dkim, const uschar ** err)
407 {
408 BOOL yield;
409
410 /* If we can't sign, just call the original function. */
411
412 if (  !(dkim->dkim_private_key && dkim->dkim_domain && dkim->dkim_selector)
413    && !dkim->force_bodyhash)
414   return transport_write_message(tctx, 0);
415
416 /* If there is no filter command set up, construct the message and calculate
417 a dkim signature of it, send the signature and a reconstructed message. This
418 avoids using a temporary file. */
419
420 if (  !transport_filter_argv
421    || !*transport_filter_argv
422    || !**transport_filter_argv
423    )
424   yield = dkt_direct(tctx, dkim, err);
425
426 else
427   /* Use the transport path to write a file, calculate a dkim signature,
428   send the signature and then send the file. */
429
430   yield = dkt_via_kfile(tctx, dkim, err);
431
432 tctx->addr->dkim_used = string_from_gstring(dkim_signing_record);
433 return yield;
434 }
435
436 #endif  /* whole file */
437
438 /* vi: aw ai sw=2
439 */
440 /* End of dkim_transport.c */