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 */
16 #ifndef DISABLE_DKIM /* rest of file */
20 dkt_sign_fail(struct ob_dkim * dkim, int * errp)
22 GET_OPTION("dkim_strict");
23 if (dkim->dkim_strict)
25 uschar * dkim_strict_result = expand_string(dkim->dkim_strict);
27 if (dkim_strict_result)
28 if ( strcmpic(dkim_strict_result, US"1") == 0
29 || strcmpic(dkim_strict_result, US"true") == 0)
31 /* Set errno to something halfway meaningful */
33 log_write(0, LOG_MAIN, "DKIM: message could not be signed,"
34 " and dkim_strict is set. Deferring message delivery.");
41 /* Send the file at in_fd down the output fd */
44 dkt_send_file(int out_fd, int in_fd, off_t off
51 DEBUG(D_transport) debug_printf("send file fd=%d size=%u\n", out_fd, (unsigned)(size - off));
53 DEBUG(D_transport) debug_printf("send file fd=%d\n", out_fd);
56 /*XXX should implement timeout, like transport_write_block_fd() ? */
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)
67 while(copied >= 0 && off < size)
68 copied = os_sendfile(out_fd, in_fd, &off, size - off);
80 if (lseek(in_fd, off, SEEK_SET) < 0) return FALSE;
82 /* Send file down the original fd */
83 while((sread = read(in_fd, deliver_out_buffer, DELIVER_OUT_BUFFER_SIZE)) > 0)
85 uschar * p = deliver_out_buffer;
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);
95 wwritten = write(out_fd, CS p, sread);
113 /* Prepend ARC-signing headers to given set of headers
116 signspec Three-element colon-sep list: identity, selector, privkey.
117 Optional fourth element: comma-sep list of options.
119 sigheaders Any signature headers already generated, eg. by DKIM, or NULL
123 Set of headers to prepend to the message, including the supplied sigheaders
124 but not the plainheaders.
128 dkt_arc_sign(const uschar * signspec, gstring * sigheaders, uschar ** errstr_p)
130 const misc_module_info * mi = misc_mod_findonly(US"arc");
131 typedef gstring * (*fn_t)(const uschar *, gstring *, uschar **);
133 return (((fn_t *) mi->functions)[ARC_SIGN]) (signspec, sigheaders, errstr_p);
134 *errstr_p = US"failed to find arc module";
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.
145 As for transport_write_message() in transort.c, with additional arguments
148 Returns: TRUE on success; FALSE (with errno) for any failure
152 dkt_direct(transport_ctx * tctx, struct ob_dkim * dkim,
155 int save_fd = tctx->u.fd;
156 int save_options = tctx->options;
157 BOOL save_wireformat = f.spool_file_wireformat;
159 gstring * dkim_signature;
161 const uschar * errstr;
164 DEBUG(D_transport) debug_printf("dkim signing direct-mode\n");
166 /* Get headers in string for signing and transmission. Do CRLF
167 and dotstuffing (but no body nor dot-termination) */
170 tctx->options = tctx->options & ~(topt_end_dot | topt_use_bdat)
171 | topt_output_string | topt_no_body;
173 rc = transport_write_message(tctx, 0);
174 hdrs = string_from_gstring(tctx->u.msg);
175 hsize = tctx->u.msg->ptr;
177 tctx->u.fd = save_fd;
178 tctx->options = save_options;
179 if (!rc) return FALSE;
181 /* Get signatures for headers plus spool data file */
183 /* The dotstuffed status of the datafile depends on whether it was stored
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)))
195 #ifdef EXPERIMENTAL_ARC
196 if (dkim->arc_signspec) /* Prepend ARC headers */
199 if (!(dkim_signature = dkt_arc_sign(dkim->arc_signspec, dkim_signature, &e)))
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. */
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)
221 || !write_chunk(tctx, hdrs, hsize)
225 f.spool_file_wireformat = save_wireformat;
226 tctx->options = save_options | topt_no_headers | topt_continuation;
228 if (!(transport_write_message(tctx, 0)))
231 tctx->options = save_options;
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
246 As for transport_write_message() in transort.c, with additional arguments
249 Returns: TRUE on success; FALSE (with errno) for any failure
253 dkt_via_kfile(transport_ctx * tctx, struct ob_dkim * dkim, const uschar ** err)
258 uschar * dkim_spool_name;
259 gstring * dkim_signature;
262 const uschar * errstr;
264 dkim_spool_name = spool_fname(US"input", message_subdir, message_id,
265 string_sprintf("-%d-K", (int)getpid()));
267 DEBUG(D_transport) debug_printf("dkim signing via file %s\n", dkim_spool_name);
269 if ((dkim_fd = Uopen(dkim_spool_name, O_RDWR|O_CREAT|O_TRUNC, SPOOL_MODE)) < 0)
271 /* Can't create spool file. Ugh. */
274 *err = string_sprintf("dkim spoolfile create: %s", strerror(errno));
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). */
282 int save_fd = tctx->u.fd;
283 tctx->u.fd = dkim_fd;
284 options = tctx->options;
285 tctx->options &= ~topt_use_bdat;
287 rc = transport_write_message(tctx, 0);
289 tctx->u.fd = save_fd;
290 tctx->options = options;
293 /* Save error state. We must clean up before returning. */
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. */
304 dkim->dot_stuffed = !!(options & topt_end_dot);
305 if (!(dkim_signature = dkim_exim_sign(dkim_fd, 0, NULL, dkim, &errstr)))
308 if (!(rc = dkt_sign_fail(dkim, &save_errno)))
315 dlen = dkim_signature->ptr;
317 #ifdef EXPERIMENTAL_ARC
318 if (dkim->arc_signspec) /* Prepend ARC headers */
320 if (!(dkim_signature = dkt_arc_sign(dkim->arc_signspec, dkim_signature,
323 dlen = dkim_signature->ptr;
328 if (options & topt_use_bdat)
330 if ((k_file_size = lseek(dkim_fd, 0, SEEK_END)) < 0)
332 *err = string_sprintf("dkim spoolfile seek: %s", strerror(errno));
336 if (options & topt_use_bdat)
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. */
342 if ( dlen + k_file_size > DELIVER_OUT_BUFFER_SIZE
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
354 /* Send the BDAT command for the entire message, as a single LAST-marked
357 if (tctx->chunk_cb(tctx, dlen + k_file_size, tc_chunk_last) != OK)
361 if(dlen > 0 && !transport_write_block(tctx, dkim_signature->s, dlen, TRUE))
364 if (!dkt_send_file(tctx->u.fd, dkim_fd, 0
376 if (dkim_fd >= 0) (void)close(dkim_fd);
377 Uunlink(dkim_spool_name);
389 /***************************************************************************************************
390 * External interface to write the message, while signing it with DKIM and/or Domainkeys *
391 ***************************************************************************************************/
393 /* This function is a wrapper around transport_write_message().
394 It is only called from the smtp transport if DKIM or Domainkeys support
398 As for transport_write_message() in transort.c, with additional arguments
401 Returns: TRUE on success; FALSE (with errno) for any failure
405 dkim_transport_write_message(transport_ctx * tctx,
406 struct ob_dkim * dkim, const uschar ** err)
410 /* If we can't sign, just call the original function. */
412 if ( !(dkim->dkim_private_key && dkim->dkim_domain && dkim->dkim_selector)
413 && !dkim->force_bodyhash)
414 return transport_write_message(tctx, 0);
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. */
420 if ( !transport_filter_argv
421 || !*transport_filter_argv
422 || !**transport_filter_argv
424 yield = dkt_direct(tctx, dkim, err);
427 /* Use the transport path to write a file, calculate a dkim signature,
428 send the signature and then send the file. */
430 yield = dkt_via_kfile(tctx, dkim, err);
432 tctx->addr->dkim_used = string_from_gstring(dkim_signing_record);
436 #endif /* whole file */
440 /* End of dkim_transport.c */