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