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