Taint enforce: directory open backstops, single-key search filename
[exim.git] / src / src / spool_mbox.c
1 /*************************************************
2 * Exim - an Internet mail transport agent *
3 *************************************************/
4
5 /* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003 - 2015
6 * License: GPL
7 * Copyright (c) The Exim Maintainers 2016 - 2018
8 */
9
10 /* Code for setting up a MBOX style spool file inside a /scan/<msgid>
11 sub directory of exim's spool directory. */
12
13 #include "exim.h"
14 #ifdef WITH_CONTENT_SCAN
15
16 extern int malware_ok;
17 extern int spam_ok;
18
19 int spool_mbox_ok = 0;
20 uschar spooled_message_id[MESSAGE_ID_LENGTH+1];
21
22 /*
23 Create an MBOX-style message file from the spooled files.
24
25 Returns a pointer to the FILE, and puts the size in bytes into mbox_file_size.
26 If mbox_fname is non-null, fill in a pointer to the name.
27 Normally, source_file_override is NULL
28 */
29
30 FILE *
31 spool_mbox(unsigned long *mbox_file_size, const uschar *source_file_override,
32 uschar ** mbox_fname)
33 {
34 uschar message_subdir[2];
35 uschar buffer[16384];
36 uschar *temp_string;
37 uschar *mbox_path;
38 FILE *mbox_file = NULL, *l_data_file = NULL, *yield = NULL;
39 struct stat statbuf;
40 int j;
41 rmark reset_point;
42
43 mbox_path = string_sprintf("%s/scan/%s/%s.eml",
44 spool_directory, message_id, message_id);
45 if (mbox_fname) *mbox_fname = mbox_path;
46
47 reset_point = store_mark();
48
49 /* Skip creation if already spooled out as mbox file */
50 if (!spool_mbox_ok)
51 {
52 /* create temp directory inside scan dir, directory_make works recursively */
53 temp_string = string_sprintf("scan/%s", message_id);
54 if (!directory_make(spool_directory, temp_string, 0750, FALSE))
55 {
56 log_write(0, LOG_MAIN|LOG_PANIC, "%s", string_open_failed(errno,
57 "scan directory %s/scan/%s", spool_directory, temp_string));
58 goto OUT;
59 }
60
61 /* open [message_id].eml file for writing */
62
63 if (!(mbox_file = modefopen(mbox_path, "wb", SPOOL_MODE)))
64 {
65 log_write(0, LOG_MAIN|LOG_PANIC, "%s", string_open_failed(errno,
66 "scan file %s", mbox_path));
67 goto OUT;
68 }
69
70 /* Generate mailbox headers. The $received_for variable is (up to at least
71 Exim 4.64) never set here, because it is only set when expanding the
72 contents of the Received: header line. However, the code below will use it
73 if it should become available in future. */
74
75 temp_string = expand_string(
76 US"From ${if def:return_path{$return_path}{MAILER-DAEMON}} ${tod_bsdinbox}\n"
77 "${if def:sender_address{X-Envelope-From: <${sender_address}>\n}}"
78 "${if def:recipients{X-Envelope-To: ${recipients}\n}}");
79
80 if (temp_string)
81 if (fwrite(temp_string, Ustrlen(temp_string), 1, mbox_file) != 1)
82 {
83 log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
84 mailbox headers to %s", mbox_path);
85 goto OUT;
86 }
87
88 /* write all non-deleted header lines to mbox file */
89
90 for (header_line * my_headerlist = header_list; my_headerlist;
91 my_headerlist = my_headerlist->next)
92 if (my_headerlist->type != '*')
93 if (fwrite(my_headerlist->text, my_headerlist->slen, 1, mbox_file) != 1)
94 {
95 log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
96 message headers to %s", mbox_path);
97 goto OUT;
98 }
99
100 /* End headers */
101 if (fwrite("\n", 1, 1, mbox_file) != 1)
102 {
103 log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
104 message headers to %s", mbox_path);
105 goto OUT;
106 }
107
108 /* Copy body file. If the main receive still has it open then it is holding
109 a lock, and we must not close it (which releases the lock), so just use the
110 global file handle. */
111 if (source_file_override)
112 l_data_file = Ufopen(source_file_override, "rb");
113 else if (spool_data_file)
114 l_data_file = spool_data_file;
115 else
116 {
117 message_subdir[1] = '\0';
118 for (int i = 0; i < 2; i++)
119 {
120 set_subdir_str(message_subdir, message_id, i);
121 temp_string = spool_fname(US"input", message_subdir, message_id, US"-D");
122 if ((l_data_file = Ufopen(temp_string, "rb"))) break;
123 }
124 }
125
126 if (!l_data_file)
127 {
128 log_write(0, LOG_MAIN|LOG_PANIC, "Could not open datafile for message %s",
129 message_id);
130 goto OUT;
131 }
132
133 /* The code used to use this line, but it doesn't work in Cygwin.
134
135 (void)fread(data_buffer, 1, 18, l_data_file);
136
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. */
141
142 if (!source_file_override)
143 (void)fseek(l_data_file, SPOOL_DATA_START_OFFSET, SEEK_SET);
144
145 do
146 {
147 uschar * s;
148
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)))
153 {
154 uschar * p = s + Ustrlen(s) - 1;
155
156 if (*p == '\n' && p[-1] == '\r')
157 *--p = '\n';
158 else if (*p == '\r')
159 ungetc(*p--, l_data_file);
160
161 j = p - buffer;
162 }
163 else
164 j = 0;
165
166 if (j > 0)
167 if (fwrite(buffer, j, 1, mbox_file) != 1)
168 {
169 log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
170 message body to %s", mbox_path);
171 goto OUT;
172 }
173 } while (j > 0);
174
175 (void)fclose(mbox_file);
176 mbox_file = NULL;
177
178 Ustrncpy(spooled_message_id, message_id, sizeof(spooled_message_id));
179 spooled_message_id[sizeof(spooled_message_id)-1] = '\0';
180 spool_mbox_ok = 1;
181 }
182
183 /* get the size of the mbox message and open [message_id].eml file for reading*/
184
185 if ( !(yield = Ufopen(mbox_path,"rb"))
186 || fstat(fileno(yield), &statbuf) != 0
187 )
188 log_write(0, LOG_MAIN|LOG_PANIC, "%s", string_open_failed(errno,
189 "scan file %s", mbox_path));
190 else
191 *mbox_file_size = statbuf.st_size;
192
193 OUT:
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);
197 return yield;
198 }
199
200
201
202
203
204 /* remove mbox spool file and temp directory */
205 void
206 unspool_mbox(void)
207 {
208 spam_ok = 0;
209 malware_ok = 0;
210
211 if (spool_mbox_ok && !f.no_mbox_unspool)
212 {
213 uschar *file_path;
214 DIR *tempdir;
215 rmark reset_point = store_mark();
216 uschar * mbox_path = string_sprintf("%s/scan/%s", spool_directory, spooled_message_id);
217
218 if (!(tempdir = exim_opendir(mbox_path)))
219 {
220 debug_printf("Unable to opendir(%s): %s\n", mbox_path, strerror(errno));
221 /* Just in case we still can: */
222 rmdir(CS mbox_path);
223 return;
224 }
225 /* loop thru dir & delete entries */
226 for (struct dirent *entry; entry = readdir(tempdir); )
227 {
228 uschar *name = US entry->d_name;
229 int dummy;
230 if (Ustrcmp(name, US".") == 0 || Ustrcmp(name, US"..") == 0) continue;
231
232 file_path = string_sprintf("%s/%s", mbox_path, name);
233 debug_printf("unspool_mbox(): unlinking '%s'\n", file_path);
234 dummy = unlink(CS file_path); dummy = dummy; /* compiler quietening */
235 }
236
237 closedir(tempdir);
238
239 /* remove directory */
240 rmdir(CS mbox_path);
241 store_reset(reset_point);
242 }
243 spool_mbox_ok = 0;
244 }
245
246 #endif