global queue_name
[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 /* returns a pointer to the FILE, and puts the size in bytes into mbox_file_size
23  * normally, source_file_override is NULL */
24
25 FILE *
26 spool_mbox(unsigned long *mbox_file_size, const uschar *source_file_override)
27 {
28 uschar message_subdir[2];
29 uschar buffer[16384];
30 uschar *temp_string;
31 uschar *mbox_path;
32 FILE *mbox_file = NULL;
33 FILE *data_file = NULL;
34 FILE *yield = NULL;
35 header_line *my_headerlist;
36 struct stat statbuf;
37 int i, j;
38 void *reset_point = store_get(0);
39
40 mbox_path = string_sprintf("%s/scan/%s/%s.eml", spool_directory, message_id,
41   message_id);
42
43 /* Skip creation if already spooled out as mbox file */
44 if (!spool_mbox_ok)
45   {
46   /* create temp directory inside scan dir, directory_make works recursively */
47   temp_string = string_sprintf("scan/%s", message_id);
48   if (!directory_make(spool_directory, temp_string, 0750, FALSE))
49     {
50     log_write(0, LOG_MAIN|LOG_PANIC, "%s", string_open_failed(errno,
51       "scan directory %s/scan/%s", spool_directory, temp_string));
52     goto OUT;
53     }
54
55   /* open [message_id].eml file for writing */
56   mbox_file = modefopen(mbox_path, "wb", SPOOL_MODE);
57   if (mbox_file == NULL)
58     {
59     log_write(0, LOG_MAIN|LOG_PANIC, "%s", string_open_failed(errno,
60       "scan file %s", mbox_path));
61     goto OUT;
62     }
63
64   /* Generate mailbox headers. The $received_for variable is (up to at least
65   Exim 4.64) never set here, because it is only set when expanding the
66   contents of the Received: header line. However, the code below will use it
67   if it should become available in future. */
68
69   temp_string = expand_string(
70     US"From ${if def:return_path{$return_path}{MAILER-DAEMON}} ${tod_bsdinbox}\n"
71     "${if def:sender_address{X-Envelope-From: <${sender_address}>\n}}"
72     "${if def:recipients{X-Envelope-To: ${recipients}\n}}");
73
74   if (temp_string != NULL)
75     {
76     i = fwrite(temp_string, Ustrlen(temp_string), 1, mbox_file);
77     if (i != 1)
78       {
79       log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
80           mailbox headers to %s", mbox_path);
81       goto OUT;
82       }
83     }
84
85   /* write all header lines to mbox file */
86   my_headerlist = header_list;
87   for (my_headerlist = header_list; my_headerlist != NULL;
88     my_headerlist = my_headerlist->next)
89     {
90     /* skip deleted headers */
91     if (my_headerlist->type == '*') continue;
92
93     i = fwrite(my_headerlist->text, my_headerlist->slen, 1, mbox_file);
94     if (i != 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
102   /* End headers */
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 */
111   if (source_file_override == NULL)
112     {
113     message_subdir[1] = '\0';
114     for (i = 0; i < 2; i++)
115       {
116       message_subdir[0] = split_spool_directory == (i == 0) ? message_id[5] : 0;
117       temp_string = string_sprintf("%s/input/%s/%s/%s-D",
118             spool_directory, queue_name, message_subdir, message_id);
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     j = fread(buffer, 1, sizeof(buffer), data_file);
147
148     if (j > 0)
149       {
150       i = fwrite(buffer, j, 1, mbox_file);
151       if (i != 1)
152         {
153         log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
154             message body to %s", mbox_path);
155         goto OUT;
156         }
157       }
158     } while (j > 0);
159
160   (void)fclose(mbox_file);
161   mbox_file = NULL;
162
163   Ustrncpy(spooled_message_id, message_id, sizeof(spooled_message_id));
164   spooled_message_id[sizeof(spooled_message_id)-1] = '\0';
165   spool_mbox_ok = 1;
166   }
167
168 /* get the size of the mbox message and open [message_id].eml file for reading*/
169 if (Ustat(mbox_path, &statbuf) != 0 ||
170     (yield = Ufopen(mbox_path,"rb")) == NULL)
171   {
172   log_write(0, LOG_MAIN|LOG_PANIC, "%s", string_open_failed(errno,
173     "scan file %s", mbox_path));
174   goto OUT;
175   }
176
177 *mbox_file_size = statbuf.st_size;
178
179 OUT:
180 if (data_file) (void)fclose(data_file);
181 if (mbox_file) (void)fclose(mbox_file);
182 store_reset(reset_point);
183 return yield;
184 }
185
186
187
188
189
190 /* remove mbox spool file and temp directory */
191 void
192 unspool_mbox(void)
193 {
194 spam_ok = 0;
195 malware_ok = 0;
196
197 if (spool_mbox_ok && !no_mbox_unspool)
198   {
199   uschar *mbox_path;
200   uschar *file_path;
201   int n;
202   struct dirent *entry;
203   DIR *tempdir;
204
205   mbox_path = string_sprintf("%s/scan/%s", spool_directory, spooled_message_id);
206
207   tempdir = opendir(CS mbox_path);
208   if (!tempdir)
209     {
210     debug_printf("Unable to opendir(%s): %s\n", mbox_path, strerror(errno));
211     /* Just in case we still can: */
212     rmdir(CS mbox_path);
213     return;
214     }
215   /* loop thru dir & delete entries */
216   while((entry = readdir(tempdir)) != NULL)
217     {
218     uschar *name = US entry->d_name;
219     if (Ustrcmp(name, US".") == 0 || Ustrcmp(name, US"..") == 0) continue;
220
221     file_path = string_sprintf("%s/%s", mbox_path, name);
222     debug_printf("unspool_mbox(): unlinking '%s'\n", file_path);
223     n = unlink(CS file_path);
224     }
225
226   closedir(tempdir);
227
228   /* remove directory */
229   rmdir(CS mbox_path);
230   store_reset(mbox_path);
231   }
232 spool_mbox_ok = 0;
233 }
234
235 #endif