Enable use of sendfile
[exim.git] / src / src / dkim_transport.c
CommitLineData
42055a33
JH
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
42055a33
JH
10
11#include "exim.h"
12
09d5060c
JH
13#ifndef DISABLE_DKIM /* rest of file */
14
42055a33 15#ifdef HAVE_LINUX_SENDFILE
09d5060c 16# include <sys/sendfile.h>
42055a33
JH
17#endif
18
19
20static BOOL
21dkt_sign_fail(struct ob_dkim * dkim, int * errp)
22{
23if (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 }
38return TRUE;
39}
40
41static BOOL
42dkt_send_file(int out_fd, int in_fd, off_t off, size_t size)
43{
44DEBUG(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 */
49lseek(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. */
56if (tls_out.active != out_fd)
57 {
58 ssize_t copied = 0;
59
60 while(copied >= 0 && off < size)
eeb35890 61 copied = sendfile(out_fd, in_fd, &off, size - off);
42055a33
JH
62 if (copied < 0)
63 return FALSE;
64 }
65else
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
98return 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
108Arguments:
109 As for transport_write_message() in transort.c, with additional arguments
110 for DKIM.
111
112Returns: TRUE on success; FALSE (with errno) for any failure
113*/
114
115static BOOL
116dkt_direct(transport_ctx * tctx, struct ob_dkim * dkim,
117 const uschar ** err)
118{
119int save_fd = tctx->u.fd;
120int save_options = tctx->options;
121uschar * hdrs, * dkim_signature;
122int siglen, hsize;
123const uschar * errstr;
124BOOL rc;
125
126DEBUG(D_transport) debug_printf("dkim signing direct-mode\n");
127
128/* Get headers in string for signing and transmission */
129
130tctx->u.msg = NULL;
131tctx->options = tctx->options & ~(topt_end_dot | topt_use_bdat)
132 | topt_output_string | topt_no_body;
133
134rc = transport_write_message(tctx, 0);
135hdrs = tctx->u.msg;
136hdrs[hsize = tctx->msg_ptr] = '\0';
137
138tctx->u.fd = save_fd;
139tctx->options = save_options;
140if (!rc) return FALSE;
141
142/* Get signatures for headers plus spool data file */
143
144dkim->dot_stuffed = !!(save_options & topt_end_dot);
145
146if ((dkim_signature = dkim_exim_sign(deliver_datafile, SPOOL_DATA_START_OFFSET,
147 hdrs, dkim, &errstr)))
148 siglen = Ustrlen(dkim_signature);
149else 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
156mean 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
158messages, the BDAT LAST command. We want no CRLF or dotstuffing expansion */
159
09d5060c 160tctx->options &= ~(topt_use_crlf | topt_escape_headers);
42055a33
JH
161transport_write_reset(0);
162if ( !write_chunk(tctx, dkim_signature, siglen)
163 || !write_chunk(tctx, hdrs, hsize))
164 return FALSE;
165
166tctx->options = save_options | topt_no_headers | topt_continuation;
167
168if (!(transport_write_message(tctx, 0)))
169 return FALSE;
170
171tctx->options = save_options;
172return 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
185Arguments:
186 As for transport_write_message() in transort.c, with additional arguments
187 for DKIM.
188
189Returns: TRUE on success; FALSE (with errno) for any failure
190*/
191
192static BOOL
193dkt_via_kfile(transport_ctx * tctx, struct ob_dkim * dkim, const uschar ** err)
194{
195int dkim_fd;
196int save_errno = 0;
197BOOL rc;
198uschar * dkim_spool_name, * dkim_signature;
199int sread = 0, wwritten = 0, siglen, options;
200off_t k_file_size;
201const uschar * errstr;
202
203dkim_spool_name = spool_fname(US"input", message_subdir, message_id,
204 string_sprintf("-%d-K", (int)getpid()));
205
206DEBUG(D_transport) debug_printf("dkim signing via file %s\n", dkim_spool_name);
207
208if ((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. */
233if (!rc)
234 {
235 save_errno = errno;
236 goto CLEANUP;
237 }
238
239/* Feed the file to the goats^W DKIM lib */
240
241dkim->dot_stuffed = !!(options & topt_end_dot);
242if ((dkim_signature = dkim_exim_sign(dkim_fd, 0, NULL, dkim, &errstr)))
243 siglen = Ustrlen(dkim_signature);
244else if (!(rc = dkt_sign_fail(dkim, &save_errno)))
245 {
246 *err = errstr;
247 goto CLEANUP;
248 }
249
250#ifndef HAVE_LINUX_SENDFILE
251if (options & topt_use_bdat)
252#endif
253 k_file_size = lseek(dkim_fd, 0, SEEK_END); /* Fetch file size */
254
255if (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
278if(siglen > 0 && !transport_write_block(tctx, dkim_signature, siglen, TRUE))
279 goto err;
280
281if (!dkt_send_file(tctx->u.fd, dkim_fd, 0, k_file_size))
282 {
283 save_errno = errno;
284 rc = FALSE;
285 }
286
287CLEANUP:
288 /* unlink -K file */
289 (void)close(dkim_fd);
290 Uunlink(dkim_spool_name);
291 errno = save_errno;
292 return rc;
293
294err:
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
310Arguments:
311 As for transport_write_message() in transort.c, with additional arguments
312 for DKIM.
313
314Returns: TRUE on success; FALSE (with errno) for any failure
315*/
316
317BOOL
318dkim_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
323if (!(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
327a dkim signature of it, send the signature and a reconstructed message. This
328avoids using a temprary file. */
329
330if ( !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,
337send the signature and then send the file. */
338
339return 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 */