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