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