1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
6 * Copyright (c) The Exim Maintainers 2016 - 2023
7 * Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003 - 2015
9 * SPDX-License-Identifier: GPL-2.0-or-later
12 /* Code for setting up a MBOX style spool file inside a /scan/<msgid>
13 sub directory of exim's spool directory. */
16 #ifdef WITH_CONTENT_SCAN
18 extern int malware_ok;
21 int spool_mbox_ok = 0;
22 uschar spooled_message_id[MESSAGE_ID_LENGTH+1];
25 Create an MBOX-style message file from the spooled files.
27 Returns a pointer to the FILE, and puts the size in bytes into mbox_file_size.
28 If mbox_fname is non-null, fill in a pointer to the name.
29 Normally, source_file_override is NULL
33 spool_mbox(unsigned long *mbox_file_size, const uschar *source_file_override,
36 uschar message_subdir[2];
40 FILE *mbox_file = NULL, *l_data_file = NULL, *yield = NULL;
45 mbox_path = string_sprintf("%s/scan/%s/%s.eml",
46 spool_directory, message_id, message_id);
47 if (mbox_fname) *mbox_fname = mbox_path;
49 reset_point = store_mark();
51 /* Skip creation if already spooled out as mbox file */
54 /* create temp directory inside scan dir, directory_make works recursively */
55 temp_string = string_sprintf("scan/%s", message_id);
56 if (!directory_make(spool_directory, temp_string, 0750, FALSE))
58 log_write(0, LOG_MAIN|LOG_PANIC, "%s",
59 string_open_failed("scan directory %s/scan/%s", spool_directory, temp_string));
63 /* open [message_id].eml file for writing */
65 if (!(mbox_file = modefopen(mbox_path, "wb", SPOOL_MODE)))
67 log_write(0, LOG_MAIN|LOG_PANIC, "%s",
68 string_open_failed("scan file %s", mbox_path));
72 /* Generate mailbox headers. The $received_for variable is (up to at least
73 Exim 4.64) never set here, because it is only set when expanding the
74 contents of the Received: header line. However, the code below will use it
75 if it should become available in future. */
77 temp_string = expand_string(
78 US"From ${if def:return_path{$return_path}{MAILER-DAEMON}} ${tod_bsdinbox}\n"
79 "${if def:sender_address{X-Envelope-From: <${sender_address}>\n}}"
80 "${if def:recipients{X-Envelope-To: ${recipients}\n}}");
83 if (fwrite(temp_string, Ustrlen(temp_string), 1, mbox_file) != 1)
85 log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
86 mailbox headers to %s", mbox_path);
90 /* write all non-deleted header lines to mbox file */
92 for (header_line * my_headerlist = header_list; my_headerlist;
93 my_headerlist = my_headerlist->next)
94 if (my_headerlist->type != '*')
95 if (fwrite(my_headerlist->text, my_headerlist->slen, 1, mbox_file) != 1)
97 log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
98 message headers to %s", mbox_path);
104 if (fwrite("\n", 1, 1, mbox_file) != 1)
106 log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
107 message headers to %s", mbox_path);
111 /* Copy body file. If the main receive still has it open then it is holding
112 a lock, and we must not close it (which releases the lock), so just use the
113 global file handle. */
115 if (source_file_override)
116 l_data_file = Ufopen(source_file_override, "rb");
117 else if (spool_data_file)
118 l_data_file = spool_data_file;
120 for (int i = 0; i < 2; i++)
122 set_subdir_str(message_subdir, message_id, i);
123 temp_string = spool_fname(US"input", message_subdir, message_id, US"-D");
124 if ((l_data_file = Ufopen(temp_string, "rb"))) break;
129 log_write(0, LOG_MAIN|LOG_PANIC, "Could not open datafile for message %s",
134 /* The code used to use this line, but it doesn't work in Cygwin.
136 (void)fread(data_buffer, 1, 18, l_data_file);
138 What's happening is that spool_mbox used to use an fread to jump over the
139 file header. That fails under Cygwin because the header is locked, but
140 doing an fseek succeeds. We have to output the leading newline
141 explicitly, because the one in the file is parted of the locked area. */
143 if (!source_file_override)
144 (void)fseek(l_data_file, spool_data_start_offset(message_id), SEEK_SET);
150 if (!f.spool_file_wireformat || source_file_override)
151 j = fread(buffer, 1, sizeof(buffer), l_data_file);
152 else /* needs CRLF -> NL */
153 if ((s = US fgets(CS buffer, sizeof(buffer), l_data_file)))
155 uschar * p = s + Ustrlen(s) - 1;
157 if (*p == '\n' && p[-1] == '\r')
160 ungetc(*p--, l_data_file);
168 if (fwrite(buffer, j, 1, mbox_file) != 1)
170 log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
171 message body to %s", mbox_path);
176 (void)fclose(mbox_file);
179 Ustrncpy(spooled_message_id, message_id, sizeof(spooled_message_id));
180 spooled_message_id[sizeof(spooled_message_id)-1] = '\0';
184 /* get the size of the mbox message and open [message_id].eml file for reading*/
186 if ( !(yield = Ufopen(mbox_path,"rb"))
187 || fstat(fileno(yield), &statbuf) != 0
189 log_write(0, LOG_MAIN|LOG_PANIC, "%s",
190 string_open_failed( "scan file %s", mbox_path));
192 *mbox_file_size = statbuf.st_size;
195 if (l_data_file && !spool_data_file) (void)fclose(l_data_file);
196 if (mbox_file) (void)fclose(mbox_file);
197 store_reset(reset_point);
205 /* remove mbox spool file and temp directory */
212 if (spool_mbox_ok && !f.no_mbox_unspool)
216 rmark reset_point = store_mark();
217 uschar * mbox_path = string_sprintf("%s/scan/%s", spool_directory, spooled_message_id);
219 if (!(tempdir = exim_opendir(mbox_path)))
221 debug_printf("Unable to opendir(%s): %s\n", mbox_path, strerror(errno));
222 /* Just in case we still can: */
223 (void) rmdir(CS mbox_path);
226 /* loop thru dir & delete entries */
227 for (struct dirent *entry; entry = readdir(tempdir); )
229 uschar *name = US entry->d_name;
230 if (Ustrcmp(name, US".") == 0 || Ustrcmp(name, US"..") == 0) continue;
232 file_path = string_sprintf("%s/%s", mbox_path, name);
233 debug_printf("unspool_mbox(): unlinking '%s'\n", file_path);
234 if (unlink(CS file_path) != 0)
235 log_write(0, LOG_MAIN|LOG_PANIC, "unlink(%s): %s", file_path, strerror(errno));
240 /* remove directory */
241 if (rmdir(CS mbox_path) != 0)
242 log_write(0, LOG_MAIN|LOG_PANIC, "rmdir(%s): %s", mbox_path, strerror(errno));
243 store_reset(reset_point);