0500da2bee57e14210e0413cc9e5ca2139f522f8
[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
113 /* This function is a wrapper around transport_write_message().
114    It is only called from the smtp transport if DKIM or Domainkeys support
115    is active and no transport filter is to be used.
116
117 Arguments:
118   As for transport_write_message() in transort.c, with additional arguments
119   for DKIM.
120
121 Returns:       TRUE on success; FALSE (with errno) for any failure
122 */
123
124 static BOOL
125 dkt_direct(transport_ctx * tctx, struct ob_dkim * dkim,
126   const uschar ** err)
127 {
128 int save_fd = tctx->u.fd;
129 int save_options = tctx->options;
130 BOOL save_wireformat = f.spool_file_wireformat;
131 uschar * hdrs;
132 gstring * dkim_signature;
133 int hsize;
134 const uschar * errstr;
135 BOOL rc;
136
137 DEBUG(D_transport) debug_printf("dkim signing direct-mode\n");
138
139 /* Get headers in string for signing and transmission.  Do CRLF
140 and dotstuffing (but no body nor dot-termination) */
141
142 tctx->u.msg = NULL;
143 tctx->options = tctx->options & ~(topt_end_dot | topt_use_bdat)
144   | topt_output_string | topt_no_body;
145
146 rc = transport_write_message(tctx, 0);
147 hdrs = string_from_gstring(tctx->u.msg);
148 hsize = tctx->u.msg->ptr;
149
150 tctx->u.fd = save_fd;
151 tctx->options = save_options;
152 if (!rc) return FALSE;
153
154 /* Get signatures for headers plus spool data file */
155
156 #ifdef EXPERIMENTAL_ARC
157 arc_sign_init();        /*XXX perhaps move this call back to the smtp tpt
158               around where it currently calls arc_ams_setup_sign_bodyhash() ? */
159 #endif
160
161 /* The dotstuffed status of the datafile depends on whether it was stored
162 in wireformat. */
163
164 dkim->dot_stuffed = f.spool_file_wireformat;
165 if (!(dkim_signature = dkim_exim_sign(deliver_datafile,
166               spool_data_start_offset(message_id), hdrs, dkim, &errstr)))
167   if (!(rc = dkt_sign_fail(dkim, &errno)))
168     {
169     *err = errstr;
170     return FALSE;
171     }
172
173 #ifdef EXPERIMENTAL_ARC
174 if (dkim->arc_signspec)                 /* Prepend ARC headers */
175   {
176   uschar * e = NULL;
177   if (!(dkim_signature = arc_sign(dkim->arc_signspec, dkim_signature, &e)))
178     {
179     *err = e;
180     return FALSE;
181     }
182   }
183 #endif
184
185 /* Write the signature and headers into the deliver-out-buffer.  This should
186 mean they go out in the same packet as the MAIL, RCPT and (first) BDAT commands
187 (transport_write_message() sizes the BDAT for the buffered amount) - for short
188 messages, the BDAT LAST command.  We want no dotstuffing expansion here, it
189 having already been done - but we have to say we want CRLF output format, and
190 temporarily set the marker for possible already-CRLF input. */
191
192 tctx->options &= ~topt_escape_headers;
193 f.spool_file_wireformat = TRUE;
194 transport_write_reset(0);
195 if (  (  dkim_signature
196       && dkim_signature->ptr > 0
197       && !write_chunk(tctx, dkim_signature->s, dkim_signature->ptr)
198       )
199    || !write_chunk(tctx, hdrs, hsize)
200    )
201   return FALSE;
202
203 f.spool_file_wireformat = save_wireformat;
204 tctx->options = save_options | topt_no_headers | topt_continuation;
205
206 if (!(transport_write_message(tctx, 0)))
207   return FALSE;
208
209 tctx->options = save_options;
210 return TRUE;
211 }
212
213
214 /* This function is a wrapper around transport_write_message().
215    It is only called from the smtp transport if DKIM or Domainkeys support
216    is active and a transport filter is to be used.  The function sets up a
217    replacement fd into a -K file, then calls the normal function. This way, the
218    exact bits that exim would have put "on the wire" will end up in the file
219    (except for TLS encapsulation, which is the very very last thing). When we
220    are done signing the file, send the signed message down the original fd (or
221    TLS fd).
222
223 Arguments:
224   As for transport_write_message() in transort.c, with additional arguments
225   for DKIM.
226
227 Returns:       TRUE on success; FALSE (with errno) for any failure
228 */
229
230 static BOOL
231 dkt_via_kfile(transport_ctx * tctx, struct ob_dkim * dkim, const uschar ** err)
232 {
233 int dkim_fd;
234 int save_errno = 0;
235 BOOL rc;
236 uschar * dkim_spool_name;
237 gstring * dkim_signature;
238 int options, dlen;
239 off_t k_file_size;
240 const uschar * errstr;
241
242 dkim_spool_name = spool_fname(US"input", message_subdir, message_id,
243                     string_sprintf("-%d-K", (int)getpid()));
244
245 DEBUG(D_transport) debug_printf("dkim signing via file %s\n", dkim_spool_name);
246
247 if ((dkim_fd = Uopen(dkim_spool_name, O_RDWR|O_CREAT|O_TRUNC, SPOOL_MODE)) < 0)
248   {
249   /* Can't create spool file. Ugh. */
250   rc = FALSE;
251   save_errno = errno;
252   *err = string_sprintf("dkim spoolfile create: %s", strerror(errno));
253   goto CLEANUP;
254   }
255
256 /* Call transport utility function to write the -K file; does the CRLF expansion
257 (but, in the CHUNKING case, neither dot-stuffing nor dot-termination). */
258
259   {
260   int save_fd = tctx->u.fd;
261   tctx->u.fd = dkim_fd;
262   options = tctx->options;
263   tctx->options &= ~topt_use_bdat;
264
265   rc = transport_write_message(tctx, 0);
266
267   tctx->u.fd = save_fd;
268   tctx->options = options;
269   }
270
271 /* Save error state. We must clean up before returning. */
272 if (!rc)
273   {
274   save_errno = errno;
275   goto CLEANUP;
276   }
277
278 #ifdef EXPERIMENTAL_ARC
279 arc_sign_init();
280 #endif
281
282 /* Feed the file to the goats^W DKIM lib.  At this point the dotstuffed
283 status of the file depends on the output of transport_write_message() just
284 above, which should be the result of the end_dot flag in tctx->options. */
285
286 dkim->dot_stuffed = !!(options & topt_end_dot);
287 if (!(dkim_signature = dkim_exim_sign(dkim_fd, 0, NULL, dkim, &errstr)))
288   {
289   dlen = 0;
290   if (!(rc = dkt_sign_fail(dkim, &save_errno)))
291     {
292     *err = errstr;
293     goto CLEANUP;
294     }
295   }
296 else
297   dlen = dkim_signature->ptr;
298
299 #ifdef EXPERIMENTAL_ARC
300 if (dkim->arc_signspec)                         /* Prepend ARC headers */
301   {
302   if (!(dkim_signature = arc_sign(dkim->arc_signspec, dkim_signature, USS err)))
303     goto CLEANUP;
304   dlen = dkim_signature->ptr;
305   }
306 #endif
307
308 #ifndef OS_SENDFILE
309 if (options & topt_use_bdat)
310 #endif
311   if ((k_file_size = lseek(dkim_fd, 0, SEEK_END)) < 0)
312     {
313     *err = string_sprintf("dkim spoolfile seek: %s", strerror(errno));
314     goto CLEANUP;
315     }
316
317 if (options & topt_use_bdat)
318   {
319   /* On big messages output a precursor chunk to get any pipelined
320   MAIL & RCPT commands flushed, then reap the responses so we can
321   error out on RCPT rejects before sending megabytes. */
322
323   if (  dlen + k_file_size > DELIVER_OUT_BUFFER_SIZE
324      && dlen > 0)
325     {
326     if (  tctx->chunk_cb(tctx, dlen, 0) != OK
327        || !transport_write_block(tctx,
328                     dkim_signature->s, dlen, FALSE)
329        || tctx->chunk_cb(tctx, 0, tc_reap_prev) != OK
330        )
331       goto err;
332     dlen = 0;
333     }
334
335   /* Send the BDAT command for the entire message, as a single LAST-marked
336   chunk. */
337
338   if (tctx->chunk_cb(tctx, dlen + k_file_size, tc_chunk_last) != OK)
339     goto err;
340   }
341
342 if(dlen > 0 && !transport_write_block(tctx, dkim_signature->s, dlen, TRUE))
343   goto err;
344
345 if (!dkt_send_file(tctx->u.fd, dkim_fd, 0
346 #ifdef OS_SENDFILE
347   , k_file_size
348 #endif
349   ))
350   {
351   save_errno = errno;
352   rc = FALSE;
353   }
354
355 CLEANUP:
356   /* unlink -K file */
357   if (dkim_fd >= 0) (void)close(dkim_fd);
358   Uunlink(dkim_spool_name);
359   errno = save_errno;
360   return rc;
361
362 err:
363   save_errno = errno;
364   rc = FALSE;
365   goto CLEANUP;
366 }
367
368
369
370 /***************************************************************************************************
371 *    External interface to write the message, while signing it with DKIM and/or Domainkeys         *
372 ***************************************************************************************************/
373
374 /* This function is a wrapper around transport_write_message().
375    It is only called from the smtp transport if DKIM or Domainkeys support
376    is compiled in.
377
378 Arguments:
379   As for transport_write_message() in transort.c, with additional arguments
380   for DKIM.
381
382 Returns:       TRUE on success; FALSE (with errno) for any failure
383 */
384
385 BOOL
386 dkim_transport_write_message(transport_ctx * tctx,
387   struct ob_dkim * dkim, const uschar ** err)
388 {
389 BOOL yield;
390
391 /* If we can't sign, just call the original function. */
392
393 if (  !(dkim->dkim_private_key && dkim->dkim_domain && dkim->dkim_selector)
394    && !dkim->force_bodyhash)
395   return transport_write_message(tctx, 0);
396
397 /* If there is no filter command set up, construct the message and calculate
398 a dkim signature of it, send the signature and a reconstructed message. This
399 avoids using a temporary file. */
400
401 if (  !transport_filter_argv
402    || !*transport_filter_argv
403    || !**transport_filter_argv
404    )
405   yield = dkt_direct(tctx, dkim, err);
406
407 else
408   /* Use the transport path to write a file, calculate a dkim signature,
409   send the signature and then send the file. */
410
411   yield = dkt_via_kfile(tctx, dkim, err);
412
413 tctx->addr->dkim_used = string_from_gstring(dkim_signing_record);
414 return yield;
415 }
416
417 #endif  /* whole file */
418
419 /* vi: aw ai sw=2
420 */
421 /* End of dkim_transport.c */