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