Copyright year updates (things touched in 2016)
[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