Merge branch 'multiqueue_336': Named queues
[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 = spool_fname(US"input", message_subdir, message_id, US"-D");
118 if ((data_file = Ufopen(temp_string, "rb"))) break;
119 }
120 }
121 else
122 data_file = Ufopen(source_file_override, "rb");
123
124 if (!data_file)
125 {
126 log_write(0, LOG_MAIN|LOG_PANIC, "Could not open datafile for message %s",
127 message_id);
128 goto OUT;
129 }
130
131 /* The code used to use this line, but it doesn't work in Cygwin.
132
133 (void)fread(data_buffer, 1, 18, data_file);
134
135 What's happening is that spool_mbox used to use an fread to jump over the
136 file header. That fails under Cygwin because the header is locked, but
137 doing an fseek succeeds. We have to output the leading newline
138 explicitly, because the one in the file is parted of the locked area. */
139
140 if (!source_file_override)
141 (void)fseek(data_file, SPOOL_DATA_START_OFFSET, SEEK_SET);
142
143 do
144 {
145 j = fread(buffer, 1, sizeof(buffer), data_file);
146
147 if (j > 0)
148 {
149 i = fwrite(buffer, j, 1, mbox_file);
150 if (i != 1)
151 {
152 log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
153 message body to %s", mbox_path);
154 goto OUT;
155 }
156 }
157 } while (j > 0);
158
159 (void)fclose(mbox_file);
160 mbox_file = NULL;
161
162 Ustrncpy(spooled_message_id, message_id, sizeof(spooled_message_id));
163 spooled_message_id[sizeof(spooled_message_id)-1] = '\0';
164 spool_mbox_ok = 1;
165 }
166
167 /* get the size of the mbox message and open [message_id].eml file for reading*/
168 if (Ustat(mbox_path, &statbuf) != 0 ||
169 (yield = Ufopen(mbox_path,"rb")) == NULL)
170 {
171 log_write(0, LOG_MAIN|LOG_PANIC, "%s", string_open_failed(errno,
172 "scan file %s", mbox_path));
173 goto OUT;
174 }
175
176 *mbox_file_size = statbuf.st_size;
177
178 OUT:
179 if (data_file) (void)fclose(data_file);
180 if (mbox_file) (void)fclose(mbox_file);
181 store_reset(reset_point);
182 return yield;
183 }
184
185
186
187
188
189 /* remove mbox spool file and temp directory */
190 void
191 unspool_mbox(void)
192 {
193 spam_ok = 0;
194 malware_ok = 0;
195
196 if (spool_mbox_ok && !no_mbox_unspool)
197 {
198 uschar *mbox_path;
199 uschar *file_path;
200 int n;
201 struct dirent *entry;
202 DIR *tempdir;
203
204 mbox_path = string_sprintf("%s/scan/%s", spool_directory, spooled_message_id);
205
206 tempdir = opendir(CS mbox_path);
207 if (!tempdir)
208 {
209 debug_printf("Unable to opendir(%s): %s\n", mbox_path, strerror(errno));
210 /* Just in case we still can: */
211 rmdir(CS mbox_path);
212 return;
213 }
214 /* loop thru dir & delete entries */
215 while((entry = readdir(tempdir)) != NULL)
216 {
217 uschar *name = US entry->d_name;
218 if (Ustrcmp(name, US".") == 0 || Ustrcmp(name, US"..") == 0) continue;
219
220 file_path = string_sprintf("%s/%s", mbox_path, name);
221 debug_printf("unspool_mbox(): unlinking '%s'\n", file_path);
222 n = unlink(CS file_path);
223 }
224
225 closedir(tempdir);
226
227 /* remove directory */
228 rmdir(CS mbox_path);
229 store_reset(mbox_path);
230 }
231 spool_mbox_ok = 0;
232 }
233
234 #endif