temporarily reverting my camel namespace patches until after we merge in some of...
[platform/upstream/evolution-data-server.git] / camel / camel-movemail.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* camel-movemail.c: mbox copying function */
3
4 /*
5  * Author:
6  *  Dan Winship <danw@ximian.com>
7  *
8  * Copyright 2000 Ximian, Inc. (www.ximian.com)
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of version 2 of the GNU General Public
12  * License as published by the Free Software Foundation.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
22  * USA
23  */
24
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
28
29 #include <sys/stat.h>
30 #include <sys/uio.h>
31 #include <errno.h>
32 #include <fcntl.h>
33 #include <stdlib.h>
34 #include <stdio.h>
35 #include <time.h>
36 #include <unistd.h>
37 #include <string.h>
38 #include <signal.h>
39 #ifdef HAVE_ALLOCA_H
40 #include <alloca.h>
41 #endif
42
43 #include "camel-movemail.h"
44 #include "camel-exception.h"
45
46 #include "camel-mime-parser.h"
47 #include "camel-mime-filter.h"
48 #include "camel-mime-filter-from.h"
49
50 #include "camel-lock-client.h"
51
52 #define d(x)
53
54 #ifdef MOVEMAIL_PATH
55 #include <sys/wait.h>
56
57 static void movemail_external (const char *source, const char *dest,
58                                CamelException *ex);
59 #endif
60
61 #ifdef HAVE_BROKEN_SPOOL
62 static int camel_movemail_copy_filter(int fromfd, int tofd, off_t start, size_t bytes, CamelMimeFilter *filter);
63 static int camel_movemail_solaris (int oldsfd, int dfd, CamelException *ex);
64 #else
65 /* these could probably be exposed as a utility? (but only mbox needs it) */
66 static int camel_movemail_copy_file(int sfd, int dfd, CamelException *ex);
67 #endif
68
69 #if 0
70 static int camel_movemail_copy(int fromfd, int tofd, off_t start, size_t bytes);
71 #endif
72
73 /**
74  * camel_movemail: Copy an mbox file from a shared spool directory to a
75  * new folder in a Camel store
76  * @source: source file
77  * @dest: destination file
78  * @ex: a CamelException
79  *
80  * This copies an mbox file from a shared directory with multiple
81  * readers and writers into a private (presumably Camel-controlled)
82  * directory. Dot locking is used on the source file (but not the
83  * destination).
84  *
85  * Return Value: Returns -1 on error.
86  **/
87 int
88 camel_movemail(const char *source, const char *dest, CamelException *ex)
89 {
90         int lockid = -1;
91         int res = -1;
92         int sfd, dfd;
93         struct stat st;
94
95         camel_exception_clear(ex);
96
97         /* Stat and then open the spool file. If it doesn't exist or
98          * is empty, the user has no mail. (There's technically a race
99          * condition here in that an MDA might have just now locked it
100          * to deliver a message, but we don't care. In that case,
101          * assuming it's unlocked is equivalent to pretending we were
102          * called a fraction earlier.)
103          */
104         if (stat (source, &st) == -1) {
105                 if (errno != ENOENT) {
106                         camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
107                                               _("Could not check mail file %s: %s"),
108                                               source, g_strerror (errno));
109                 }
110                 return -1;
111         }
112
113         if (st.st_size == 0)
114                 return 0;
115
116         /* open files */
117         sfd = open (source, O_RDWR);
118         if (sfd == -1) {
119                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
120                                       _("Could not open mail file %s: %s"),
121                                       source, g_strerror (errno));
122                 return -1;
123         }
124
125         dfd = open (dest, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);
126         if (dfd == -1) {
127                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
128                                       _("Could not open temporary mail "
129                                         "file %s: %s"), dest,
130                                       g_strerror (errno));
131                 close (sfd);
132                 return -1;
133         }
134
135         /* lock our source mailbox */
136         lockid = camel_lock_helper_lock(source, ex);
137         if (lockid == -1) {
138                 close(sfd);
139                 close(dfd);
140                 return -1;
141         }
142
143 #ifdef HAVE_BROKEN_SPOOL
144         res = camel_movemail_solaris(sfd, dfd, ex);
145 #else
146         res = camel_movemail_copy_file(sfd, dfd, ex);
147 #endif
148
149         /* If no errors occurred copying the data, and we successfully
150          * close the destination file, then truncate the source file.
151          */
152         if (res != -1) {
153                 if (close (dfd) == 0) {
154                         ftruncate (sfd, 0);
155                 } else {
156                         camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
157                                               _("Failed to store mail in temp file %s: %s"),
158                                               dest, g_strerror (errno));
159                         res = -1;
160                 }
161         } else
162                 close (dfd);
163         close (sfd);
164
165         camel_lock_helper_unlock(lockid);
166
167         return res;
168 }
169
170 #ifdef MOVEMAIL_PATH
171 static void
172 movemail_external (const char *source, const char *dest, CamelException *ex)
173 {
174         sigset_t mask, omask;
175         pid_t pid;
176         int fd[2], len = 0, nread, status;
177         char buf[BUFSIZ], *output = NULL;
178
179         /* Block SIGCHLD so the app can't mess us up. */
180         sigemptyset (&mask);
181         sigaddset (&mask, SIGCHLD);
182         sigprocmask (SIG_BLOCK, &mask, &omask);
183
184         if (pipe (fd) == -1) {
185                 sigprocmask (SIG_SETMASK, &omask, NULL);
186                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
187                                       _("Could not create pipe: %s"),
188                                       g_strerror (errno));
189                 return;
190         }
191
192         pid = fork ();
193         switch (pid) {
194         case -1:
195                 close (fd[0]);
196                 close (fd[1]);
197                 sigprocmask (SIG_SETMASK, &omask, NULL);
198                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
199                                       _("Could not fork: %s"),
200                                       g_strerror (errno));
201                 return;
202
203         case 0:
204                 /* Child */
205                 close (fd[0]);
206                 close (STDIN_FILENO);
207                 dup2 (fd[1], STDOUT_FILENO);
208                 dup2 (fd[1], STDERR_FILENO);
209
210                 execl (MOVEMAIL_PATH, MOVEMAIL_PATH, source, dest, NULL);
211                 _exit (255);
212                 break;
213
214         default:
215                 break;
216         }
217
218         /* Parent */
219         close (fd[1]);
220
221         /* Read movemail's output. */
222         while ((nread = read (fd[0], buf, sizeof (buf))) > 0) {
223                 output = g_realloc (output, len + nread + 1);
224                 memcpy (output + len, buf, nread);
225                 len += nread;
226                 output[len] = '\0';
227         }
228         close (fd[0]);
229
230         /* Now get the exit status. */
231         while (waitpid (pid, &status, 0) == -1 && errno == EINTR)
232                 ;
233         sigprocmask (SIG_SETMASK, &omask, NULL);
234
235         if (!WIFEXITED (status) || WEXITSTATUS (status) != 0) {
236                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
237                                       _("Movemail program failed: %s"),
238                                       output ? output : _("(Unknown error)"));
239         }
240         g_free (output);
241 }
242 #endif
243
244 #ifndef HAVE_BROKEN_SPOOL
245 static int
246 camel_movemail_copy_file(int sfd, int dfd, CamelException *ex)
247 {
248         int nread, nwrote;
249         char buf[4096];
250
251         while (1) {
252                 int written = 0;
253
254                 nread = read (sfd, buf, sizeof (buf));
255                 if (nread == 0)
256                         break;
257                 else if (nread == -1) {
258                         if (errno == EINTR)
259                                 continue;
260                         camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
261                                               _("Error reading mail file: %s"),
262                                               g_strerror (errno));
263                         return -1;
264                 }
265
266                 while (nread) {
267                         nwrote = write (dfd, buf + written, nread);
268                         if (nwrote == -1) {
269                                 if (errno == EINTR)
270                                         continue; /* continues inner loop */
271                                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
272                                                       _("Error writing mail temp file: %s"),
273                                                       g_strerror (errno));
274                                 return -1;
275                         }
276                         written += nwrote;
277                         nread -= nwrote;
278                 }
279         }
280
281         return 0;
282 }
283 #endif
284
285 #if 0
286 static int
287 camel_movemail_copy(int fromfd, int tofd, off_t start, size_t bytes)
288 {
289         char buffer[4096];
290         int written = 0;
291
292         d(printf("writing %d bytes ... ", bytes));
293
294         if (lseek(fromfd, start, SEEK_SET) != start)
295                 return -1;
296
297         while (bytes>0) {
298                 int toread, towrite;
299
300                 toread = bytes;
301                 if (bytes>4096)
302                         toread = 4096;
303                 else
304                         toread = bytes;
305                 do {
306                         towrite = read(fromfd, buffer, toread);
307                 } while (towrite == -1 && errno == EINTR);
308
309                 if (towrite == -1)
310                         return -1;
311
312                 /* check for 'end of file' */
313                 if (towrite == 0) {
314                         d(printf("end of file?\n"));
315                         break;
316                 }
317
318                 do {
319                         toread = write(tofd, buffer, towrite);
320                 } while (toread == -1 && errno == EINTR);
321
322                 if (toread == -1)
323                         return -1;
324
325                 written += toread;
326                 bytes -= toread;
327         }
328
329         d(printf("written %d bytes\n", written));
330
331         return written;
332 }
333 #endif
334
335 #define PRE_SIZE (32)
336
337 #ifdef HAVE_BROKEN_SPOOL
338 static int
339 camel_movemail_copy_filter(int fromfd, int tofd, off_t start, size_t bytes, CamelMimeFilter *filter)
340 {
341         char buffer[4096+PRE_SIZE];
342         int written = 0;
343         char *filterbuffer;
344         int filterlen, filterpre;
345
346         d(printf("writing %d bytes ... ", bytes));
347
348         camel_mime_filter_reset(filter);
349
350         if (lseek(fromfd, start, SEEK_SET) != start)
351                 return -1;
352
353         while (bytes>0) {
354                 int toread, towrite;
355
356                 toread = bytes;
357                 if (bytes>4096)
358                         toread = 4096;
359                 else
360                         toread = bytes;
361                 do {
362                         towrite = read(fromfd, buffer+PRE_SIZE, toread);
363                 } while (towrite == -1 && errno == EINTR);
364
365                 if (towrite == -1)
366                         return -1;
367
368                 d(printf("read %d unfiltered bytes\n", towrite));
369
370                 /* check for 'end of file' */
371                 if (towrite == 0) {
372                         d(printf("end of file?\n"));
373                         camel_mime_filter_complete(filter, buffer+PRE_SIZE, towrite, PRE_SIZE,
374                                                    &filterbuffer, &filterlen, &filterpre);
375                         towrite = filterlen;
376                         if (towrite == 0)
377                                 break;
378                 } else {
379                         camel_mime_filter_filter(filter, buffer+PRE_SIZE, towrite, PRE_SIZE,
380                                                  &filterbuffer, &filterlen, &filterpre);
381                         towrite = filterlen;
382                 }
383
384                 d(printf("writing %d filtered bytes\n", towrite));
385
386                 do {
387                         toread = write(tofd, filterbuffer, towrite);
388                 } while (toread == -1 && errno == EINTR);
389
390                 if (toread == -1)
391                         return -1;
392
393                 written += toread;
394                 bytes -= toread;
395         }
396
397         d(printf("written %d bytes\n", written));
398
399         return written;
400 }
401
402 /* write the headers back out again, but not he Content-Length header, because we dont
403    want to maintain it! */
404 static int
405 solaris_header_write(int fd, struct _header_raw *header)
406 {
407         struct iovec iv[4];
408         int outlen = 0, len;
409
410         iv[1].iov_base = ":";
411         iv[1].iov_len = 1;
412         iv[3].iov_base = "\n";
413         iv[3].iov_len = 1;
414
415         while (header) {
416                 if (strcasecmp(header->name, "Content-Length")) {
417                         iv[0].iov_base = header->name;
418                         iv[0].iov_len = strlen(header->name);
419                         iv[2].iov_base = header->value;
420                         iv[2].iov_len = strlen(header->value);
421                 
422                         do {
423                                 len = writev(fd, iv, 4);
424                         } while (len == -1 && errno == EINTR);
425                         
426                         if (len == -1)
427                                 return -1;
428                         outlen += len;
429                 }
430                 header = header->next;
431         }
432
433         do {
434                 len = write(fd, "\n", 1);
435         } while (len == -1 && errno == EINTR);
436
437         if (len == -1)
438                 return -1;
439
440         outlen += 1;
441
442         d(printf("Wrote %d bytes of headers\n", outlen));
443
444         return outlen;
445 }
446
447 /* Well, since Solaris is a tad broken wrt its 'mbox' folder format,
448    we must convert it to a real mbox format.  Thankfully this is
449    mostly pretty easy */
450 static int
451 camel_movemail_solaris (int oldsfd, int dfd, CamelException *ex)
452 {
453         CamelMimeParser *mp;
454         char *buffer;
455         int len;
456         int sfd;
457         CamelMimeFilterFrom *ffrom;
458         int ret = 1;
459         char *from = NULL;
460
461         /* need to dup as the mime parser will close on finish */
462         sfd = dup(oldsfd);
463         if (sfd == -1) {
464                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
465                                       _("Error copying mail temp file: %s"),
466                                       g_strerror (errno));
467                 return -1;
468         }
469
470         mp = camel_mime_parser_new();
471         camel_mime_parser_scan_from(mp, TRUE);
472         camel_mime_parser_init_with_fd(mp, sfd);
473
474         ffrom = camel_mime_filter_from_new();
475
476         while (camel_mime_parser_step(mp, &buffer, &len) == HSCAN_FROM) {
477                 g_assert(camel_mime_parser_from_line(mp));
478                 from = g_strdup(camel_mime_parser_from_line(mp));
479                 if (camel_mime_parser_step(mp, &buffer, &len) != HSCAN_FROM_END) {
480                         const char *cl;
481                         int length;
482                         int start, body;
483                         off_t newpos;
484
485                         ret = 0;
486
487                         start = camel_mime_parser_tell_start_from(mp);
488                         body = camel_mime_parser_tell(mp);
489
490                         if (write(dfd, from, strlen(from)) != strlen(from))
491                                 goto fail;
492
493                         /* write out headers, but NOT content-length header */
494                         if (solaris_header_write(dfd, camel_mime_parser_headers_raw(mp)) == -1)
495                                 goto fail;
496
497                         cl = camel_mime_parser_header(mp, "content-length", NULL);
498                         if (cl == NULL) {
499                                 g_warning("Required Content-Length header is missing from solaris mail box @ %d", (int)camel_mime_parser_tell(mp));
500                                 camel_mime_parser_drop_step(mp);
501                                 camel_mime_parser_drop_step(mp);
502                                 camel_mime_parser_step(mp, &buffer, &len);
503                                 camel_mime_parser_unstep(mp);
504                                 length = camel_mime_parser_tell_start_from(mp) - body;
505                                 newpos = -1;
506                         } else {
507                                 length = atoi(cl);
508                                 camel_mime_parser_drop_step(mp);
509                                 camel_mime_parser_drop_step(mp);
510                                 newpos = length+body;
511                         }
512                         /* copy body->length converting From lines */
513                         if (camel_movemail_copy_filter(sfd, dfd, body, length, (CamelMimeFilter *)ffrom) == -1)
514                                 goto fail;
515                         if (newpos != -1)
516                                 camel_mime_parser_seek(mp, newpos, SEEK_SET);
517                 } else {
518                         g_error("Inalid parser state: %d", camel_mime_parser_state(mp));
519                 }
520                 g_free(from);
521         }
522
523         camel_object_unref((CamelObject *)mp);
524         camel_object_unref((CamelObject *)ffrom);
525
526         return ret;
527
528 fail:
529         g_free(from);
530
531         camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
532                               _("Error copying mail temp file: %s"),
533                               g_strerror (errno));
534
535
536         camel_object_unref((CamelObject *)mp);
537         camel_object_unref((CamelObject *)ffrom);
538
539         return -1;
540 }
541 #endif /* HAVE_BROKEN_SPOOL */
542