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