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