357eba9da2fcd22bcfaf7922157197c443de05e4
[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
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 /* externals, we must reset them on unspooling */
17 #ifdef WITH_OLD_DEMIME
18 extern int demime_ok;
19 extern struct file_extension *file_extensions;
20 #endif
21
22 extern int malware_ok;
23 extern int spam_ok;
24
25 int spool_mbox_ok = 0;
26 uschar spooled_message_id[MESSAGE_ID_LENGTH+1];
27
28 /* returns a pointer to the FILE, and puts the size in bytes into mbox_file_size
29 * normally, source_file_override is NULL */
30
31 FILE *
32 spool_mbox(unsigned long *mbox_file_size, const uschar *source_file_override)
33 {
34 uschar message_subdir[2];
35 uschar buffer[16384];
36 uschar *temp_string;
37 uschar *mbox_path;
38 FILE *mbox_file = NULL;
39 FILE *data_file = NULL;
40 FILE *yield = NULL;
41 header_line *my_headerlist;
42 struct stat statbuf;
43 int i, j;
44 void *reset_point = store_get(0);
45
46 mbox_path = string_sprintf("%s/scan/%s/%s.eml", spool_directory, message_id,
47 message_id);
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 mbox_file = modefopen(mbox_path, "wb", SPOOL_MODE);
63 if (mbox_file == NULL)
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 != NULL)
81 {
82 i = fwrite(temp_string, Ustrlen(temp_string), 1, mbox_file);
83 if (i != 1)
84 {
85 log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
86 mailbox headers to %s", mbox_path);
87 goto OUT;
88 }
89 }
90
91 /* write all header lines to mbox file */
92 my_headerlist = header_list;
93 for (my_headerlist = header_list; my_headerlist != NULL;
94 my_headerlist = my_headerlist->next)
95 {
96 /* skip deleted headers */
97 if (my_headerlist->type == '*') continue;
98
99 i = fwrite(my_headerlist->text, my_headerlist->slen, 1, mbox_file);
100 if (i != 1)
101 {
102 log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
103 message headers to %s", mbox_path);
104 goto OUT;
105 }
106 }
107
108 /* End headers */
109 if (fwrite("\n", 1, 1, mbox_file) != 1)
110 {
111 log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
112 message headers to %s", mbox_path);
113 goto OUT;
114 }
115
116 /* copy body file */
117 if (source_file_override == NULL)
118 {
119 message_subdir[1] = '\0';
120 for (i = 0; i < 2; i++)
121 {
122 message_subdir[0] = (split_spool_directory == (i == 0))? message_id[5] : 0;
123 temp_string = string_sprintf("%s/input/%s/%s-D", spool_directory,
124 message_subdir, message_id);
125 data_file = Ufopen(temp_string, "rb");
126 if (data_file != NULL) break;
127 }
128 }
129 else
130 data_file = Ufopen(source_file_override, "rb");
131
132 if (data_file == NULL)
133 {
134 log_write(0, LOG_MAIN|LOG_PANIC, "Could not open datafile for message %s",
135 message_id);
136 goto OUT;
137 }
138
139 /* The code used to use this line, but it doesn't work in Cygwin.
140
141 (void)fread(data_buffer, 1, 18, data_file);
142
143 What's happening is that spool_mbox used to use an fread to jump over the
144 file header. That fails under Cygwin because the header is locked, but
145 doing an fseek succeeds. We have to output the leading newline
146 explicitly, because the one in the file is parted of the locked area. */
147
148 if (!source_file_override)
149 (void)fseek(data_file, SPOOL_DATA_START_OFFSET, SEEK_SET);
150
151 do
152 {
153 j = fread(buffer, 1, sizeof(buffer), data_file);
154
155 if (j > 0)
156 {
157 i = fwrite(buffer, j, 1, mbox_file);
158 if (i != 1)
159 {
160 log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
161 message body to %s", mbox_path);
162 goto OUT;
163 }
164 }
165 } while (j > 0);
166
167 (void)fclose(mbox_file);
168 mbox_file = NULL;
169
170 Ustrncpy(spooled_message_id, message_id, sizeof(spooled_message_id));
171 spooled_message_id[sizeof(spooled_message_id)-1] = '\0';
172 spool_mbox_ok = 1;
173 }
174
175 /* get the size of the mbox message and open [message_id].eml file for reading*/
176 if (Ustat(mbox_path, &statbuf) != 0 ||
177 (yield = Ufopen(mbox_path,"rb")) == NULL)
178 {
179 log_write(0, LOG_MAIN|LOG_PANIC, "%s", string_open_failed(errno,
180 "scan file %s", mbox_path));
181 goto OUT;
182 }
183
184 *mbox_file_size = statbuf.st_size;
185
186 OUT:
187 if (data_file) (void)fclose(data_file);
188 if (mbox_file) (void)fclose(mbox_file);
189 store_reset(reset_point);
190 return yield;
191 }
192
193
194
195
196 /* remove mbox spool file, demimed files and temp directory */
197
198 void
199 unspool_mbox(void)
200 {
201
202 /* reset all exiscan state variables */
203 #ifdef WITH_OLD_DEMIME
204 demime_ok = 0;
205 demime_errorlevel = 0;
206 demime_reason = NULL;
207 file_extensions = NULL;
208 #endif
209
210 spam_ok = 0;
211 malware_ok = 0;
212
213 if (spool_mbox_ok && !no_mbox_unspool)
214 {
215 uschar *mbox_path;
216 uschar *file_path;
217 int n;
218 struct dirent *entry;
219 DIR *tempdir;
220
221 mbox_path = string_sprintf("%s/scan/%s", spool_directory, spooled_message_id);
222
223 tempdir = opendir(CS mbox_path);
224 if (!tempdir)
225 {
226 debug_printf("Unable to opendir(%s): %s\n", mbox_path, strerror(errno));
227 /* Just in case we still can: */
228 rmdir(CS mbox_path);
229 return;
230 }
231 /* loop thru dir & delete entries */
232 while((entry = readdir(tempdir)) != NULL)
233 {
234 uschar *name = US entry->d_name;
235 if (Ustrcmp(name, US".") == 0 || Ustrcmp(name, US"..") == 0) continue;
236
237 file_path = string_sprintf("%s/%s", mbox_path, name);
238 debug_printf("unspool_mbox(): unlinking '%s'\n", file_path);
239 n = unlink(CS file_path);
240 }
241
242 closedir(tempdir);
243
244 /* remove directory */
245 rmdir(CS mbox_path);
246 store_reset(mbox_path);
247 }
248 spool_mbox_ok = 0;
249 }
250
251 #endif