Reduce number of places knowing about filename-construction for mbox file-for-scanning
[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 /*
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   mbox_file = modefopen(mbox_path, "wb", SPOOL_MODE);
66   if (mbox_file == NULL)
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 != NULL)
84     {
85     i = fwrite(temp_string, Ustrlen(temp_string), 1, mbox_file);
86     if (i != 1)
87       {
88       log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
89           mailbox headers to %s", mbox_path);
90       goto OUT;
91       }
92     }
93
94   /* write all header lines to mbox file */
95   my_headerlist = header_list;
96   for (my_headerlist = header_list; my_headerlist != NULL;
97     my_headerlist = my_headerlist->next)
98     {
99     /* skip deleted headers */
100     if (my_headerlist->type == '*') continue;
101
102     i = fwrite(my_headerlist->text, my_headerlist->slen, 1, mbox_file);
103     if (i != 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
111   /* End headers */
112   if (fwrite("\n", 1, 1, mbox_file) != 1)
113     {
114     log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
115       message headers to %s", mbox_path);
116     goto OUT;
117     }
118
119   /* copy body file */
120   if (!source_file_override)
121     {
122     message_subdir[1] = '\0';
123     for (i = 0; i < 2; i++)
124       {
125       message_subdir[0] = split_spool_directory == (i == 0) ? message_id[5] : 0;
126       temp_string = spool_fname(US"input", message_subdir, message_id, US"-D");
127       if ((data_file = Ufopen(temp_string, "rb"))) break;
128       }
129     }
130   else
131     data_file = Ufopen(source_file_override, "rb");
132
133   if (!data_file)
134     {
135     log_write(0, LOG_MAIN|LOG_PANIC, "Could not open datafile for message %s",
136       message_id);
137     goto OUT;
138     }
139
140   /* The code used to use this line, but it doesn't work in Cygwin.
141
142       (void)fread(data_buffer, 1, 18, data_file);
143     
144      What's happening is that spool_mbox used to use an fread to jump over the
145      file header. That fails under Cygwin because the header is locked, but
146      doing an fseek succeeds. We have to output the leading newline
147      explicitly, because the one in the file is parted of the locked area.  */
148
149   if (!source_file_override)
150     (void)fseek(data_file, SPOOL_DATA_START_OFFSET, SEEK_SET);
151
152   do
153     {
154     j = fread(buffer, 1, sizeof(buffer), data_file);
155
156     if (j > 0)
157       {
158       i = fwrite(buffer, j, 1, mbox_file);
159       if (i != 1)
160         {
161         log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
162             message body to %s", mbox_path);
163         goto OUT;
164         }
165       }
166     } while (j > 0);
167
168   (void)fclose(mbox_file);
169   mbox_file = NULL;
170
171   Ustrncpy(spooled_message_id, message_id, sizeof(spooled_message_id));
172   spooled_message_id[sizeof(spooled_message_id)-1] = '\0';
173   spool_mbox_ok = 1;
174   }
175
176 /* get the size of the mbox message and open [message_id].eml file for reading*/
177
178 if (  !(yield = Ufopen(mbox_path,"rb"))
179    || fstat(fileno(yield), &statbuf) != 0
180    )
181   log_write(0, LOG_MAIN|LOG_PANIC, "%s", string_open_failed(errno,
182     "scan file %s", mbox_path));
183 else
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
197 /* remove mbox spool file and temp directory */
198 void
199 unspool_mbox(void)
200 {
201 spam_ok = 0;
202 malware_ok = 0;
203
204 if (spool_mbox_ok && !no_mbox_unspool)
205   {
206   uschar *mbox_path;
207   uschar *file_path;
208   struct dirent *entry;
209   DIR *tempdir;
210
211   mbox_path = string_sprintf("%s/scan/%s", spool_directory, spooled_message_id);
212
213   tempdir = opendir(CS mbox_path);
214   if (!tempdir)
215     {
216     debug_printf("Unable to opendir(%s): %s\n", mbox_path, strerror(errno));
217     /* Just in case we still can: */
218     rmdir(CS mbox_path);
219     return;
220     }
221   /* loop thru dir & delete entries */
222   while((entry = readdir(tempdir)) != NULL)
223     {
224     uschar *name = US entry->d_name;
225     int dummy;
226     if (Ustrcmp(name, US".") == 0 || Ustrcmp(name, US"..") == 0) continue;
227
228     file_path = string_sprintf("%s/%s", mbox_path, name);
229     debug_printf("unspool_mbox(): unlinking '%s'\n", file_path);
230     dummy = unlink(CS file_path);
231     }
232
233   closedir(tempdir);
234
235   /* remove directory */
236   rmdir(CS mbox_path);
237   store_reset(mbox_path);
238   }
239 spool_mbox_ok = 0;
240 }
241
242 #endif