95e71b9c9d324df39c3751caf77b113b28e4883e
[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[MESSAGE_ID_LENGTH+1];
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   Ustrncpy(spooled_message_id, message_id, sizeof(spooled_message_id));
169   spooled_message_id[sizeof(spooled_message_id)-1] = '\0';
170   spool_mbox_ok = 1;
171   }
172
173 /* get the size of the mbox message and open [message_id].eml file for reading*/
174 if (Ustat(mbox_path, &statbuf) != 0 ||
175     (yield = Ufopen(mbox_path,"rb")) == NULL)
176   {
177   log_write(0, LOG_MAIN|LOG_PANIC, "%s", string_open_failed(errno,
178     "scan file %s", mbox_path));
179   goto OUT;
180   }
181
182 *mbox_file_size = statbuf.st_size;
183
184 OUT:
185 if (data_file) (void)fclose(data_file);
186 if (mbox_file) (void)fclose(mbox_file);
187 store_reset(reset_point);
188 return yield;
189 }
190
191
192
193
194 /* remove mbox spool file, demimed files and temp directory */
195
196 void
197 unspool_mbox(void)
198 {
199
200 /* reset all exiscan state variables */
201 #ifdef WITH_OLD_DEMIME
202 demime_ok = 0;
203 demime_errorlevel = 0;
204 demime_reason = NULL;
205 file_extensions = NULL;
206 #endif
207
208 spam_ok = 0;
209 malware_ok = 0;
210
211 if (spool_mbox_ok && !no_mbox_unspool)
212   {
213   uschar *mbox_path;
214   uschar *file_path;
215   int n;
216   struct dirent *entry;
217   DIR *tempdir;
218
219   mbox_path = string_sprintf("%s/scan/%s", spool_directory, spooled_message_id);
220
221   tempdir = opendir(CS mbox_path);
222   if (!tempdir)
223     {
224     debug_printf("Unable to opendir(%s): %s\n", mbox_path, strerror(errno));
225     /* Just in case we still can: */
226     rmdir(CS mbox_path);
227     return;
228     }
229   /* loop thru dir & delete entries */
230   while((entry = readdir(tempdir)) != NULL)
231     {
232     uschar *name = US entry->d_name;
233     if (Ustrcmp(name, US".") == 0 || Ustrcmp(name, US"..") == 0) continue;
234
235     file_path = string_sprintf("%s/%s", mbox_path, name);
236     debug_printf("unspool_mbox(): unlinking '%s'\n", file_path);
237     n = unlink(CS file_path);
238     }
239
240   closedir(tempdir);
241
242   /* remove directory */
243   rmdir(CS mbox_path);
244   store_reset(mbox_path);
245   }
246 spool_mbox_ok = 0;
247 }
248
249 #endif