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