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);
112 /* Prepend ARC-signing headers to given set of headers
115 signspec Three-element colon-sep list: identity, selector, privkey.
116 Optional fourth element: comma-sep list of options.
118 sigheaders Any signature headers already generated, eg. by DKIM, or NULL
122 Set of headers to prepend to the message, including the supplied sigheaders
123 but not the plainheaders.
127 dkt_arc_sign(const uschar * signspec, gstring * sigheaders, uschar ** errstr_p)
129 const misc_module_info * mi = misc_mod_findonly(US"arc");
130 typedef gstring * (*fn_t)(const uschar *, gstring *, uschar **);
132 return (((fn_t *) mi->functions)[ARC_SIGN]) (signspec, sigheaders, errstr_p);
133 *errstr_p = US"failed to find arc module";
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.
144 As for transport_write_message() in transort.c, with additional arguments
147 Returns: TRUE on success; FALSE (with errno) for any failure
151 dkt_direct(transport_ctx * tctx, struct ob_dkim * dkim,
154 int save_fd = tctx->u.fd;
155 int save_options = tctx->options;
156 BOOL save_wireformat = f.spool_file_wireformat;
158 gstring * dkim_signature;
160 const uschar * errstr;
163 DEBUG(D_transport) debug_printf("dkim signing direct-mode\n");
165 /* Get headers in string for signing and transmission. Do CRLF
166 and dotstuffing (but no body nor dot-termination) */
169 tctx->options = tctx->options & ~(topt_end_dot | topt_use_bdat)
170 | topt_output_string | topt_no_body;
172 rc = transport_write_message(tctx, 0);
173 hdrs = string_from_gstring(tctx->u.msg);
174 hsize = tctx->u.msg->ptr;
176 tctx->u.fd = save_fd;
177 tctx->options = save_options;
178 if (!rc) return FALSE;
180 /* Get signatures for headers plus spool data file */
182 /* The dotstuffed status of the datafile depends on whether it was stored
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)))
194 #ifdef EXPERIMENTAL_ARC
195 if (dkim->arc_signspec) /* Prepend ARC headers */
198 if (!(dkim_signature = dkt_arc_sign(dkim->arc_signspec, dkim_signature, &e)))
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. */
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)
220 || !write_chunk(tctx, hdrs, hsize)
224 f.spool_file_wireformat = save_wireformat;
225 tctx->options = save_options | topt_no_headers | topt_continuation;
227 if (!(transport_write_message(tctx, 0)))
230 tctx->options = save_options;
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
245 As for transport_write_message() in transort.c, with additional arguments
248 Returns: TRUE on success; FALSE (with errno) for any failure
252 dkt_via_kfile(transport_ctx * tctx, struct ob_dkim * dkim, const uschar ** err)
257 uschar * dkim_spool_name;
258 gstring * dkim_signature;
261 const uschar * errstr;
263 dkim_spool_name = spool_fname(US"input", message_subdir, message_id,
264 string_sprintf("-%d-K", (int)getpid()));
266 DEBUG(D_transport) debug_printf("dkim signing via file %s\n", dkim_spool_name);
268 if ((dkim_fd = Uopen(dkim_spool_name, O_RDWR|O_CREAT|O_TRUNC, SPOOL_MODE)) < 0)
270 /* Can't create spool file. Ugh. */
273 *err = string_sprintf("dkim spoolfile create: %s", strerror(errno));
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). */
281 int save_fd = tctx->u.fd;
282 tctx->u.fd = dkim_fd;
283 options = tctx->options;
284 tctx->options &= ~topt_use_bdat;
286 rc = transport_write_message(tctx, 0);
288 tctx->u.fd = save_fd;
289 tctx->options = options;
292 /* Save error state. We must clean up before returning. */
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. */
303 dkim->dot_stuffed = !!(options & topt_end_dot);
304 if (!(dkim_signature = dkim_exim_sign(dkim_fd, 0, NULL, dkim, &errstr)))
307 if (!(rc = dkt_sign_fail(dkim, &save_errno)))
314 dlen = dkim_signature->ptr;
316 #ifdef EXPERIMENTAL_ARC
317 if (dkim->arc_signspec) /* Prepend ARC headers */
319 if (!(dkim_signature = dkt_arc_sign(dkim->arc_signspec, dkim_signature,
322 dlen = dkim_signature->ptr;
327 if (options & topt_use_bdat)
329 if ((k_file_size = lseek(dkim_fd, 0, SEEK_END)) < 0)
331 *err = string_sprintf("dkim spoolfile seek: %s", strerror(errno));
335 if (options & topt_use_bdat)
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. */
341 if ( dlen + k_file_size > DELIVER_OUT_BUFFER_SIZE
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
353 /* Send the BDAT command for the entire message, as a single LAST-marked
356 if (tctx->chunk_cb(tctx, dlen + k_file_size, tc_chunk_last) != OK)
360 if(dlen > 0 && !transport_write_block(tctx, dkim_signature->s, dlen, TRUE))
363 if (!dkt_send_file(tctx->u.fd, dkim_fd, 0
375 if (dkim_fd >= 0) (void)close(dkim_fd);
376 Uunlink(dkim_spool_name);
388 /***************************************************************************************************
389 * External interface to write the message, while signing it with DKIM and/or Domainkeys *
390 ***************************************************************************************************/
392 /* This function is a wrapper around transport_write_message().
393 It is only called from the smtp transport if DKIM or Domainkeys support
397 As for transport_write_message() in transort.c, with additional arguments
400 Returns: TRUE on success; FALSE (with errno) for any failure
404 dkim_transport_write_message(transport_ctx * tctx,
405 struct ob_dkim * dkim, const uschar ** err)
409 /* If we can't sign, just call the original function. */
411 if ( !(dkim->dkim_private_key && dkim->dkim_domain && dkim->dkim_selector)
412 && !dkim->force_bodyhash)
413 return transport_write_message(tctx, 0);
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. */
419 if ( !transport_filter_argv
420 || !*transport_filter_argv
421 || !**transport_filter_argv
423 yield = dkt_direct(tctx, dkim, err);
426 /* Use the transport path to write a file, calculate a dkim signature,
427 send the signature and then send the file. */
429 yield = dkt_via_kfile(tctx, dkim, err);
431 tctx->addr->dkim_used = string_from_gstring(dkim_signing_record);
435 #endif /* whole file */
439 /* End of dkim_transport.c */