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