SPDX: license tags (mostly by guesswork)
[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-only
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   if (fwrite("\n", 1, 1, mbox_file) != 1)
103     {
104     log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
105       message headers to %s", mbox_path);
106     goto OUT;
107     }
108
109   /* Copy body file.  If the main receive still has it open then it is holding
110   a lock, and we must not close it (which releases the lock), so just use the
111   global file handle. */
112   if (source_file_override)
113     l_data_file = Ufopen(source_file_override, "rb");
114   else if (spool_data_file)
115     l_data_file = spool_data_file;
116   else
117     {
118     message_subdir[1] = '\0';
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
127   if (!l_data_file)
128     {
129     log_write(0, LOG_MAIN|LOG_PANIC, "Could not open datafile for message %s",
130       message_id);
131     goto OUT;
132     }
133
134   /* The code used to use this line, but it doesn't work in Cygwin.
135
136       (void)fread(data_buffer, 1, 18, l_data_file);
137
138      What's happening is that spool_mbox used to use an fread to jump over the
139      file header. That fails under Cygwin because the header is locked, but
140      doing an fseek succeeds. We have to output the leading newline
141      explicitly, because the one in the file is parted of the locked area.  */
142
143   if (!source_file_override)
144     (void)fseek(l_data_file, SPOOL_DATA_START_OFFSET, SEEK_SET);
145
146   do
147     {
148     uschar * s;
149
150     if (!f.spool_file_wireformat || source_file_override)
151       j = fread(buffer, 1, sizeof(buffer), l_data_file);
152     else                                                /* needs CRLF -> NL */
153       if ((s = US fgets(CS buffer, sizeof(buffer), l_data_file)))
154         {
155         uschar * p = s + Ustrlen(s) - 1;
156
157         if (*p == '\n' && p[-1] == '\r')
158           *--p = '\n';
159         else if (*p == '\r')
160           ungetc(*p--, l_data_file);
161
162         j = p - buffer;
163         }
164       else
165         j = 0;
166
167     if (j > 0)
168       if (fwrite(buffer, j, 1, mbox_file) != 1)
169         {
170         log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
171             message body to %s", mbox_path);
172         goto OUT;
173         }
174     } while (j > 0);
175
176   (void)fclose(mbox_file);
177   mbox_file = NULL;
178
179   Ustrncpy(spooled_message_id, message_id, sizeof(spooled_message_id));
180   spooled_message_id[sizeof(spooled_message_id)-1] = '\0';
181   spool_mbox_ok = 1;
182   }
183
184 /* get the size of the mbox message and open [message_id].eml file for reading*/
185
186 if (  !(yield = Ufopen(mbox_path,"rb"))
187    || fstat(fileno(yield), &statbuf) != 0
188    )
189   log_write(0, LOG_MAIN|LOG_PANIC, "%s",
190     string_open_failed( "scan file %s", mbox_path));
191 else
192   *mbox_file_size = statbuf.st_size;
193
194 OUT:
195 if (l_data_file && !spool_data_file) (void)fclose(l_data_file);
196 if (mbox_file) (void)fclose(mbox_file);
197 store_reset(reset_point);
198 return yield;
199 }
200
201
202
203
204
205 /* remove mbox spool file and temp directory */
206 void
207 unspool_mbox(void)
208 {
209 spam_ok = 0;
210 malware_ok = 0;
211
212 if (spool_mbox_ok && !f.no_mbox_unspool)
213   {
214   uschar *file_path;
215   DIR *tempdir;
216   rmark reset_point = store_mark();
217   uschar * mbox_path = string_sprintf("%s/scan/%s", spool_directory, spooled_message_id);
218
219   if (!(tempdir = exim_opendir(mbox_path)))
220     {
221     debug_printf("Unable to opendir(%s): %s\n", mbox_path, strerror(errno));
222     /* Just in case we still can: */
223     (void) rmdir(CS mbox_path);
224     return;
225     }
226   /* loop thru dir & delete entries */
227   for (struct dirent *entry; entry = readdir(tempdir); )
228     {
229     uschar *name = US entry->d_name;
230     if (Ustrcmp(name, US".") == 0 || Ustrcmp(name, US"..") == 0) continue;
231
232     file_path = string_sprintf("%s/%s", mbox_path, name);
233     debug_printf("unspool_mbox(): unlinking '%s'\n", file_path);
234     if (unlink(CS file_path) != 0)
235       log_write(0, LOG_MAIN|LOG_PANIC, "unlink(%s): %s", file_path, strerror(errno));
236     }
237
238   closedir(tempdir);
239
240   /* remove directory */
241   if (rmdir(CS mbox_path) != 0)
242     log_write(0, LOG_MAIN|LOG_PANIC, "rmdir(%s): %s", mbox_path, strerror(errno));
243   store_reset(reset_point);
244   }
245 spool_mbox_ok = 0;
246 }
247
248 #endif