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