Reduce number of places knowing about filename-construction for mbox file-for-scanning
[exim.git] / src / src / spool_mbox.c
CommitLineData
8523533c
TK
1/*************************************************
2* Exim - an Internet mail transport agent *
3*************************************************/
4
80fea873
JH
5/* Copyright (c) Tom Kistner <tom@duncanthrax.net> 2003 - 2015
6 * License: GPL
7 * Copyright (c) The Exim Maintainers 2016
8 */
8523533c
TK
9
10/* Code for setting up a MBOX style spool file inside a /scan/<msgid>
11sub directory of exim's spool directory. */
12
13#include "exim.h"
14#ifdef WITH_CONTENT_SCAN
15
8523533c
TK
16extern int malware_ok;
17extern int spam_ok;
18
19int spool_mbox_ok = 0;
6e3b198d 20uschar spooled_message_id[MESSAGE_ID_LENGTH+1];
8523533c 21
040721f2
JH
22/*
23Create an MBOX-style message file from the spooled files.
24
25Returns a pointer to the FILE, and puts the size in bytes into mbox_file_size.
26If mbox_fname is non-null, fill in a pointer to the name.
27Normally, source_file_override is NULL
28*/
8523533c 29
0f0c8159 30FILE *
040721f2
JH
31spool_mbox(unsigned long *mbox_file_size, const uschar *source_file_override,
32 uschar ** mbox_fname)
0f0c8159 33{
3c51463e
JH
34uschar message_subdir[2];
35uschar buffer[16384];
36uschar *temp_string;
37uschar *mbox_path;
38FILE *mbox_file = NULL;
39FILE *data_file = NULL;
40FILE *yield = NULL;
41header_line *my_headerlist;
42struct stat statbuf;
43int i, j;
040721f2
JH
44void *reset_point;
45
46mbox_path = string_sprintf("%s/scan/%s/%s.eml",
47 spool_directory, message_id, message_id);
48if (mbox_fname) *mbox_fname = mbox_path;
3c51463e 49
040721f2 50reset_point = store_get(0);
3c51463e
JH
51
52/* Skip creation if already spooled out as mbox file */
53if (!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 }
8e669ac1 63
3c51463e
JH
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)
f951fd57 84 {
3c51463e
JH
85 i = fwrite(temp_string, Ustrlen(temp_string), 1, mbox_file);
86 if (i != 1)
87 {
1ac6b2e7 88 log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
3c51463e 89 mailbox headers to %s", mbox_path);
1ac6b2e7 90 goto OUT;
3c51463e 91 }
1ac6b2e7 92 }
f951fd57 93
3c51463e
JH
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);
f951fd57 107 goto OUT;
3c51463e
JH
108 }
109 }
8e669ac1 110
3c51463e
JH
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 }
21a04aa3 118
3c51463e 119 /* copy body file */
040721f2 120 if (!source_file_override)
3c51463e
JH
121 {
122 message_subdir[1] = '\0';
123 for (i = 0; i < 2; i++)
124 {
a2da3176 125 message_subdir[0] = split_spool_directory == (i == 0) ? message_id[5] : 0;
41313d92 126 temp_string = spool_fname(US"input", message_subdir, message_id, US"-D");
a2da3176 127 if ((data_file = Ufopen(temp_string, "rb"))) break;
3c51463e
JH
128 }
129 }
130 else
131 data_file = Ufopen(source_file_override, "rb");
8523533c 132
a2da3176 133 if (!data_file)
3c51463e
JH
134 {
135 log_write(0, LOG_MAIN|LOG_PANIC, "Could not open datafile for message %s",
136 message_id);
f951fd57 137 goto OUT;
3c51463e
JH
138 }
139
140 /* The code used to use this line, but it doesn't work in Cygwin.
8523533c 141
3c51463e
JH
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. */
8523533c 148
3c51463e
JH
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
8d468c4c
JH
171 Ustrncpy(spooled_message_id, message_id, sizeof(spooled_message_id));
172 spooled_message_id[sizeof(spooled_message_id)-1] = '\0';
3c51463e
JH
173 spool_mbox_ok = 1;
174 }
175
176/* get the size of the mbox message and open [message_id].eml file for reading*/
10c50704
JH
177
178if ( !(yield = Ufopen(mbox_path,"rb"))
179 || fstat(fileno(yield), &statbuf) != 0
180 )
3c51463e
JH
181 log_write(0, LOG_MAIN|LOG_PANIC, "%s", string_open_failed(errno,
182 "scan file %s", mbox_path));
10c50704
JH
183else
184 *mbox_file_size = statbuf.st_size;
3c51463e
JH
185
186OUT:
187if (data_file) (void)fclose(data_file);
188if (mbox_file) (void)fclose(mbox_file);
189store_reset(reset_point);
190return yield;
8523533c
TK
191}
192
3c51463e
JH
193
194
195
8e669ac1 196
03d5892b 197/* remove mbox spool file and temp directory */
3c51463e
JH
198void
199unspool_mbox(void)
200{
3c51463e
JH
201spam_ok = 0;
202malware_ok = 0;
8e669ac1 203
3c51463e
JH
204if (spool_mbox_ok && !no_mbox_unspool)
205 {
206 uschar *mbox_path;
207 uschar *file_path;
3c51463e
JH
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: */
f951fd57 218 rmdir(CS mbox_path);
3c51463e
JH
219 return;
220 }
221 /* loop thru dir & delete entries */
222 while((entry = readdir(tempdir)) != NULL)
223 {
224 uschar *name = US entry->d_name;
4dc2379a 225 int dummy;
3c51463e
JH
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);
4dc2379a 230 dummy = unlink(CS file_path);
3c51463e
JH
231 }
232
233 closedir(tempdir);
234
235 /* remove directory */
236 rmdir(CS mbox_path);
237 store_reset(mbox_path);
238 }
239spool_mbox_ok = 0;
8523533c
TK
240}
241
242#endif