749484f2b0974bd891dffaf847033731de4f7b4d
[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