| 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 |
| 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->dkim_strict, US"1") == 0) || |
| 25 | (strcmpic(dkim->dkim_strict, 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 |
| 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 |
| 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 |
| 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 |
| 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 */ |