Merge branch 'master' into 4.next
[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
8 /* Code for setting up a MBOX style spool file inside a /scan/<msgid>
9 sub directory of exim's spool directory. */
10
11 #include "exim.h"
12 #ifdef WITH_CONTENT_SCAN
13
14 /* externals, we must reset them on unspooling */
15 #ifdef WITH_OLD_DEMIME
16 extern int demime_ok;
17 extern struct file_extension *file_extensions;
18 #endif
19
20 extern int malware_ok;
21 extern int spam_ok;
22
23 int spool_mbox_ok = 0;
24 uschar spooled_message_id[17];
25
26 /* returns a pointer to the FILE, and puts the size in bytes into mbox_file_size
27 * normally, source_file_override is NULL */
28
29 FILE *
30 spool_mbox(unsigned long *mbox_file_size, const uschar *source_file_override)
31 {
32 uschar message_subdir[2];
33 uschar buffer[16384];
34 uschar *temp_string;
35 uschar *mbox_path;
36 FILE *mbox_file = NULL;
37 FILE *data_file = NULL;
38 FILE *yield = NULL;
39 header_line *my_headerlist;
40 struct stat statbuf;
41 int i, j;
42 void *reset_point = store_get(0);
43
44 mbox_path = string_sprintf("%s/scan/%s/%s.eml", spool_directory, message_id,
45 message_id);
46
47 /* Skip creation if already spooled out as mbox file */
48 if (!spool_mbox_ok)
49 {
50 /* create temp directory inside scan dir, directory_make works recursively */
51 temp_string = string_sprintf("scan/%s", message_id);
52 if (!directory_make(spool_directory, temp_string, 0750, FALSE))
53 {
54 log_write(0, LOG_MAIN|LOG_PANIC, "%s", string_open_failed(errno,
55 "scan directory %s/scan/%s", spool_directory, temp_string));
56 goto OUT;
57 }
58
59 /* open [message_id].eml file for writing */
60 mbox_file = modefopen(mbox_path, "wb", SPOOL_MODE);
61 if (mbox_file == NULL)
62 {
63 log_write(0, LOG_MAIN|LOG_PANIC, "%s", string_open_failed(errno,
64 "scan file %s", mbox_path));
65 goto OUT;
66 }
67
68 /* Generate mailbox headers. The $received_for variable is (up to at least
69 Exim 4.64) never set here, because it is only set when expanding the
70 contents of the Received: header line. However, the code below will use it
71 if it should become available in future. */
72
73 temp_string = expand_string(
74 US"From ${if def:return_path{$return_path}{MAILER-DAEMON}} ${tod_bsdinbox}\n"
75 "${if def:sender_address{X-Envelope-From: <${sender_address}>\n}}"
76 "${if def:recipients{X-Envelope-To: ${recipients}\n}}");
77
78 if (temp_string != NULL)
79 {
80 i = fwrite(temp_string, Ustrlen(temp_string), 1, mbox_file);
81 if (i != 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
89 /* write all header lines to mbox file */
90 my_headerlist = header_list;
91 for (my_headerlist = header_list; my_headerlist != NULL;
92 my_headerlist = my_headerlist->next)
93 {
94 /* skip deleted headers */
95 if (my_headerlist->type == '*') continue;
96
97 i = fwrite(my_headerlist->text, my_headerlist->slen, 1, mbox_file);
98 if (i != 1)
99 {
100 log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
101 message headers to %s", mbox_path);
102 goto OUT;
103 }
104 }
105
106 /* End headers */
107 if (fwrite("\n", 1, 1, mbox_file) != 1)
108 {
109 log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
110 message headers to %s", mbox_path);
111 goto OUT;
112 }
113
114 /* copy body file */
115 if (source_file_override == NULL)
116 {
117 message_subdir[1] = '\0';
118 for (i = 0; i < 2; i++)
119 {
120 message_subdir[0] = (split_spool_directory == (i == 0))? message_id[5] : 0;
121 temp_string = string_sprintf("%s/input/%s/%s-D", spool_directory,
122 message_subdir, message_id);
123 data_file = Ufopen(temp_string, "rb");
124 if (data_file != NULL) break;
125 }
126 }
127 else
128 data_file = Ufopen(source_file_override, "rb");
129
130 if (data_file == NULL)
131 {
132 log_write(0, LOG_MAIN|LOG_PANIC, "Could not open datafile for message %s",
133 message_id);
134 goto OUT;
135 }
136
137 /* The code used to use this line, but it doesn't work in Cygwin.
138
139 (void)fread(data_buffer, 1, 18, data_file);
140
141 What's happening is that spool_mbox used to use an fread to jump over the
142 file header. That fails under Cygwin because the header is locked, but
143 doing an fseek succeeds. We have to output the leading newline
144 explicitly, because the one in the file is parted of the locked area. */
145
146 if (!source_file_override)
147 (void)fseek(data_file, SPOOL_DATA_START_OFFSET, SEEK_SET);
148
149 do
150 {
151 j = fread(buffer, 1, sizeof(buffer), data_file);
152
153 if (j > 0)
154 {
155 i = fwrite(buffer, j, 1, mbox_file);
156 if (i != 1)
157 {
158 log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
159 message body to %s", mbox_path);
160 goto OUT;
161 }
162 }
163 } while (j > 0);
164
165 (void)fclose(mbox_file);
166 mbox_file = NULL;
167
168 Ustrcpy(spooled_message_id, message_id);
169 spool_mbox_ok = 1;
170 }
171
172 /* get the size of the mbox message and open [message_id].eml file for reading*/
173 if (Ustat(mbox_path, &statbuf) != 0 ||
174 (yield = Ufopen(mbox_path,"rb")) == NULL)
175 {
176 log_write(0, LOG_MAIN|LOG_PANIC, "%s", string_open_failed(errno,
177 "scan file %s", mbox_path));
178 goto OUT;
179 }
180
181 *mbox_file_size = statbuf.st_size;
182
183 OUT:
184 if (data_file) (void)fclose(data_file);
185 if (mbox_file) (void)fclose(mbox_file);
186 store_reset(reset_point);
187 return yield;
188 }
189
190
191
192
193 /* remove mbox spool file, demimed files and temp directory */
194
195 void
196 unspool_mbox(void)
197 {
198
199 /* reset all exiscan state variables */
200 #ifdef WITH_OLD_DEMIME
201 demime_ok = 0;
202 demime_errorlevel = 0;
203 demime_reason = NULL;
204 file_extensions = NULL;
205 #endif
206
207 spam_ok = 0;
208 malware_ok = 0;
209
210 if (spool_mbox_ok && !no_mbox_unspool)
211 {
212 uschar *mbox_path;
213 uschar *file_path;
214 int n;
215 struct dirent *entry;
216 DIR *tempdir;
217
218 mbox_path = string_sprintf("%s/scan/%s", spool_directory, spooled_message_id);
219
220 tempdir = opendir(CS mbox_path);
221 if (!tempdir)
222 {
223 debug_printf("Unable to opendir(%s): %s\n", mbox_path, strerror(errno));
224 /* Just in case we still can: */
225 rmdir(CS mbox_path);
226 return;
227 }
228 /* loop thru dir & delete entries */
229 while((entry = readdir(tempdir)) != NULL)
230 {
231 uschar *name = US entry->d_name;
232 if (Ustrcmp(name, US".") == 0 || Ustrcmp(name, US"..") == 0) continue;
233
234 file_path = string_sprintf("%s/%s", mbox_path, name);
235 debug_printf("unspool_mbox(): unlinking '%s'\n", file_path);
236 n = unlink(CS file_path);
237 }
238
239 closedir(tempdir);
240
241 /* remove directory */
242 rmdir(CS mbox_path);
243 store_reset(mbox_path);
244 }
245 spool_mbox_ok = 0;
246 }
247
248 #endif