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