1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
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 */
10 /* Transport shim for dkim signing */
15 #ifndef DISABLE_DKIM /* rest of file */
19 dkt_sign_fail(struct ob_dkim * dkim, int * errp)
21 GET_OPTION("dkim_strict");
22 if (dkim->dkim_strict)
24 uschar * dkim_strict_result = expand_string(dkim->dkim_strict);
26 if (dkim_strict_result)
27 if ( strcmpic(dkim_strict_result, US"1") == 0
28 || strcmpic(dkim_strict_result, US"true") == 0)
30 /* Set errno to something halfway meaningful */
32 log_write(0, LOG_MAIN, "DKIM: message could not be signed,"
33 " and dkim_strict is set. Deferring message delivery.");
40 /* Send the file at in_fd down the output fd */
43 dkt_send_file(int out_fd, int in_fd, off_t off
50 DEBUG(D_transport) debug_printf("send file fd=%d size=%u\n", out_fd, (unsigned)(size - off));
52 DEBUG(D_transport) debug_printf("send file fd=%d\n", out_fd);
55 /*XXX should implement timeout, like transport_write_block_fd() ? */
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)
66 while(copied >= 0 && off < size)
67 copied = os_sendfile(out_fd, in_fd, &off, size - off);
79 if (lseek(in_fd, off, SEEK_SET) < 0) return FALSE;
81 /* Send file down the original fd */
82 while((sread = read(in_fd, deliver_out_buffer, DELIVER_OUT_BUFFER_SIZE)) > 0)
84 uschar * p = deliver_out_buffer;
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);
94 wwritten = write(out_fd, CS p, sread);
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.
118 As for transport_write_message() in transort.c, with additional arguments
121 Returns: TRUE on success; FALSE (with errno) for any failure
125 dkt_direct(transport_ctx * tctx, struct ob_dkim * dkim,
128 int save_fd = tctx->u.fd;
129 int save_options = tctx->options;
130 BOOL save_wireformat = f.spool_file_wireformat;
132 gstring * dkim_signature;
134 const uschar * errstr;
137 DEBUG(D_transport) debug_printf("dkim signing direct-mode\n");
139 /* Get headers in string for signing and transmission. Do CRLF
140 and dotstuffing (but no body nor dot-termination) */
143 tctx->options = tctx->options & ~(topt_end_dot | topt_use_bdat)
144 | topt_output_string | topt_no_body;
146 rc = transport_write_message(tctx, 0);
147 hdrs = string_from_gstring(tctx->u.msg);
148 hsize = tctx->u.msg->ptr;
150 tctx->u.fd = save_fd;
151 tctx->options = save_options;
152 if (!rc) return FALSE;
154 /* Get signatures for headers plus spool data file */
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() ? */
161 /* The dotstuffed status of the datafile depends on whether it was stored
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)))
173 #ifdef EXPERIMENTAL_ARC
174 if (dkim->arc_signspec) /* Prepend ARC headers */
177 if (!(dkim_signature = arc_sign(dkim->arc_signspec, dkim_signature, &e)))
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. */
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)
199 || !write_chunk(tctx, hdrs, hsize)
203 f.spool_file_wireformat = save_wireformat;
204 tctx->options = save_options | topt_no_headers | topt_continuation;
206 if (!(transport_write_message(tctx, 0)))
209 tctx->options = save_options;
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
224 As for transport_write_message() in transort.c, with additional arguments
227 Returns: TRUE on success; FALSE (with errno) for any failure
231 dkt_via_kfile(transport_ctx * tctx, struct ob_dkim * dkim, const uschar ** err)
236 uschar * dkim_spool_name;
237 gstring * dkim_signature;
240 const uschar * errstr;
242 dkim_spool_name = spool_fname(US"input", message_subdir, message_id,
243 string_sprintf("-%d-K", (int)getpid()));
245 DEBUG(D_transport) debug_printf("dkim signing via file %s\n", dkim_spool_name);
247 if ((dkim_fd = Uopen(dkim_spool_name, O_RDWR|O_CREAT|O_TRUNC, SPOOL_MODE)) < 0)
249 /* Can't create spool file. Ugh. */
252 *err = string_sprintf("dkim spoolfile create: %s", strerror(errno));
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). */
260 int save_fd = tctx->u.fd;
261 tctx->u.fd = dkim_fd;
262 options = tctx->options;
263 tctx->options &= ~topt_use_bdat;
265 rc = transport_write_message(tctx, 0);
267 tctx->u.fd = save_fd;
268 tctx->options = options;
271 /* Save error state. We must clean up before returning. */
278 #ifdef EXPERIMENTAL_ARC
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. */
286 dkim->dot_stuffed = !!(options & topt_end_dot);
287 if (!(dkim_signature = dkim_exim_sign(dkim_fd, 0, NULL, dkim, &errstr)))
290 if (!(rc = dkt_sign_fail(dkim, &save_errno)))
297 dlen = dkim_signature->ptr;
299 #ifdef EXPERIMENTAL_ARC
300 if (dkim->arc_signspec) /* Prepend ARC headers */
302 if (!(dkim_signature = arc_sign(dkim->arc_signspec, dkim_signature, USS err)))
304 dlen = dkim_signature->ptr;
309 if (options & topt_use_bdat)
311 if ((k_file_size = lseek(dkim_fd, 0, SEEK_END)) < 0)
313 *err = string_sprintf("dkim spoolfile seek: %s", strerror(errno));
317 if (options & topt_use_bdat)
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. */
323 if ( dlen + k_file_size > DELIVER_OUT_BUFFER_SIZE
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
335 /* Send the BDAT command for the entire message, as a single LAST-marked
338 if (tctx->chunk_cb(tctx, dlen + k_file_size, tc_chunk_last) != OK)
342 if(dlen > 0 && !transport_write_block(tctx, dkim_signature->s, dlen, TRUE))
345 if (!dkt_send_file(tctx->u.fd, dkim_fd, 0
357 if (dkim_fd >= 0) (void)close(dkim_fd);
358 Uunlink(dkim_spool_name);
370 /***************************************************************************************************
371 * External interface to write the message, while signing it with DKIM and/or Domainkeys *
372 ***************************************************************************************************/
374 /* This function is a wrapper around transport_write_message().
375 It is only called from the smtp transport if DKIM or Domainkeys support
379 As for transport_write_message() in transort.c, with additional arguments
382 Returns: TRUE on success; FALSE (with errno) for any failure
386 dkim_transport_write_message(transport_ctx * tctx,
387 struct ob_dkim * dkim, const uschar ** err)
391 /* If we can't sign, just call the original function. */
393 if ( !(dkim->dkim_private_key && dkim->dkim_domain && dkim->dkim_selector)
394 && !dkim->force_bodyhash)
395 return transport_write_message(tctx, 0);
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. */
401 if ( !transport_filter_argv
402 || !*transport_filter_argv
403 || !**transport_filter_argv
405 yield = dkt_direct(tctx, dkim, err);
408 /* Use the transport path to write a file, calculate a dkim signature,
409 send the signature and then send the file. */
411 yield = dkt_via_kfile(tctx, dkim, err);
413 tctx->addr->dkim_used = string_from_gstring(dkim_signing_record);
417 #endif /* whole file */
421 /* End of dkim_transport.c */