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