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