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