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