| 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 | /* Functions for writing spool files, and moving them about. */ |
| 9 | |
| 10 | |
| 11 | #include "exim.h" |
| 12 | |
| 13 | |
| 14 | |
| 15 | /************************************************* |
| 16 | * Deal with header writing errors * |
| 17 | *************************************************/ |
| 18 | |
| 19 | /* This function is called immediately after errors in writing the spool, with |
| 20 | errno still set. It creates an error message, depending on the circumstances. |
| 21 | If errmsg is NULL, it logs the message and panic-dies. Otherwise errmsg is set |
| 22 | to point to the message, and -1 is returned. This function makes the code of |
| 23 | spool_write_header() a bit neater. |
| 24 | |
| 25 | Arguments: |
| 26 | where SW_RECEIVING, SW_DELIVERING, or SW_MODIFYING |
| 27 | errmsg where to put the message; NULL => panic-die |
| 28 | s text to add to log string |
| 29 | temp_name name of temp file to unlink |
| 30 | f FILE to close, if not NULL |
| 31 | |
| 32 | Returns: -1 if errmsg is not NULL; otherwise doesn't return |
| 33 | */ |
| 34 | |
| 35 | static int |
| 36 | spool_write_error(int where, uschar **errmsg, uschar *s, uschar *temp_name, |
| 37 | FILE *f) |
| 38 | { |
| 39 | uschar *msg = where == SW_RECEIVING |
| 40 | ? string_sprintf("spool file %s error while receiving from %s: %s", s, |
| 41 | sender_fullhost ? sender_fullhost : sender_ident, |
| 42 | strerror(errno)) |
| 43 | : string_sprintf("spool file %s error while %s: %s", s, |
| 44 | where == SW_DELIVERING ? "delivering" : "modifying", |
| 45 | strerror(errno)); |
| 46 | |
| 47 | if (temp_name) Uunlink(temp_name); |
| 48 | if (f) (void)fclose(f); |
| 49 | |
| 50 | if (errmsg) |
| 51 | *errmsg = msg; |
| 52 | else |
| 53 | log_write(0, LOG_MAIN|LOG_PANIC_DIE, "%s", msg); |
| 54 | |
| 55 | return -1; |
| 56 | } |
| 57 | |
| 58 | |
| 59 | |
| 60 | /************************************************* |
| 61 | * Open file under temporary name * |
| 62 | *************************************************/ |
| 63 | |
| 64 | /* This is used for opening spool files under a temporary name, |
| 65 | with a single attempt at deleting if they already exist. |
| 66 | |
| 67 | Argument: temporary name for spool header file |
| 68 | Returns: file descriptor of open file, or < 0 on failure, with errno unchanged |
| 69 | */ |
| 70 | |
| 71 | int |
| 72 | spool_open_temp(uschar *temp_name) |
| 73 | { |
| 74 | int fd = Uopen(temp_name, O_RDWR|O_CREAT|O_EXCL, SPOOL_MODE); |
| 75 | |
| 76 | /* If the file already exists, something has gone wrong. This process may well |
| 77 | have previously created the file if it is delivering more than one address, but |
| 78 | it should have renamed it almost immediately. A file could, however, be left |
| 79 | around as a result of a system crash, and by coincidence this process might |
| 80 | have the same pid. We therefore have one go at unlinking it before giving up. |
| 81 | */ |
| 82 | |
| 83 | if (fd < 0 && errno == EEXIST) |
| 84 | { |
| 85 | DEBUG(D_any) debug_printf("%s exists: unlinking\n", temp_name); |
| 86 | Uunlink(temp_name); |
| 87 | fd = Uopen(temp_name, O_RDWR|O_CREAT|O_EXCL, SPOOL_MODE); |
| 88 | } |
| 89 | |
| 90 | /* If the file has been opened, make sure the file's group is the Exim gid, and |
| 91 | double-check the mode because the group setting doesn't always get set |
| 92 | automatically. */ |
| 93 | |
| 94 | if (fd >= 0) |
| 95 | if (exim_fchown(fd, exim_uid, exim_gid, temp_name) || fchmod(fd, SPOOL_MODE)) |
| 96 | { |
| 97 | DEBUG(D_any) debug_printf("failed setting perms on %s\n", temp_name); |
| 98 | (void) close(fd); fd = -1; |
| 99 | Uunlink(temp_name); |
| 100 | } |
| 101 | |
| 102 | return fd; |
| 103 | } |
| 104 | |
| 105 | |
| 106 | |
| 107 | static void |
| 108 | spool_var_write(FILE * fp, const uschar * name, const uschar * val) |
| 109 | { |
| 110 | if (is_tainted(val)) putc('-', fp); |
| 111 | fprintf(fp, "-%s %s\n", name, val); |
| 112 | } |
| 113 | |
| 114 | /************************************************* |
| 115 | * Write the header spool file * |
| 116 | *************************************************/ |
| 117 | |
| 118 | /* Returns the size of the file for success; zero for failure. The file is |
| 119 | written under a temporary name, and then renamed. It's done this way so that it |
| 120 | works with re-writing the file on message deferral as well as for the initial |
| 121 | write. Whenever this function is called, the data file for the message should |
| 122 | be open and locked, thus preventing any other exim process from working on this |
| 123 | message. |
| 124 | |
| 125 | Argument: |
| 126 | id the message id |
| 127 | where SW_RECEIVING, SW_DELIVERING, or SW_MODIFYING |
| 128 | errmsg where to put an error message; if NULL, panic-die on error |
| 129 | |
| 130 | Returns: the size of the header texts on success; |
| 131 | negative on writing failure, unless errmsg == NULL |
| 132 | */ |
| 133 | |
| 134 | int |
| 135 | spool_write_header(uschar *id, int where, uschar **errmsg) |
| 136 | { |
| 137 | int fd; |
| 138 | int size_correction; |
| 139 | FILE * fp; |
| 140 | struct stat statbuf; |
| 141 | uschar * tname; |
| 142 | uschar * fname; |
| 143 | |
| 144 | tname = spool_fname(US"input", message_subdir, US"hdr.", message_id); |
| 145 | |
| 146 | if ((fd = spool_open_temp(tname)) < 0) |
| 147 | return spool_write_error(where, errmsg, US"open", NULL, NULL); |
| 148 | fp = fdopen(fd, "wb"); |
| 149 | DEBUG(D_receive|D_deliver) debug_printf("Writing spool header file: %s\n", tname); |
| 150 | |
| 151 | /* We now have an open file to which the header data is to be written. Start |
| 152 | with the file's leaf name, to make the file self-identifying. Continue with the |
| 153 | identity of the submitting user, followed by the sender's address. The sender's |
| 154 | address is enclosed in <> because it might be the null address. Then write the |
| 155 | received time and the number of warning messages that have been sent. */ |
| 156 | |
| 157 | fprintf(fp, "%s-H\n", message_id); |
| 158 | fprintf(fp, "%.63s %ld %ld\n", originator_login, (long int)originator_uid, |
| 159 | (long int)originator_gid); |
| 160 | fprintf(fp, "<%s>\n", sender_address); |
| 161 | fprintf(fp, "%d %d\n", (int)received_time.tv_sec, warning_count); |
| 162 | |
| 163 | fprintf(fp, "-received_time_usec .%06d\n", (int)received_time.tv_usec); |
| 164 | |
| 165 | /* If there is information about a sending host, remember it. The HELO |
| 166 | data can be set for local SMTP as well as remote. */ |
| 167 | |
| 168 | if (sender_helo_name) spool_var_write(fp, US"helo_name", sender_helo_name); |
| 169 | |
| 170 | if (sender_host_address) |
| 171 | { |
| 172 | if (is_tainted(sender_host_address)) putc('-', fp); |
| 173 | fprintf(fp, "-host_address %s.%d\n", sender_host_address, sender_host_port); |
| 174 | if (sender_host_name) |
| 175 | spool_var_write(fp, US"host_name", sender_host_name); |
| 176 | if (sender_host_authenticated) |
| 177 | spool_var_write(fp, US"host_auth", sender_host_authenticated); |
| 178 | } |
| 179 | |
| 180 | /* Also about the interface a message came in on */ |
| 181 | |
| 182 | if (interface_address) |
| 183 | { |
| 184 | if (is_tainted(interface_address)) putc('-', fp); |
| 185 | fprintf(fp, "-interface_address %s.%d\n", interface_address, interface_port); |
| 186 | } |
| 187 | |
| 188 | if (smtp_active_hostname != primary_hostname) |
| 189 | spool_var_write(fp, US"active_hostname", smtp_active_hostname); |
| 190 | |
| 191 | /* Likewise for any ident information; for local messages this is |
| 192 | likely to be the same as originator_login, but will be different if |
| 193 | the originator was root, forcing a different ident. */ |
| 194 | |
| 195 | if (sender_ident) |
| 196 | spool_var_write(fp, US"ident", sender_ident); |
| 197 | |
| 198 | /* Ditto for the received protocol */ |
| 199 | |
| 200 | if (received_protocol) |
| 201 | spool_var_write(fp, US"received_protocol", received_protocol); |
| 202 | |
| 203 | /* Preserve any ACL variables that are set. */ |
| 204 | |
| 205 | tree_walk(acl_var_c, &acl_var_write, fp); |
| 206 | tree_walk(acl_var_m, &acl_var_write, fp); |
| 207 | |
| 208 | /* Now any other data that needs to be remembered. */ |
| 209 | |
| 210 | if (f.spool_file_wireformat) |
| 211 | fprintf(fp, "-spool_file_wireformat\n"); |
| 212 | else |
| 213 | fprintf(fp, "-body_linecount %d\n", body_linecount); |
| 214 | fprintf(fp, "-max_received_linelength %d\n", max_received_linelength); |
| 215 | |
| 216 | if (body_zerocount > 0) fprintf(fp, "-body_zerocount %d\n", body_zerocount); |
| 217 | |
| 218 | if (authenticated_id) |
| 219 | spool_var_write(fp, US"auth_id", authenticated_id); |
| 220 | if (authenticated_sender) |
| 221 | spool_var_write(fp, US"auth_sender", authenticated_sender); |
| 222 | |
| 223 | if (f.allow_unqualified_recipient) fprintf(fp, "-allow_unqualified_recipient\n"); |
| 224 | if (f.allow_unqualified_sender) fprintf(fp, "-allow_unqualified_sender\n"); |
| 225 | if (f.deliver_firsttime) fprintf(fp, "-deliver_firsttime\n"); |
| 226 | if (f.deliver_freeze) fprintf(fp, "-frozen " TIME_T_FMT "\n", deliver_frozen_at); |
| 227 | if (f.dont_deliver) fprintf(fp, "-N\n"); |
| 228 | if (host_lookup_deferred) fprintf(fp, "-host_lookup_deferred\n"); |
| 229 | if (host_lookup_failed) fprintf(fp, "-host_lookup_failed\n"); |
| 230 | if (f.sender_local) fprintf(fp, "-local\n"); |
| 231 | if (f.local_error_message) fprintf(fp, "-localerror\n"); |
| 232 | #ifdef HAVE_LOCAL_SCAN |
| 233 | if (local_scan_data) spool_var_write(fp, US"local_scan", local_scan_data); |
| 234 | #endif |
| 235 | #ifdef WITH_CONTENT_SCAN |
| 236 | if (spam_bar) spool_var_write(fp, US"spam_bar", spam_bar); |
| 237 | if (spam_score) spool_var_write(fp, US"spam_score", spam_score); |
| 238 | if (spam_score_int) spool_var_write(fp, US"spam_score_int", spam_score_int); |
| 239 | #endif |
| 240 | if (f.deliver_manual_thaw) fprintf(fp, "-manual_thaw\n"); |
| 241 | if (f.sender_set_untrusted) fprintf(fp, "-sender_set_untrusted\n"); |
| 242 | |
| 243 | #ifdef EXPERIMENTAL_BRIGHTMAIL |
| 244 | if (bmi_verdicts) spool_var_write(fp, US"bmi_verdicts", bmi_verdicts); |
| 245 | #endif |
| 246 | |
| 247 | #ifndef DISABLE_TLS |
| 248 | if (tls_in.certificate_verified) fprintf(fp, "-tls_certificate_verified\n"); |
| 249 | if (tls_in.cipher) spool_var_write(fp, US"tls_cipher", tls_in.cipher); |
| 250 | if (tls_in.peercert) |
| 251 | { |
| 252 | if (tls_export_cert(big_buffer, big_buffer_size, tls_in.peercert)) |
| 253 | fprintf(fp, "--tls_peercert %s\n", CS big_buffer); |
| 254 | } |
| 255 | if (tls_in.peerdn) spool_var_write(fp, US"tls_peerdn", string_printing(tls_in.peerdn)); |
| 256 | if (tls_in.sni) spool_var_write(fp, US"tls_sni", string_printing(tls_in.sni)); |
| 257 | if (tls_in.ourcert) |
| 258 | { |
| 259 | if (tls_export_cert(big_buffer, big_buffer_size, tls_in.ourcert)) |
| 260 | fprintf(fp, "-tls_ourcert %s\n", CS big_buffer); |
| 261 | } |
| 262 | if (tls_in.ocsp) fprintf(fp, "-tls_ocsp %d\n", tls_in.ocsp); |
| 263 | # ifdef EXPERIMENTAL_TLS_RESUME |
| 264 | fprintf(fp, "-tls_resumption %c\n", 'A' + tls_in.resumption); |
| 265 | # endif |
| 266 | if (tls_in.ver) spool_var_write(fp, US"tls_ver", tls_in.ver); |
| 267 | #endif |
| 268 | |
| 269 | #ifdef SUPPORT_I18N |
| 270 | if (message_smtputf8) |
| 271 | { |
| 272 | fprintf(fp, "-smtputf8\n"); |
| 273 | if (message_utf8_downconvert) |
| 274 | fprintf(fp, "-utf8_%sdowncvt\n", message_utf8_downconvert < 0 ? "opt" : ""); |
| 275 | } |
| 276 | #endif |
| 277 | |
| 278 | /* Write the dsn flags to the spool header file */ |
| 279 | DEBUG(D_deliver) debug_printf("DSN: Write SPOOL: -dsn_envid %s\n", dsn_envid); |
| 280 | if (dsn_envid) fprintf(fp, "-dsn_envid %s\n", dsn_envid); |
| 281 | DEBUG(D_deliver) debug_printf("DSN: Write SPOOL :-dsn_ret %d\n", dsn_ret); |
| 282 | if (dsn_ret) fprintf(fp, "-dsn_ret %d\n", dsn_ret); |
| 283 | |
| 284 | /* To complete the envelope, write out the tree of non-recipients, followed by |
| 285 | the list of recipients. These won't be disjoint the first time, when no |
| 286 | checking has been done. If a recipient is a "one-time" alias, it is followed by |
| 287 | a space and its parent address number (pno). */ |
| 288 | |
| 289 | tree_write(tree_nonrecipients, fp); |
| 290 | fprintf(fp, "%d\n", recipients_count); |
| 291 | for (int i = 0; i < recipients_count; i++) |
| 292 | { |
| 293 | recipient_item *r = recipients_list + i; |
| 294 | |
| 295 | DEBUG(D_deliver) debug_printf("DSN: Flags: 0x%x\n", r->dsn_flags); |
| 296 | |
| 297 | if (r->pno < 0 && !r->errors_to && r->dsn_flags == 0) |
| 298 | fprintf(fp, "%s\n", r->address); |
| 299 | else |
| 300 | { |
| 301 | uschar * errors_to = r->errors_to ? r->errors_to : US""; |
| 302 | /* for DSN SUPPORT extend exim 4 spool in a compatible way by |
| 303 | adding new values upfront and add flag 0x02 */ |
| 304 | uschar * orcpt = r->orcpt ? r->orcpt : US""; |
| 305 | |
| 306 | fprintf(fp, "%s %s %d,%d %s %d,%d#3\n", r->address, orcpt, Ustrlen(orcpt), |
| 307 | r->dsn_flags, errors_to, Ustrlen(errors_to), r->pno); |
| 308 | } |
| 309 | |
| 310 | DEBUG(D_deliver) debug_printf("DSN: **** SPOOL_OUT - " |
| 311 | "address: <%s> errorsto: <%s> orcpt: <%s> dsn_flags: 0x%x\n", |
| 312 | r->address, r->errors_to, r->orcpt, r->dsn_flags); |
| 313 | } |
| 314 | |
| 315 | /* Put a blank line before the headers */ |
| 316 | |
| 317 | fprintf(fp, "\n"); |
| 318 | |
| 319 | /* Save the size of the file so far so we can subtract it from the final length |
| 320 | to get the actual size of the headers. */ |
| 321 | |
| 322 | fflush(fp); |
| 323 | if (fstat(fd, &statbuf)) |
| 324 | return spool_write_error(where, errmsg, US"fstat", tname, fp); |
| 325 | size_correction = statbuf.st_size; |
| 326 | |
| 327 | /* Finally, write out the message's headers. To make it easier to read them |
| 328 | in again, precede each one with the count of its length. Make the count fixed |
| 329 | length to aid human eyes when debugging and arrange for it not be included in |
| 330 | the size. It is followed by a space for normal headers, a flagging letter for |
| 331 | various other headers, or an asterisk for old headers that have been rewritten. |
| 332 | These are saved as a record for debugging. Don't included them in the message's |
| 333 | size. */ |
| 334 | |
| 335 | for (header_line * h = header_list; h; h = h->next) |
| 336 | { |
| 337 | fprintf(fp, "%03d%c %s", h->slen, h->type, h->text); |
| 338 | size_correction += 5; |
| 339 | if (h->type == '*') size_correction += h->slen; |
| 340 | } |
| 341 | |
| 342 | /* Flush and check for any errors while writing */ |
| 343 | |
| 344 | if (fflush(fp) != 0 || ferror(fp)) |
| 345 | return spool_write_error(where, errmsg, US"write", tname, fp); |
| 346 | |
| 347 | /* Force the file's contents to be written to disk. Note that fflush() |
| 348 | just pushes it out of C, and fclose() doesn't guarantee to do the write |
| 349 | either. That's just the way Unix works... */ |
| 350 | |
| 351 | if (EXIMfsync(fileno(fp)) < 0) |
| 352 | return spool_write_error(where, errmsg, US"sync", tname, fp); |
| 353 | |
| 354 | /* Get the size of the file, and close it. */ |
| 355 | |
| 356 | if (fstat(fd, &statbuf) != 0) |
| 357 | return spool_write_error(where, errmsg, US"fstat", tname, NULL); |
| 358 | if (fclose(fp) != 0) |
| 359 | return spool_write_error(where, errmsg, US"close", tname, NULL); |
| 360 | |
| 361 | /* Rename the file to its correct name, thereby replacing any previous |
| 362 | incarnation. */ |
| 363 | |
| 364 | fname = spool_fname(US"input", message_subdir, id, US"-H"); |
| 365 | DEBUG(D_receive|D_deliver) debug_printf("Renaming spool header file: %s\n", fname); |
| 366 | |
| 367 | if (Urename(tname, fname) < 0) |
| 368 | return spool_write_error(where, errmsg, US"rename", tname, NULL); |
| 369 | |
| 370 | /* Linux (and maybe other OS?) does not automatically sync a directory after |
| 371 | an operation like rename. We therefore have to do it forcibly ourselves in |
| 372 | these cases, to make sure the file is actually accessible on disk, as opposed |
| 373 | to just the data being accessible from a file in lost+found. Linux also has |
| 374 | O_DIRECTORY, for opening a directory. |
| 375 | |
| 376 | However, it turns out that some file systems (some versions of NFS?) do not |
| 377 | support directory syncing. It seems safe enough to ignore EINVAL to cope with |
| 378 | these cases. One hack on top of another... but that's life. */ |
| 379 | |
| 380 | #ifdef NEED_SYNC_DIRECTORY |
| 381 | |
| 382 | tname = spool_fname(US"input", message_subdir, US".", US""); |
| 383 | |
| 384 | # ifndef O_DIRECTORY |
| 385 | # define O_DIRECTORY 0 |
| 386 | # endif |
| 387 | |
| 388 | if ((fd = Uopen(tname, O_RDONLY|O_DIRECTORY, 0)) < 0) |
| 389 | return spool_write_error(where, errmsg, US"directory open", fname, NULL); |
| 390 | |
| 391 | if (EXIMfsync(fd) < 0 && errno != EINVAL) |
| 392 | return spool_write_error(where, errmsg, US"directory sync", fname, NULL); |
| 393 | |
| 394 | if (close(fd) < 0) |
| 395 | return spool_write_error(where, errmsg, US"directory close", fname, NULL); |
| 396 | |
| 397 | #endif /* NEED_SYNC_DIRECTORY */ |
| 398 | |
| 399 | /* Return the number of characters in the headers, which is the file size, less |
| 400 | the preliminary stuff, less the additional count fields on the headers. */ |
| 401 | |
| 402 | DEBUG(D_receive) debug_printf("Size of headers = %d\n", |
| 403 | (int)(statbuf.st_size - size_correction)); |
| 404 | |
| 405 | return statbuf.st_size - size_correction; |
| 406 | } |
| 407 | |
| 408 | |
| 409 | /************************************************ |
| 410 | * Make a hard link * |
| 411 | ************************************************/ |
| 412 | |
| 413 | /* Used by spool_move_message() below. Note re the use of sprintf(): the value |
| 414 | of spool_directory is checked to ensure that it is less than 200 characters at |
| 415 | start-up time. |
| 416 | |
| 417 | Arguments: |
| 418 | dir base directory name |
| 419 | dq destiinationqueue name |
| 420 | subdir subdirectory name |
| 421 | id message id |
| 422 | suffix suffix to add to id |
| 423 | from source directory prefix |
| 424 | to destination directory prefix |
| 425 | noentok if TRUE, absence of file is not an error |
| 426 | |
| 427 | Returns: TRUE if all went well |
| 428 | FALSE, having panic logged if not |
| 429 | */ |
| 430 | |
| 431 | static BOOL |
| 432 | make_link(uschar *dir, uschar * dq, uschar *subdir, uschar *id, uschar *suffix, |
| 433 | uschar *from, uschar *to, BOOL noentok) |
| 434 | { |
| 435 | uschar * fname = spool_fname(string_sprintf("%s%s", from, dir), subdir, id, suffix); |
| 436 | uschar * tname = spool_q_fname(string_sprintf("%s%s", to, dir), dq, subdir, id, suffix); |
| 437 | if (Ulink(fname, tname) < 0 && (!noentok || errno != ENOENT)) |
| 438 | { |
| 439 | log_write(0, LOG_MAIN|LOG_PANIC, "link(\"%s\", \"%s\") failed while moving " |
| 440 | "message: %s", fname, tname, strerror(errno)); |
| 441 | return FALSE; |
| 442 | } |
| 443 | return TRUE; |
| 444 | } |
| 445 | |
| 446 | |
| 447 | |
| 448 | /************************************************ |
| 449 | * Break a link * |
| 450 | ************************************************/ |
| 451 | |
| 452 | /* Used by spool_move_message() below. Note re the use of sprintf(): the value |
| 453 | of spool_directory is checked to ensure that it is less than 200 characters at |
| 454 | start-up time. |
| 455 | |
| 456 | Arguments: |
| 457 | dir base directory name |
| 458 | subdir subdirectory name |
| 459 | id message id |
| 460 | suffix suffix to add to id |
| 461 | from source directory prefix |
| 462 | noentok if TRUE, absence of file is not an error |
| 463 | |
| 464 | Returns: TRUE if all went well |
| 465 | FALSE, having panic logged if not |
| 466 | */ |
| 467 | |
| 468 | static BOOL |
| 469 | break_link(uschar *dir, uschar *subdir, uschar *id, uschar *suffix, uschar *from, |
| 470 | BOOL noentok) |
| 471 | { |
| 472 | uschar * fname = spool_fname(string_sprintf("%s%s", from, dir), subdir, id, suffix); |
| 473 | if (Uunlink(fname) < 0 && (!noentok || errno != ENOENT)) |
| 474 | { |
| 475 | log_write(0, LOG_MAIN|LOG_PANIC, "unlink(\"%s\") failed while moving " |
| 476 | "message: %s", fname, strerror(errno)); |
| 477 | return FALSE; |
| 478 | } |
| 479 | return TRUE; |
| 480 | } |
| 481 | |
| 482 | |
| 483 | |
| 484 | /************************************************ |
| 485 | * Move message files * |
| 486 | ************************************************/ |
| 487 | |
| 488 | /* Move the files for a message (-H, -D, and msglog) from one directory (or |
| 489 | hierarchy) to another. It is assume that there is no -J file in existence when |
| 490 | this is done. |
| 491 | |
| 492 | Arguments: |
| 493 | id the id of the message to be delivered |
| 494 | subdir the subdirectory name, or an empty string |
| 495 | from a prefix for "input" or "msglog" for where the message is now |
| 496 | to a prefix for "input" or "msglog" for where the message is to go |
| 497 | |
| 498 | Returns: TRUE if all is well |
| 499 | FALSE if not, with error logged in panic and main logs |
| 500 | */ |
| 501 | |
| 502 | BOOL |
| 503 | spool_move_message(uschar *id, uschar *subdir, uschar *from, uschar *to) |
| 504 | { |
| 505 | uschar * dest_qname = queue_name_dest ? queue_name_dest : queue_name; |
| 506 | |
| 507 | /* Create any output directories that do not exist. */ |
| 508 | |
| 509 | (void) directory_make(spool_directory, |
| 510 | spool_q_sname(string_sprintf("%sinput", to), dest_qname, subdir), |
| 511 | INPUT_DIRECTORY_MODE, TRUE); |
| 512 | (void) directory_make(spool_directory, |
| 513 | spool_q_sname(string_sprintf("%smsglog", to), dest_qname, subdir), |
| 514 | INPUT_DIRECTORY_MODE, TRUE); |
| 515 | |
| 516 | /* Move the message by first creating new hard links for all the files, and |
| 517 | then removing the old links. When moving messages onto the main spool, the -H |
| 518 | file should be set up last, because that's the one that tells Exim there is a |
| 519 | message to be delivered, so we create its new link last and remove its old link |
| 520 | first. Programs that look at the alternate directories should follow the same |
| 521 | rule of waiting for a -H file before doing anything. When moving messages off |
| 522 | the mail spool, the -D file should be open and locked at the time, thus keeping |
| 523 | Exim's hands off. */ |
| 524 | |
| 525 | if (!make_link(US"msglog", dest_qname, subdir, id, US"", from, to, TRUE) || |
| 526 | !make_link(US"input", dest_qname, subdir, id, US"-D", from, to, FALSE) || |
| 527 | !make_link(US"input", dest_qname, subdir, id, US"-H", from, to, FALSE)) |
| 528 | return FALSE; |
| 529 | |
| 530 | if (!break_link(US"input", subdir, id, US"-H", from, FALSE) || |
| 531 | !break_link(US"input", subdir, id, US"-D", from, FALSE) || |
| 532 | !break_link(US"msglog", subdir, id, US"", from, TRUE)) |
| 533 | return FALSE; |
| 534 | |
| 535 | log_write(0, LOG_MAIN, "moved from %s%s%s%sinput, %smsglog to %s%s%s%sinput, %smsglog", |
| 536 | *queue_name?"(":"", *queue_name?queue_name:US"", *queue_name?") ":"", |
| 537 | from, from, |
| 538 | *dest_qname?"(":"", *dest_qname?dest_qname:US"", *dest_qname?") ":"", |
| 539 | to, to); |
| 540 | |
| 541 | return TRUE; |
| 542 | } |
| 543 | |
| 544 | |
| 545 | /* End of spool_out.c */ |
| 546 | /* vi: aw ai sw=2 |
| 547 | */ |