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