Doc: fix minor typos
[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-D", spool_directory,
118         message_subdir, message_id);
119       data_file = Ufopen(temp_string, "rb");
120       if (data_file != NULL) break;
121       }
122     }
123   else
124     data_file = Ufopen(source_file_override, "rb");
125
126   if (data_file == NULL)
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, 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(data_file, SPOOL_DATA_START_OFFSET, SEEK_SET);
144
145   do
146     {
147     j = fread(buffer, 1, sizeof(buffer), data_file);
148
149     if (j > 0)
150       {
151       i = fwrite(buffer, j, 1, mbox_file);
152       if (i != 1)
153         {
154         log_write(0, LOG_MAIN|LOG_PANIC, "Error/short write while writing \
155             message body to %s", mbox_path);
156         goto OUT;
157         }
158       }
159     } while (j > 0);
160
161   (void)fclose(mbox_file);
162   mbox_file = NULL;
163
164   Ustrncpy(spooled_message_id, message_id, sizeof(spooled_message_id));
165   spooled_message_id[sizeof(spooled_message_id)-1] = '\0';
166   spool_mbox_ok = 1;
167   }
168
169 /* get the size of the mbox message and open [message_id].eml file for reading*/
170 if (Ustat(mbox_path, &statbuf) != 0 ||
171     (yield = Ufopen(mbox_path,"rb")) == NULL)
172   {
173   log_write(0, LOG_MAIN|LOG_PANIC, "%s", string_open_failed(errno,
174     "scan file %s", mbox_path));
175   goto OUT;
176   }
177
178 *mbox_file_size = statbuf.st_size;
179
180 OUT:
181 if (data_file) (void)fclose(data_file);
182 if (mbox_file) (void)fclose(mbox_file);
183 store_reset(reset_point);
184 return yield;
185 }
186
187
188
189
190
191 /* remove mbox spool file and temp directory */
192 void
193 unspool_mbox(void)
194 {
195 spam_ok = 0;
196 malware_ok = 0;
197
198 if (spool_mbox_ok && !no_mbox_unspool)
199   {
200   uschar *mbox_path;
201   uschar *file_path;
202   int n;
203   struct dirent *entry;
204   DIR *tempdir;
205
206   mbox_path = string_sprintf("%s/scan/%s", spool_directory, spooled_message_id);
207
208   tempdir = opendir(CS mbox_path);
209   if (!tempdir)
210     {
211     debug_printf("Unable to opendir(%s): %s\n", mbox_path, strerror(errno));
212     /* Just in case we still can: */
213     rmdir(CS mbox_path);
214     return;
215     }
216   /* loop thru dir & delete entries */
217   while((entry = readdir(tempdir)) != NULL)
218     {
219     uschar *name = US entry->d_name;
220     if (Ustrcmp(name, US".") == 0 || Ustrcmp(name, US"..") == 0) continue;
221
222     file_path = string_sprintf("%s/%s", mbox_path, name);
223     debug_printf("unspool_mbox(): unlinking '%s'\n", file_path);
224     n = unlink(CS file_path);
225     }
226
227   closedir(tempdir);
228
229   /* remove directory */
230   rmdir(CS mbox_path);
231   store_reset(mbox_path);
232   }
233 spool_mbox_ok = 0;
234 }
235
236 #endif