DKIM: reduce memory usage (2nd go)
[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 = spool_fname(US"input", message_subdir, message_id, US"-D");
118       if ((data_file = Ufopen(temp_string, "rb"))) break;
119       }
120     }
121   else
122     data_file = Ufopen(source_file_override, "rb");
123
124   if (!data_file)
125     {
126     log_write(0, LOG_MAIN|LOG_PANIC, "Could not open datafile for message %s",
127       message_id);
128     goto OUT;
129     }
130
131   /* The code used to use this line, but it doesn't work in Cygwin.
132
133       (void)fread(data_buffer, 1, 18, data_file);
134     
135      What's happening is that spool_mbox used to use an fread to jump over the
136      file header. That fails under Cygwin because the header is locked, but
137      doing an fseek succeeds. We have to output the leading newline
138      explicitly, because the one in the file is parted of the locked area.  */
139
140   if (!source_file_override)
141     (void)fseek(data_file, SPOOL_DATA_START_OFFSET, SEEK_SET);
142
143   do
144     {
145     j = fread(buffer, 1, sizeof(buffer), data_file);
146
147     if (j > 0)
148       {
149       i = fwrite(buffer, j, 1, mbox_file);
150       if (i != 1)
151         {
152         log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
153             message body to %s", mbox_path);
154         goto OUT;
155         }
156       }
157     } while (j > 0);
158
159   (void)fclose(mbox_file);
160   mbox_file = NULL;
161
162   Ustrncpy(spooled_message_id, message_id, sizeof(spooled_message_id));
163   spooled_message_id[sizeof(spooled_message_id)-1] = '\0';
164   spool_mbox_ok = 1;
165   }
166
167 /* get the size of the mbox message and open [message_id].eml file for reading*/
168 if (Ustat(mbox_path, &statbuf) != 0 ||
169     (yield = Ufopen(mbox_path,"rb")) == NULL)
170   {
171   log_write(0, LOG_MAIN|LOG_PANIC, "%s", string_open_failed(errno,
172     "scan file %s", mbox_path));
173   goto OUT;
174   }
175
176 *mbox_file_size = statbuf.st_size;
177
178 OUT:
179 if (data_file) (void)fclose(data_file);
180 if (mbox_file) (void)fclose(mbox_file);
181 store_reset(reset_point);
182 return yield;
183 }
184
185
186
187
188
189 /* remove mbox spool file and temp directory */
190 void
191 unspool_mbox(void)
192 {
193 spam_ok = 0;
194 malware_ok = 0;
195
196 if (spool_mbox_ok && !no_mbox_unspool)
197   {
198   uschar *mbox_path;
199   uschar *file_path;
200   int n;
201   struct dirent *entry;
202   DIR *tempdir;
203
204   mbox_path = string_sprintf("%s/scan/%s", spool_directory, spooled_message_id);
205
206   tempdir = opendir(CS mbox_path);
207   if (!tempdir)
208     {
209     debug_printf("Unable to opendir(%s): %s\n", mbox_path, strerror(errno));
210     /* Just in case we still can: */
211     rmdir(CS mbox_path);
212     return;
213     }
214   /* loop thru dir & delete entries */
215   while((entry = readdir(tempdir)) != NULL)
216     {
217     uschar *name = US entry->d_name;
218     if (Ustrcmp(name, US".") == 0 || Ustrcmp(name, US"..") == 0) continue;
219
220     file_path = string_sprintf("%s/%s", mbox_path, name);
221     debug_printf("unspool_mbox(): unlinking '%s'\n", file_path);
222     n = unlink(CS file_path);
223     }
224
225   closedir(tempdir);
226
227   /* remove directory */
228   rmdir(CS mbox_path);
229   store_reset(mbox_path);
230   }
231 spool_mbox_ok = 0;
232 }
233
234 #endif