ARC initial implementation. Experimental. Bug 2162
[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 - 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 dkim->dot_stuffed = !!(save_options & topt_end_dot);
155
156 if (!(dkim_signature = dkim_exim_sign(deliver_datafile, SPOOL_DATA_START_OFFSET,
157                                     hdrs, dkim, &errstr)))
158   if (!(rc = dkt_sign_fail(dkim, &errno)))
159     {
160     *err = errstr;
161     return FALSE;
162     }
163
164 #ifdef EXPERIMENTAL_ARC
165 if (dkim->arc_signspec)                 /* Prepend ARC headers */
166   if (!(dkim_signature =
167         arc_sign(dkim->arc_signspec, dkim_signature, &verrstr)))
168     {
169     *err = verrstr;
170     return FALSE;
171     }
172 #endif
173
174 /* Write the signature and headers into the deliver-out-buffer.  This should
175 mean they go out in the same packet as the MAIL, RCPT and (first) BDAT commands
176 (transport_write_message() sizes the BDAT for the buffered amount) - for short
177 messages, the BDAT LAST command.  We want no dotstuffing expansion here, it
178 having already been done - but we have to say we want CRLF output format, and
179 temporarily set the marker for possible already-CRLF input. */
180
181 tctx->options &= ~topt_escape_headers;
182 spool_file_wireformat = TRUE;
183 transport_write_reset(0);
184 if (  (  dkim_signature
185       && dkim_signature->ptr > 0
186       && !write_chunk(tctx, dkim_signature->s, dkim_signature->ptr)
187       )
188    || !write_chunk(tctx, hdrs, hsize)
189    )
190   return FALSE;
191
192 spool_file_wireformat = save_wireformat;
193 tctx->options = save_options | topt_no_headers | topt_continuation;
194
195 if (!(transport_write_message(tctx, 0)))
196   return FALSE;
197
198 tctx->options = save_options;
199 return TRUE;
200 }
201
202
203 /* This function is a wrapper around transport_write_message().
204    It is only called from the smtp transport if DKIM or Domainkeys support
205    is active and a transport filter is to be used.  The function sets up a
206    replacement fd into a -K file, then calls the normal function. This way, the
207    exact bits that exim would have put "on the wire" will end up in the file
208    (except for TLS encapsulation, which is the very very last thing). When we
209    are done signing the file, send the signed message down the original fd (or
210    TLS fd).
211
212 Arguments:
213   As for transport_write_message() in transort.c, with additional arguments
214   for DKIM.
215
216 Returns:       TRUE on success; FALSE (with errno) for any failure
217 */
218
219 static BOOL
220 dkt_via_kfile(transport_ctx * tctx, struct ob_dkim * dkim, const uschar ** err)
221 {
222 int dkim_fd;
223 int save_errno = 0;
224 BOOL rc;
225 uschar * dkim_spool_name;
226 gstring * dkim_signature;
227 int options, dlen;
228 off_t k_file_size;
229 const uschar * errstr;
230
231 dkim_spool_name = spool_fname(US"input", message_subdir, message_id,
232                     string_sprintf("-%d-K", (int)getpid()));
233
234 DEBUG(D_transport) debug_printf("dkim signing via file %s\n", dkim_spool_name);
235
236 if ((dkim_fd = Uopen(dkim_spool_name, O_RDWR|O_CREAT|O_TRUNC, SPOOL_MODE)) < 0)
237   {
238   /* Can't create spool file. Ugh. */
239   rc = FALSE;
240   save_errno = errno;
241   *err = string_sprintf("dkim spoolfile create: %s", strerror(errno));
242   goto CLEANUP;
243   }
244
245 /* Call transport utility function to write the -K file; does the CRLF expansion
246 (but, in the CHUNKING case, neither dot-stuffing nor dot-termination). */
247
248   {
249   int save_fd = tctx->u.fd;
250   tctx->u.fd = dkim_fd;
251   options = tctx->options;
252   tctx->options &= ~topt_use_bdat;
253
254   rc = transport_write_message(tctx, 0);
255
256   tctx->u.fd = save_fd;
257   tctx->options = options;
258   }
259
260 /* Save error state. We must clean up before returning. */
261 if (!rc)
262   {
263   save_errno = errno;
264   goto CLEANUP;
265   }
266
267 /* Feed the file to the goats^W DKIM lib */
268
269 dkim->dot_stuffed = !!(options & topt_end_dot);
270 if (!(dkim_signature = dkim_exim_sign(dkim_fd, 0, NULL, dkim, &errstr)))
271   {
272   dlen = 0;
273   if (!(rc = dkt_sign_fail(dkim, &save_errno)))
274     {
275     *err = errstr;
276     goto CLEANUP;
277     }
278   }
279 else
280   dlen = dkim_signature->ptr;
281
282 #ifdef EXPERIMENTAL_ARC
283 if (dkim->arc_signspec)                         /* Prepend ARC headers */
284   {
285   if (!(dkim_signature = arc_sign(dkim->arc_signspec, dkim_signature, USS err)))
286     goto CLEANUP;
287   dlen = dkim_signature->ptr;
288   }
289 #endif
290
291 #ifndef OS_SENDFILE
292 if (options & topt_use_bdat)
293 #endif
294   if ((k_file_size = lseek(dkim_fd, 0, SEEK_END)) < 0)
295     {
296     *err = string_sprintf("dkim spoolfile seek: %s", strerror(errno));
297     goto CLEANUP;
298     }
299
300 if (options & topt_use_bdat)
301   {
302   /* On big messages output a precursor chunk to get any pipelined
303   MAIL & RCPT commands flushed, then reap the responses so we can
304   error out on RCPT rejects before sending megabytes. */
305
306   if (  dlen + k_file_size > DELIVER_OUT_BUFFER_SIZE
307      && dlen > 0)
308     {
309     if (  tctx->chunk_cb(tctx, dlen, 0) != OK
310        || !transport_write_block(tctx,
311                     dkim_signature->s, dlen, FALSE)
312        || tctx->chunk_cb(tctx, 0, tc_reap_prev) != OK
313        )
314       goto err;
315     dlen = 0;
316     }
317
318   /* Send the BDAT command for the entire message, as a single LAST-marked
319   chunk. */
320
321   if (tctx->chunk_cb(tctx, dlen + k_file_size, tc_chunk_last) != OK)
322     goto err;
323   }
324
325 if(dlen > 0 && !transport_write_block(tctx, dkim_signature->s, dlen, TRUE))
326   goto err;
327
328 if (!dkt_send_file(tctx->u.fd, dkim_fd, 0
329 #ifdef OS_SENDFILE
330   , k_file_size
331 #endif
332   ))
333   {
334   save_errno = errno;
335   rc = FALSE;
336   }
337
338 CLEANUP:
339   /* unlink -K file */
340   if (dkim_fd >= 0) (void)close(dkim_fd);
341   Uunlink(dkim_spool_name);
342   errno = save_errno;
343   return rc;
344
345 err:
346   save_errno = errno;
347   rc = FALSE;
348   goto CLEANUP;
349 }
350
351
352
353 /***************************************************************************************************
354 *    External interface to write the message, while signing it with DKIM and/or Domainkeys         *
355 ***************************************************************************************************/
356
357 /* This function is a wrapper around transport_write_message().
358    It is only called from the smtp transport if DKIM or Domainkeys support
359    is compiled in.
360
361 Arguments:
362   As for transport_write_message() in transort.c, with additional arguments
363   for DKIM.
364
365 Returns:       TRUE on success; FALSE (with errno) for any failure
366 */
367
368 BOOL
369 dkim_transport_write_message(transport_ctx * tctx,
370   struct ob_dkim * dkim, const uschar ** err)
371 {
372 /* If we can't sign, just call the original function. */
373
374 if (  !(dkim->dkim_private_key && dkim->dkim_domain && dkim->dkim_selector)
375    && !dkim->force_bodyhash)
376   return transport_write_message(tctx, 0);
377
378 /* If there is no filter command set up, construct the message and calculate
379 a dkim signature of it, send the signature and a reconstructed message. This
380 avoids using a temprary file. */
381
382 if (  !transport_filter_argv
383    || !*transport_filter_argv
384    || !**transport_filter_argv
385    )
386   return dkt_direct(tctx, dkim, err);
387
388 /* Use the transport path to write a file, calculate a dkim signature,
389 send the signature and then send the file. */
390
391 return dkt_via_kfile(tctx, dkim, err);
392 }
393
394 #endif  /* whole file */
395
396 /* vi: aw ai sw=2
397 */
398 /* End of dkim_transport.c */