Update copyright year to 2018
[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;
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;
45
46 mbox_path = string_sprintf("%s/scan/%s/%s.eml",
47 spool_directory, message_id, message_id);
48 if (mbox_fname) *mbox_fname = mbox_path;
49
50 reset_point = store_get(0);
51
52 /* Skip creation if already spooled out as mbox file */
53 if (!spool_mbox_ok)
54 {
55 /* create temp directory inside scan dir, directory_make works recursively */
56 temp_string = string_sprintf("scan/%s", message_id);
57 if (!directory_make(spool_directory, temp_string, 0750, FALSE))
58 {
59 log_write(0, LOG_MAIN|LOG_PANIC, "%s", string_open_failed(errno,
60 "scan directory %s/scan/%s", spool_directory, temp_string));
61 goto OUT;
62 }
63
64 /* open [message_id].eml file for writing */
65
66 if (!(mbox_file = modefopen(mbox_path, "wb", SPOOL_MODE)))
67 {
68 log_write(0, LOG_MAIN|LOG_PANIC, "%s", string_open_failed(errno,
69 "scan file %s", mbox_path));
70 goto OUT;
71 }
72
73 /* Generate mailbox headers. The $received_for variable is (up to at least
74 Exim 4.64) never set here, because it is only set when expanding the
75 contents of the Received: header line. However, the code below will use it
76 if it should become available in future. */
77
78 temp_string = expand_string(
79 US"From ${if def:return_path{$return_path}{MAILER-DAEMON}} ${tod_bsdinbox}\n"
80 "${if def:sender_address{X-Envelope-From: <${sender_address}>\n}}"
81 "${if def:recipients{X-Envelope-To: ${recipients}\n}}");
82
83 if (temp_string)
84 if (fwrite(temp_string, Ustrlen(temp_string), 1, mbox_file) != 1)
85 {
86 log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
87 mailbox headers to %s", mbox_path);
88 goto OUT;
89 }
90
91 /* write all non-deleted header lines to mbox file */
92
93 for (my_headerlist = header_list; my_headerlist;
94 my_headerlist = my_headerlist->next)
95 if (my_headerlist->type != '*')
96 if (fwrite(my_headerlist->text, my_headerlist->slen, 1, mbox_file) != 1)
97 {
98 log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
99 message headers to %s", mbox_path);
100 goto OUT;
101 }
102
103 /* End headers */
104 if (fwrite("\n", 1, 1, mbox_file) != 1)
105 {
106 log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
107 message headers to %s", mbox_path);
108 goto OUT;
109 }
110
111 /* copy body file */
112 if (!source_file_override)
113 {
114 message_subdir[1] = '\0';
115 for (i = 0; i < 2; i++)
116 {
117 message_subdir[0] = split_spool_directory == (i == 0) ? message_id[5] : 0;
118 temp_string = spool_fname(US"input", message_subdir, message_id, US"-D");
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 uschar * s;
147
148 if (!spool_file_wireformat || source_file_override)
149 j = fread(buffer, 1, sizeof(buffer), data_file);
150 else /* needs CRLF -> NL */
151 if ((s = US fgets(CS buffer, sizeof(buffer), data_file)))
152 {
153 uschar * p = s + Ustrlen(s) - 1;
154
155 if (*p == '\n' && p[-1] == '\r')
156 *--p = '\n';
157 else if (*p == '\r')
158 ungetc(*p--, data_file);
159
160 j = p - buffer;
161 }
162 else
163 j = 0;
164
165 if (j > 0)
166 if (fwrite(buffer, j, 1, mbox_file) != 1)
167 {
168 log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
169 message body to %s", mbox_path);
170 goto OUT;
171 }
172 } while (j > 0);
173
174 (void)fclose(mbox_file);
175 mbox_file = NULL;
176
177 Ustrncpy(spooled_message_id, message_id, sizeof(spooled_message_id));
178 spooled_message_id[sizeof(spooled_message_id)-1] = '\0';
179 spool_mbox_ok = 1;
180 }
181
182 /* get the size of the mbox message and open [message_id].eml file for reading*/
183
184 if ( !(yield = Ufopen(mbox_path,"rb"))
185 || fstat(fileno(yield), &statbuf) != 0
186 )
187 log_write(0, LOG_MAIN|LOG_PANIC, "%s", string_open_failed(errno,
188 "scan file %s", mbox_path));
189 else
190 *mbox_file_size = statbuf.st_size;
191
192 OUT:
193 if (data_file) (void)fclose(data_file);
194 if (mbox_file) (void)fclose(mbox_file);
195 store_reset(reset_point);
196 return yield;
197 }
198
199
200
201
202
203 /* remove mbox spool file and temp directory */
204 void
205 unspool_mbox(void)
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 struct dirent *entry;
215 DIR *tempdir;
216
217 mbox_path = string_sprintf("%s/scan/%s", spool_directory, spooled_message_id);
218
219 if (!(tempdir = opendir(CS mbox_path)))
220 {
221 debug_printf("Unable to opendir(%s): %s\n", mbox_path, strerror(errno));
222 /* Just in case we still can: */
223 rmdir(CS mbox_path);
224 return;
225 }
226 /* loop thru dir & delete entries */
227 while((entry = readdir(tempdir)))
228 {
229 uschar *name = US entry->d_name;
230 int dummy;
231 if (Ustrcmp(name, US".") == 0 || Ustrcmp(name, US"..") == 0) continue;
232
233 file_path = string_sprintf("%s/%s", mbox_path, name);
234 debug_printf("unspool_mbox(): unlinking '%s'\n", file_path);
235 dummy = unlink(CS file_path); dummy = dummy; /* compiler quietening */
236 }
237
238 closedir(tempdir);
239
240 /* remove directory */
241 rmdir(CS mbox_path);
242 store_reset(mbox_path);
243 }
244 spool_mbox_ok = 0;
245 }
246
247 #endif