0a2a1d27d1eb79b187f9cebf1dfecbef3adaa556
[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 - 2021
8  * SPDX-License-Identifier: GPL-2.0-or-later
9  */
10
11 /* Code for setting up a MBOX style spool file inside a /scan/<msgid>
12 sub directory of exim's spool directory. */
13
14 #include "exim.h"
15 #ifdef WITH_CONTENT_SCAN
16
17 extern int malware_ok;
18 extern int spam_ok;
19
20 int spool_mbox_ok = 0;
21 uschar spooled_message_id[MESSAGE_ID_LENGTH+1];
22
23 /*
24 Create an MBOX-style message file from the spooled files.
25
26 Returns a pointer to the FILE, and puts the size in bytes into mbox_file_size.
27 If mbox_fname is non-null, fill in a pointer to the name.
28 Normally, source_file_override is NULL
29 */
30
31 FILE *
32 spool_mbox(unsigned long *mbox_file_size, const uschar *source_file_override,
33   uschar ** mbox_fname)
34 {
35 uschar message_subdir[2];
36 uschar buffer[16384];
37 uschar *temp_string;
38 uschar *mbox_path;
39 FILE *mbox_file = NULL, *l_data_file = NULL, *yield = NULL;
40 struct stat statbuf;
41 int j;
42 rmark reset_point;
43
44 mbox_path = string_sprintf("%s/scan/%s/%s.eml",
45   spool_directory, message_id, message_id);
46 if (mbox_fname) *mbox_fname = mbox_path;
47
48 reset_point = store_mark();
49
50 /* Skip creation if already spooled out as mbox file */
51 if (!spool_mbox_ok)
52   {
53   /* create temp directory inside scan dir, directory_make works recursively */
54   temp_string = string_sprintf("scan/%s", message_id);
55   if (!directory_make(spool_directory, temp_string, 0750, FALSE))
56     {
57     log_write(0, LOG_MAIN|LOG_PANIC, "%s",
58       string_open_failed("scan directory %s/scan/%s", spool_directory, temp_string));
59     goto OUT;
60     }
61
62   /* open [message_id].eml file for writing */
63
64   if (!(mbox_file = modefopen(mbox_path, "wb", SPOOL_MODE)))
65     {
66     log_write(0, LOG_MAIN|LOG_PANIC, "%s",
67       string_open_failed("scan file %s", mbox_path));
68     goto OUT;
69     }
70
71   /* Generate mailbox headers. The $received_for variable is (up to at least
72   Exim 4.64) never set here, because it is only set when expanding the
73   contents of the Received: header line. However, the code below will use it
74   if it should become available in future. */
75
76   temp_string = expand_string(
77     US"From ${if def:return_path{$return_path}{MAILER-DAEMON}} ${tod_bsdinbox}\n"
78     "${if def:sender_address{X-Envelope-From: <${sender_address}>\n}}"
79     "${if def:recipients{X-Envelope-To: ${recipients}\n}}");
80
81   if (temp_string)
82     if (fwrite(temp_string, Ustrlen(temp_string), 1, mbox_file) != 1)
83       {
84       log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
85           mailbox headers to %s", mbox_path);
86       goto OUT;
87       }
88
89   /* write all non-deleted header lines to mbox file */
90
91   for (header_line * my_headerlist = header_list; my_headerlist;
92       my_headerlist = my_headerlist->next)
93     if (my_headerlist->type != '*')
94       if (fwrite(my_headerlist->text, my_headerlist->slen, 1, mbox_file) != 1)
95         {
96         log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
97             message headers to %s", mbox_path);
98         goto OUT;
99         }
100
101   /* End headers */
102
103   if (fwrite("\n", 1, 1, mbox_file) != 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   /* Copy body file.  If the main receive still has it open then it is holding
111   a lock, and we must not close it (which releases the lock), so just use the
112   global file handle. */
113
114   if (source_file_override)
115     l_data_file = Ufopen(source_file_override, "rb");
116   else if (spool_data_file)
117     l_data_file = spool_data_file;
118   else
119     for (int i = 0; i < 2; i++)
120       {
121       set_subdir_str(message_subdir, message_id, i);
122       temp_string = spool_fname(US"input", message_subdir, message_id, US"-D");
123       if ((l_data_file = Ufopen(temp_string, "rb"))) break;
124       }
125
126   if (!l_data_file)
127     {
128     log_write(0, LOG_MAIN|LOG_PANIC, "Could not open datafile for message %s",
129       message_id);
130     goto OUT;
131     }
132
133   /* The code used to use this line, but it doesn't work in Cygwin.
134
135       (void)fread(data_buffer, 1, 18, l_data_file);
136
137      What's happening is that spool_mbox used to use an fread to jump over the
138      file header. That fails under Cygwin because the header is locked, but
139      doing an fseek succeeds. We have to output the leading newline
140      explicitly, because the one in the file is parted of the locked area.  */
141
142   if (!source_file_override)
143     (void)fseek(l_data_file, spool_data_start_offset(message_id), SEEK_SET);
144
145   do
146     {
147     uschar * s;
148
149     if (!f.spool_file_wireformat || source_file_override)
150       j = fread(buffer, 1, sizeof(buffer), l_data_file);
151     else                                                /* needs CRLF -> NL */
152       if ((s = US fgets(CS buffer, sizeof(buffer), l_data_file)))
153         {
154         uschar * p = s + Ustrlen(s) - 1;
155
156         if (*p == '\n' && p[-1] == '\r')
157           *--p = '\n';
158         else if (*p == '\r')
159           ungetc(*p--, l_data_file);
160
161         j = p - buffer;
162         }
163       else
164         j = 0;
165
166     if (j > 0)
167       if (fwrite(buffer, j, 1, mbox_file) != 1)
168         {
169         log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
170             message body to %s", mbox_path);
171         goto OUT;
172         }
173     } while (j > 0);
174
175   (void)fclose(mbox_file);
176   mbox_file = NULL;
177
178   Ustrncpy(spooled_message_id, message_id, sizeof(spooled_message_id));
179   spooled_message_id[sizeof(spooled_message_id)-1] = '\0';
180   spool_mbox_ok = 1;
181   }
182
183 /* get the size of the mbox message and open [message_id].eml file for reading*/
184
185 if (  !(yield = Ufopen(mbox_path,"rb"))
186    || fstat(fileno(yield), &statbuf) != 0
187    )
188   log_write(0, LOG_MAIN|LOG_PANIC, "%s",
189     string_open_failed( "scan file %s", mbox_path));
190 else
191   *mbox_file_size = statbuf.st_size;
192
193 OUT:
194 if (l_data_file && !spool_data_file) (void)fclose(l_data_file);
195 if (mbox_file) (void)fclose(mbox_file);
196 store_reset(reset_point);
197 return yield;
198 }
199
200
201
202
203
204 /* remove mbox spool file and temp directory */
205 void
206 unspool_mbox(void)
207 {
208 spam_ok = 0;
209 malware_ok = 0;
210
211 if (spool_mbox_ok && !f.no_mbox_unspool)
212   {
213   uschar *file_path;
214   DIR *tempdir;
215   rmark reset_point = store_mark();
216   uschar * mbox_path = string_sprintf("%s/scan/%s", spool_directory, spooled_message_id);
217
218   if (!(tempdir = exim_opendir(mbox_path)))
219     {
220     debug_printf("Unable to opendir(%s): %s\n", mbox_path, strerror(errno));
221     /* Just in case we still can: */
222     (void) rmdir(CS mbox_path);
223     return;
224     }
225   /* loop thru dir & delete entries */
226   for (struct dirent *entry; entry = readdir(tempdir); )
227     {
228     uschar *name = US entry->d_name;
229     if (Ustrcmp(name, US".") == 0 || Ustrcmp(name, US"..") == 0) continue;
230
231     file_path = string_sprintf("%s/%s", mbox_path, name);
232     debug_printf("unspool_mbox(): unlinking '%s'\n", file_path);
233     if (unlink(CS file_path) != 0)
234       log_write(0, LOG_MAIN|LOG_PANIC, "unlink(%s): %s", file_path, strerror(errno));
235     }
236
237   closedir(tempdir);
238
239   /* remove directory */
240   if (rmdir(CS mbox_path) != 0)
241     log_write(0, LOG_MAIN|LOG_PANIC, "rmdir(%s): %s", mbox_path, strerror(errno));
242   store_reset(reset_point);
243   }
244 spool_mbox_ok = 0;
245 }
246
247 #endif