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