Copyright updates:
[exim.git] / src / src / dkim_transport.c
1 /*************************************************
2 *     Exim - an Internet mail transport agent    *
3 *************************************************/
4
5 /* Copyright (c) The Exim Maintainers 2022 */
6 /* Copyright (c) University of Cambridge 1995 - 2018 */
7 /* See the file NOTICE for conditions of use and distribution. */
8
9 /* Transport shim for dkim signing */
10
11
12 #include "exim.h"
13
14 #ifndef DISABLE_DKIM    /* rest of file */
15
16
17 static BOOL
18 dkt_sign_fail(struct ob_dkim * dkim, int * errp)
19 {
20 if (dkim->dkim_strict)
21   {
22   uschar * dkim_strict_result = expand_string(dkim->dkim_strict);
23
24   if (dkim_strict_result)
25     if (  strcmpic(dkim_strict_result, US"1") == 0
26        || strcmpic(dkim_strict_result, US"true") == 0)
27       {
28       /* Set errno to something halfway meaningful */
29       *errp = EACCES;
30       log_write(0, LOG_MAIN, "DKIM: message could not be signed,"
31         " and dkim_strict is set. Deferring message delivery.");
32       return FALSE;
33       }
34   }
35 return TRUE;
36 }
37
38 /* Send the file at in_fd down the output fd */
39
40 static BOOL
41 dkt_send_file(int out_fd, int in_fd, off_t off
42 #ifdef OS_SENDFILE
43   , size_t size
44 #endif
45   )
46 {
47 #ifdef OS_SENDFILE
48 DEBUG(D_transport) debug_printf("send file fd=%d size=%u\n", out_fd, (unsigned)(size - off));
49 #else
50 DEBUG(D_transport) debug_printf("send file fd=%d\n", out_fd);
51 #endif
52
53 /*XXX should implement timeout, like transport_write_block_fd() ? */
54
55 #ifdef OS_SENDFILE
56 /* We can use sendfile() to shove the file contents
57    to the socket. However only if we don't use TLS,
58    as then there's another layer of indirection
59    before the data finally hits the socket. */
60 if (tls_out.active.sock != out_fd)
61   {
62   ssize_t copied = 0;
63
64   while(copied >= 0 && off < size)
65     copied = os_sendfile(out_fd, in_fd, &off, size - off);
66   if (copied < 0)
67     return FALSE;
68   }
69 else
70
71 #endif
72
73   {
74   int sread, wwritten;
75
76   /* Rewind file */
77   if (lseek(in_fd, off, SEEK_SET) < 0) return FALSE;
78
79   /* Send file down the original fd */
80   while((sread = read(in_fd, deliver_out_buffer, DELIVER_OUT_BUFFER_SIZE)) > 0)
81     {
82     uschar * p = deliver_out_buffer;
83     /* write the chunk */
84
85     while (sread)
86       {
87 #ifndef DISABLE_TLS
88       wwritten = tls_out.active.sock == out_fd
89         ? tls_write(tls_out.active.tls_ctx, p, sread, FALSE)
90         : write(out_fd, CS p, sread);
91 #else
92       wwritten = write(out_fd, CS p, sread);
93 #endif
94       if (wwritten == -1)
95         return FALSE;
96       p += wwritten;
97       sread -= wwritten;
98       }
99     }
100
101   if (sread == -1)
102     return FALSE;
103   }
104
105 return TRUE;
106 }
107
108
109
110
111 /* This function is a wrapper around transport_write_message().
112    It is only called from the smtp transport if DKIM or Domainkeys support
113    is active and no transport filter is to be used.
114
115 Arguments:
116   As for transport_write_message() in transort.c, with additional arguments
117   for DKIM.
118
119 Returns:       TRUE on success; FALSE (with errno) for any failure
120 */
121
122 static BOOL
123 dkt_direct(transport_ctx * tctx, struct ob_dkim * dkim,
124   const uschar ** err)
125 {
126 int save_fd = tctx->u.fd;
127 int save_options = tctx->options;
128 BOOL save_wireformat = f.spool_file_wireformat;
129 uschar * hdrs;
130 gstring * dkim_signature;
131 int hsize;
132 const uschar * errstr;
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 /* The dotstuffed status of the datafile depends on whether it was stored
159 in wireformat. */
160
161 dkim->dot_stuffed = f.spool_file_wireformat;
162 if (!(dkim_signature = dkim_exim_sign(deliver_datafile, SPOOL_DATA_START_OFFSET,
163                                     hdrs, dkim, &errstr)))
164   if (!(rc = dkt_sign_fail(dkim, &errno)))
165     {
166     *err = errstr;
167     return FALSE;
168     }
169
170 #ifdef EXPERIMENTAL_ARC
171 if (dkim->arc_signspec)                 /* Prepend ARC headers */
172   {
173   uschar * e = NULL;
174   if (!(dkim_signature = arc_sign(dkim->arc_signspec, dkim_signature, &e)))
175     {
176     *err = e;
177     return FALSE;
178     }
179   }
180 #endif
181
182 /* Write the signature and headers into the deliver-out-buffer.  This should
183 mean they go out in the same packet as the MAIL, RCPT and (first) BDAT commands
184 (transport_write_message() sizes the BDAT for the buffered amount) - for short
185 messages, the BDAT LAST command.  We want no dotstuffing expansion here, it
186 having already been done - but we have to say we want CRLF output format, and
187 temporarily set the marker for possible already-CRLF input. */
188
189 tctx->options &= ~topt_escape_headers;
190 f.spool_file_wireformat = TRUE;
191 transport_write_reset(0);
192 if (  (  dkim_signature
193       && dkim_signature->ptr > 0
194       && !write_chunk(tctx, dkim_signature->s, dkim_signature->ptr)
195       )
196    || !write_chunk(tctx, hdrs, hsize)
197    )
198   return FALSE;
199
200 f.spool_file_wireformat = save_wireformat;
201 tctx->options = save_options | topt_no_headers | topt_continuation;
202
203 if (!(transport_write_message(tctx, 0)))
204   return FALSE;
205
206 tctx->options = save_options;
207 return TRUE;
208 }
209
210
211 /* This function is a wrapper around transport_write_message().
212    It is only called from the smtp transport if DKIM or Domainkeys support
213    is active and a transport filter is to be used.  The function sets up a
214    replacement fd into a -K file, then calls the normal function. This way, the
215    exact bits that exim would have put "on the wire" will end up in the file
216    (except for TLS encapsulation, which is the very very last thing). When we
217    are done signing the file, send the signed message down the original fd (or
218    TLS fd).
219
220 Arguments:
221   As for transport_write_message() in transort.c, with additional arguments
222   for DKIM.
223
224 Returns:       TRUE on success; FALSE (with errno) for any failure
225 */
226
227 static BOOL
228 dkt_via_kfile(transport_ctx * tctx, struct ob_dkim * dkim, const uschar ** err)
229 {
230 int dkim_fd;
231 int save_errno = 0;
232 BOOL rc;
233 uschar * dkim_spool_name;
234 gstring * dkim_signature;
235 int options, dlen;
236 off_t k_file_size;
237 const uschar * errstr;
238
239 dkim_spool_name = spool_fname(US"input", message_subdir, message_id,
240                     string_sprintf("-%d-K", (int)getpid()));
241
242 DEBUG(D_transport) debug_printf("dkim signing via file %s\n", dkim_spool_name);
243
244 if ((dkim_fd = Uopen(dkim_spool_name, O_RDWR|O_CREAT|O_TRUNC, SPOOL_MODE)) < 0)
245   {
246   /* Can't create spool file. Ugh. */
247   rc = FALSE;
248   save_errno = errno;
249   *err = string_sprintf("dkim spoolfile create: %s", strerror(errno));
250   goto CLEANUP;
251   }
252
253 /* Call transport utility function to write the -K file; does the CRLF expansion
254 (but, in the CHUNKING case, neither dot-stuffing nor dot-termination). */
255
256   {
257   int save_fd = tctx->u.fd;
258   tctx->u.fd = dkim_fd;
259   options = tctx->options;
260   tctx->options &= ~topt_use_bdat;
261
262   rc = transport_write_message(tctx, 0);
263
264   tctx->u.fd = save_fd;
265   tctx->options = options;
266   }
267
268 /* Save error state. We must clean up before returning. */
269 if (!rc)
270   {
271   save_errno = errno;
272   goto CLEANUP;
273   }
274
275 #ifdef EXPERIMENTAL_ARC
276 arc_sign_init();
277 #endif
278
279 /* Feed the file to the goats^W DKIM lib.  At this point the dotstuffed
280 status of the file depends on the output of transport_write_message() just
281 above, which should be the result of the end_dot flag in tctx->options. */
282
283 dkim->dot_stuffed = !!(options & topt_end_dot);
284 if (!(dkim_signature = dkim_exim_sign(dkim_fd, 0, NULL, dkim, &errstr)))
285   {
286   dlen = 0;
287   if (!(rc = dkt_sign_fail(dkim, &save_errno)))
288     {
289     *err = errstr;
290     goto CLEANUP;
291     }
292   }
293 else
294   dlen = dkim_signature->ptr;
295
296 #ifdef EXPERIMENTAL_ARC
297 if (dkim->arc_signspec)                         /* Prepend ARC headers */
298   {
299   if (!(dkim_signature = arc_sign(dkim->arc_signspec, dkim_signature, USS err)))
300     goto CLEANUP;
301   dlen = dkim_signature->ptr;
302   }
303 #endif
304
305 #ifndef OS_SENDFILE
306 if (options & topt_use_bdat)
307 #endif
308   if ((k_file_size = lseek(dkim_fd, 0, SEEK_END)) < 0)
309     {
310     *err = string_sprintf("dkim spoolfile seek: %s", strerror(errno));
311     goto CLEANUP;
312     }
313
314 if (options & topt_use_bdat)
315   {
316   /* On big messages output a precursor chunk to get any pipelined
317   MAIL & RCPT commands flushed, then reap the responses so we can
318   error out on RCPT rejects before sending megabytes. */
319
320   if (  dlen + k_file_size > DELIVER_OUT_BUFFER_SIZE
321      && dlen > 0)
322     {
323     if (  tctx->chunk_cb(tctx, dlen, 0) != OK
324        || !transport_write_block(tctx,
325                     dkim_signature->s, dlen, FALSE)
326        || tctx->chunk_cb(tctx, 0, tc_reap_prev) != OK
327        )
328       goto err;
329     dlen = 0;
330     }
331
332   /* Send the BDAT command for the entire message, as a single LAST-marked
333   chunk. */
334
335   if (tctx->chunk_cb(tctx, dlen + k_file_size, tc_chunk_last) != OK)
336     goto err;
337   }
338
339 if(dlen > 0 && !transport_write_block(tctx, dkim_signature->s, dlen, TRUE))
340   goto err;
341
342 if (!dkt_send_file(tctx->u.fd, dkim_fd, 0
343 #ifdef OS_SENDFILE
344   , k_file_size
345 #endif
346   ))
347   {
348   save_errno = errno;
349   rc = FALSE;
350   }
351
352 CLEANUP:
353   /* unlink -K file */
354   if (dkim_fd >= 0) (void)close(dkim_fd);
355   Uunlink(dkim_spool_name);
356   errno = save_errno;
357   return rc;
358
359 err:
360   save_errno = errno;
361   rc = FALSE;
362   goto CLEANUP;
363 }
364
365
366
367 /***************************************************************************************************
368 *    External interface to write the message, while signing it with DKIM and/or Domainkeys         *
369 ***************************************************************************************************/
370
371 /* This function is a wrapper around transport_write_message().
372    It is only called from the smtp transport if DKIM or Domainkeys support
373    is compiled in.
374
375 Arguments:
376   As for transport_write_message() in transort.c, with additional arguments
377   for DKIM.
378
379 Returns:       TRUE on success; FALSE (with errno) for any failure
380 */
381
382 BOOL
383 dkim_transport_write_message(transport_ctx * tctx,
384   struct ob_dkim * dkim, const uschar ** err)
385 {
386 /* If we can't sign, just call the original function. */
387
388 if (  !(dkim->dkim_private_key && dkim->dkim_domain && dkim->dkim_selector)
389    && !dkim->force_bodyhash)
390   return transport_write_message(tctx, 0);
391
392 /* If there is no filter command set up, construct the message and calculate
393 a dkim signature of it, send the signature and a reconstructed message. This
394 avoids using a temprary file. */
395
396 if (  !transport_filter_argv
397    || !*transport_filter_argv
398    || !**transport_filter_argv
399    )
400   return dkt_direct(tctx, dkim, err);
401
402 /* Use the transport path to write a file, calculate a dkim signature,
403 send the signature and then send the file. */
404
405 return dkt_via_kfile(tctx, dkim, err);
406 }
407
408 #endif  /* whole file */
409
410 /* vi: aw ai sw=2
411 */
412 /* End of dkim_transport.c */