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