1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
5 /* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003 - 2015
7 * Copyright (c) The Exim Maintainers 2016 - 2021
8 * SPDX-License-Identifier: GPL-2.0-or-later
11 /* Code for setting up a MBOX style spool file inside a /scan/<msgid>
12 sub directory of exim's spool directory. */
15 #ifdef WITH_CONTENT_SCAN
17 extern int malware_ok;
20 int spool_mbox_ok = 0;
21 uschar spooled_message_id[MESSAGE_ID_LENGTH+1];
24 Create an MBOX-style message file from the spooled files.
26 Returns a pointer to the FILE, and puts the size in bytes into mbox_file_size.
27 If mbox_fname is non-null, fill in a pointer to the name.
28 Normally, source_file_override is NULL
32 spool_mbox(unsigned long *mbox_file_size, const uschar *source_file_override,
35 uschar message_subdir[2];
39 FILE *mbox_file = NULL, *l_data_file = NULL, *yield = NULL;
44 mbox_path = string_sprintf("%s/scan/%s/%s.eml",
45 spool_directory, message_id, message_id);
46 if (mbox_fname) *mbox_fname = mbox_path;
48 reset_point = store_mark();
50 /* Skip creation if already spooled out as mbox file */
53 /* create temp directory inside scan dir, directory_make works recursively */
54 temp_string = string_sprintf("scan/%s", message_id);
55 if (!directory_make(spool_directory, temp_string, 0750, FALSE))
57 log_write(0, LOG_MAIN|LOG_PANIC, "%s",
58 string_open_failed("scan directory %s/scan/%s", spool_directory, temp_string));
62 /* open [message_id].eml file for writing */
64 if (!(mbox_file = modefopen(mbox_path, "wb", SPOOL_MODE)))
66 log_write(0, LOG_MAIN|LOG_PANIC, "%s",
67 string_open_failed("scan file %s", mbox_path));
71 /* Generate mailbox headers. The $received_for variable is (up to at least
72 Exim 4.64) never set here, because it is only set when expanding the
73 contents of the Received: header line. However, the code below will use it
74 if it should become available in future. */
76 temp_string = expand_string(
77 US"From ${if def:return_path{$return_path}{MAILER-DAEMON}} ${tod_bsdinbox}\n"
78 "${if def:sender_address{X-Envelope-From: <${sender_address}>\n}}"
79 "${if def:recipients{X-Envelope-To: ${recipients}\n}}");
82 if (fwrite(temp_string, Ustrlen(temp_string), 1, mbox_file) != 1)
84 log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
85 mailbox headers to %s", mbox_path);
89 /* write all non-deleted header lines to mbox file */
91 for (header_line * my_headerlist = header_list; my_headerlist;
92 my_headerlist = my_headerlist->next)
93 if (my_headerlist->type != '*')
94 if (fwrite(my_headerlist->text, my_headerlist->slen, 1, mbox_file) != 1)
96 log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
97 message headers to %s", mbox_path);
103 if (fwrite("\n", 1, 1, mbox_file) != 1)
105 log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
106 message headers to %s", mbox_path);
110 /* Copy body file. If the main receive still has it open then it is holding
111 a lock, and we must not close it (which releases the lock), so just use the
112 global file handle. */
114 if (source_file_override)
115 l_data_file = Ufopen(source_file_override, "rb");
116 else if (spool_data_file)
117 l_data_file = spool_data_file;
119 for (int i = 0; i < 2; i++)
121 set_subdir_str(message_subdir, message_id, i);
122 temp_string = spool_fname(US"input", message_subdir, message_id, US"-D");
123 if ((l_data_file = Ufopen(temp_string, "rb"))) break;
128 log_write(0, LOG_MAIN|LOG_PANIC, "Could not open datafile for message %s",
133 /* The code used to use this line, but it doesn't work in Cygwin.
135 (void)fread(data_buffer, 1, 18, l_data_file);
137 What's happening is that spool_mbox used to use an fread to jump over the
138 file header. That fails under Cygwin because the header is locked, but
139 doing an fseek succeeds. We have to output the leading newline
140 explicitly, because the one in the file is parted of the locked area. */
142 if (!source_file_override)
143 (void)fseek(l_data_file, spool_data_start_offset(message_id), SEEK_SET);
149 if (!f.spool_file_wireformat || source_file_override)
150 j = fread(buffer, 1, sizeof(buffer), l_data_file);
151 else /* needs CRLF -> NL */
152 if ((s = US fgets(CS buffer, sizeof(buffer), l_data_file)))
154 uschar * p = s + Ustrlen(s) - 1;
156 if (*p == '\n' && p[-1] == '\r')
159 ungetc(*p--, l_data_file);
167 if (fwrite(buffer, j, 1, mbox_file) != 1)
169 log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
170 message body to %s", mbox_path);
175 (void)fclose(mbox_file);
178 Ustrncpy(spooled_message_id, message_id, sizeof(spooled_message_id));
179 spooled_message_id[sizeof(spooled_message_id)-1] = '\0';
183 /* get the size of the mbox message and open [message_id].eml file for reading*/
185 if ( !(yield = Ufopen(mbox_path,"rb"))
186 || fstat(fileno(yield), &statbuf) != 0
188 log_write(0, LOG_MAIN|LOG_PANIC, "%s",
189 string_open_failed( "scan file %s", mbox_path));
191 *mbox_file_size = statbuf.st_size;
194 if (l_data_file && !spool_data_file) (void)fclose(l_data_file);
195 if (mbox_file) (void)fclose(mbox_file);
196 store_reset(reset_point);
204 /* remove mbox spool file and temp directory */
211 if (spool_mbox_ok && !f.no_mbox_unspool)
215 rmark reset_point = store_mark();
216 uschar * mbox_path = string_sprintf("%s/scan/%s", spool_directory, spooled_message_id);
218 if (!(tempdir = exim_opendir(mbox_path)))
220 debug_printf("Unable to opendir(%s): %s\n", mbox_path, strerror(errno));
221 /* Just in case we still can: */
222 (void) rmdir(CS mbox_path);
225 /* loop thru dir & delete entries */
226 for (struct dirent *entry; entry = readdir(tempdir); )
228 uschar *name = US entry->d_name;
229 if (Ustrcmp(name, US".") == 0 || Ustrcmp(name, US"..") == 0) continue;
231 file_path = string_sprintf("%s/%s", mbox_path, name);
232 debug_printf("unspool_mbox(): unlinking '%s'\n", file_path);
233 if (unlink(CS file_path) != 0)
234 log_write(0, LOG_MAIN|LOG_PANIC, "unlink(%s): %s", file_path, strerror(errno));
239 /* remove directory */
240 if (rmdir(CS mbox_path) != 0)
241 log_write(0, LOG_MAIN|LOG_PANIC, "rmdir(%s): %s", mbox_path, strerror(errno));
242 store_reset(reset_point);