/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* camel-movemail.c: mbox copying function
*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
* This library is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see .
*
* Authors: Dan Winship
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "camel-lock-client.h"
#include "camel-mime-filter-from.h"
#include "camel-mime-filter.h"
#include "camel-mime-parser.h"
#include "camel-movemail.h"
#define d(x)
#define CHECK_CALL(x) G_STMT_START { \
if ((x) == -1) { \
g_debug ("%s: Call of '" #x "' failed: %s", G_STRFUNC, g_strerror (errno)); \
} \
} G_STMT_END
#ifdef MOVEMAIL_PATH
#include
static void movemail_external (const gchar *source, const gchar *dest,
GError **error);
#endif
#ifdef HAVE_BROKEN_SPOOL
static gint camel_movemail_copy_filter (gint fromfd, gint tofd, goffset start, gsize bytes, CamelMimeFilter *filter);
static gint camel_movemail_solaris (gint oldsfd, gint dfd, GError **error);
#else
/* these could probably be exposed as a utility? (but only mbox needs it) */
static gint camel_movemail_copy_file (gint sfd, gint dfd, GError **error);
#endif
#if 0
static gint camel_movemail_copy (gint fromfd, gint tofd, goffset start, gsize bytes);
#endif
/**
* camel_movemail:
* @source: source file
* @dest: destination file
* @error: return location for a #GError, or %NULL
*
* This copies an mbox file from a shared directory with multiple
* readers and writers into a private (presumably Camel-controlled)
* directory. Dot locking is used on the source file (but not the
* destination).
*
* Return Value: Returns -1 on error or 0 on success.
**/
gint
camel_movemail (const gchar *source,
const gchar *dest,
GError **error)
{
gint lockid = -1;
gint res = -1;
gint sfd, dfd;
struct stat st;
/* open files */
sfd = open (source, O_RDWR);
if (sfd == -1 && errno != ENOENT) {
g_set_error (
error, G_IO_ERROR,
g_io_error_from_errno (errno),
_("Could not open mail file %s: %s"),
source, g_strerror (errno));
return -1;
} else if (sfd == -1) {
/* No mail. */
return 0;
}
/* Stat the spool file. If it doesn't exist or
* is empty, the user has no mail. (There's technically a race
* condition here in that an MDA might have just now locked it
* to deliver a message, but we don't care. In that case,
* assuming it's unlocked is equivalent to pretending we were
* called a fraction earlier.)
*/
if (fstat (sfd, &st) == -1) {
close (sfd);
g_set_error (
error, G_IO_ERROR,
g_io_error_from_errno (errno),
_("Could not check mail file %s: %s"),
source, g_strerror (errno));
return -1;
}
if (st.st_size == 0) {
close (sfd);
return 0;
}
dfd = open (dest, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);
if (dfd == -1) {
g_set_error (
error, G_IO_ERROR,
g_io_error_from_errno (errno),
_("Could not open temporary mail file %s: %s"),
dest, g_strerror (errno));
close (sfd);
return -1;
}
/* lock our source mailbox */
lockid = camel_lock_helper_lock (source, error);
if (lockid == -1) {
close (sfd);
close (dfd);
return -1;
}
#ifdef HAVE_BROKEN_SPOOL
res = camel_movemail_solaris (sfd, dfd, ex);
#else
res = camel_movemail_copy_file (sfd, dfd, error);
#endif
/* If no errors occurred copying the data, and we successfully
* close the destination file, then truncate the source file.
*/
if (res != -1) {
if (close (dfd) == 0) {
CHECK_CALL (ftruncate (sfd, 0));
} else {
g_set_error (
error, G_IO_ERROR,
g_io_error_from_errno (errno),
_("Failed to store mail in temp file %s: %s"),
dest, g_strerror (errno));
res = -1;
}
} else
close (dfd);
close (sfd);
camel_lock_helper_unlock (lockid);
return res;
}
#ifdef MOVEMAIL_PATH
static void
movemail_external (const gchar *source,
const gchar *dest,
GError **error)
{
sigset_t mask, omask;
pid_t pid;
gint fd[2], len = 0, nread, status;
gchar buf[BUFSIZ], *output = NULL;
/* Block SIGCHLD so the app can't mess us up. */
sigemptyset (&mask);
sigaddset (&mask, SIGCHLD);
sigprocmask (SIG_BLOCK, &mask, &omask);
if (pipe (fd) == -1) {
sigprocmask (SIG_SETMASK, &omask, NULL);
g_set_error (
error, G_IO_ERROR,
g_io_error_from_errno (errno),
_("Could not create pipe: %s"),
g_strerror (errno));
return;
}
pid = fork ();
switch (pid) {
case -1:
close (fd[0]);
close (fd[1]);
sigprocmask (SIG_SETMASK, &omask, NULL);
g_set_error (
error, G_IO_ERROR,
g_io_error_from_errno (errno),
_("Could not fork: %s"), g_strerror (errno));
return;
case 0:
/* Child */
close (fd[0]);
close (STDIN_FILENO);
dup2 (fd[1], STDOUT_FILENO);
dup2 (fd[1], STDERR_FILENO);
execl (MOVEMAIL_PATH, MOVEMAIL_PATH, source, dest, NULL);
_exit (255);
break;
default:
break;
}
/* Parent */
close (fd[1]);
/* Read movemail's output. */
while ((nread = read (fd[0], buf, sizeof (buf))) > 0) {
output = g_realloc (output, len + nread + 1);
memcpy (output + len, buf, nread);
len += nread;
output[len] = '\0';
}
close (fd[0]);
/* Now get the exit status. */
while (waitpid (pid, &status, 0) == -1 && errno == EINTR)
;
sigprocmask (SIG_SETMASK, &omask, NULL);
if (!WIFEXITED (status) || WEXITSTATUS (status) != 0) {
g_set_error (
error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
_("Movemail program failed: %s"),
output ? output : _("(Unknown error)"));
}
g_free (output);
}
#endif
#ifndef HAVE_BROKEN_SPOOL
static gint
camel_movemail_copy_file (gint sfd,
gint dfd,
GError **error)
{
gint nread, nwrote;
gchar buf[4096];
while (1) {
gint written = 0;
nread = read (sfd, buf, sizeof (buf));
if (nread == 0)
break;
else if (nread == -1) {
if (errno == EINTR)
continue;
g_set_error (
error, G_IO_ERROR,
g_io_error_from_errno (errno),
_("Error reading mail file: %s"),
g_strerror (errno));
return -1;
}
while (nread) {
nwrote = write (dfd, buf + written, nread);
if (nwrote == -1) {
if (errno == EINTR)
continue; /* continues inner loop */
g_set_error (
error, G_IO_ERROR,
g_io_error_from_errno (errno),
_("Error writing mail temp file: %s"),
g_strerror (errno));
return -1;
}
written += nwrote;
nread -= nwrote;
}
}
return 0;
}
#endif
#if 0
static gint
camel_movemail_copy (gint fromfd,
gint tofd,
goffset start,
gsize bytes)
{
gchar buffer[4096];
gint written = 0;
d (printf ("writing %d bytes ... ", bytes));
if (lseek (fromfd, start, SEEK_SET) != start)
return -1;
while (bytes > 0) {
gint toread, towrite;
toread = bytes;
if (bytes > 4096)
toread = 4096;
else
toread = bytes;
do {
towrite = read (fromfd, buffer, toread);
} while (towrite == -1 && errno == EINTR);
if (towrite == -1)
return -1;
/* check for 'end of file' */
if (towrite == 0) {
d (printf ("end of file?\n"));
break;
}
do {
toread = write (tofd, buffer, towrite);
} while (toread == -1 && errno == EINTR);
if (toread == -1)
return -1;
written += toread;
bytes -= toread;
}
d (printf ("written %d bytes\n", written));
return written;
}
#endif
#define PRE_SIZE (32)
#ifdef HAVE_BROKEN_SPOOL
static gint
camel_movemail_copy_filter (gint fromfd,
gint tofd,
goffset start,
gsize bytes,
CamelMimeFilter *filter)
{
gchar buffer[4096 + PRE_SIZE];
gint written = 0;
gchar *filterbuffer;
gint filterlen, filterpre;
d (printf ("writing %d bytes ... ", bytes));
camel_mime_filter_reset (filter);
if (lseek (fromfd, start, SEEK_SET) != start)
return -1;
while (bytes > 0) {
gint toread, towrite;
toread = bytes;
if (bytes > 4096)
toread = 4096;
else
toread = bytes;
do {
towrite = read (fromfd, buffer + PRE_SIZE, toread);
} while (towrite == -1 && errno == EINTR);
if (towrite == -1)
return -1;
d (printf ("read %d unfiltered bytes\n", towrite));
/* check for 'end of file' */
if (towrite == 0) {
d (printf ("end of file?\n"));
camel_mime_filter_complete (
filter, buffer + PRE_SIZE, towrite, PRE_SIZE,
&filterbuffer, &filterlen, &filterpre);
towrite = filterlen;
if (towrite == 0)
break;
} else {
camel_mime_filter_filter (
filter, buffer + PRE_SIZE, towrite, PRE_SIZE,
&filterbuffer, &filterlen, &filterpre);
towrite = filterlen;
}
d (printf ("writing %d filtered bytes\n", towrite));
do {
toread = write (tofd, filterbuffer, towrite);
} while (toread == -1 && errno == EINTR);
if (toread == -1)
return -1;
written += toread;
bytes -= toread;
}
d (printf ("written %d bytes\n", written));
return written;
}
/* write the headers back out again, but not he Content-Length header, because we dont
* want to maintain it! */
static gint
solaris_header_write (gint fd,
struct _camel_header_raw *header)
{
struct iovec iv[4];
gint outlen = 0, len;
iv[1].iov_base = ":";
iv[1].iov_len = 1;
iv[3].iov_base = "\n";
iv[3].iov_len = 1;
while (header) {
if (g_ascii_strcasecmp (header->name, "Content-Length")) {
iv[0].iov_base = header->name;
iv[0].iov_len = strlen (header->name);
iv[2].iov_base = header->value;
iv[2].iov_len = strlen (header->value);
do {
len = writev (fd, iv, 4);
} while (len == -1 && errno == EINTR);
if (len == -1)
return -1;
outlen += len;
}
header = header->next;
}
do {
len = write (fd, "\n", 1);
} while (len == -1 && errno == EINTR);
if (len == -1)
return -1;
outlen += 1;
d (printf ("Wrote %d bytes of headers\n", outlen));
return outlen;
}
/* Well, since Solaris is a tad broken wrt its 'mbox' folder format,
* we must convert it to a real mbox format. Thankfully this is
* mostly pretty easy */
static gint
camel_movemail_solaris (gint oldsfd,
gint dfd,
GError **error)
{
CamelMimeParser *mp;
gchar *buffer;
gint len;
gint sfd;
CamelMimeFilter *ffrom;
gint ret = 1;
gchar *from = NULL;
/* need to dup as the mime parser will close on finish */
sfd = dup (oldsfd);
if (sfd == -1) {
g_set_error (
error, G_IO_ERROR,
g_io_error_from_errno (errno),
_("Error copying mail temp file: %s"),
g_strerror (errno));
return -1;
}
mp = camel_mime_parser_new ();
camel_mime_parser_scan_from (mp, TRUE);
camel_mime_parser_init_with_fd (mp, sfd);
ffrom = camel_mime_filter_from_new ();
while (camel_mime_parser_step (mp, &buffer, &len) == CAMEL_MIME_PARSER_STATE_FROM) {
g_return_val_if_fail (camel_mime_parser_from_line (mp), -1);
from = g_strdup (camel_mime_parser_from_line (mp));
if (camel_mime_parser_step (mp, &buffer, &len) != CAMEL_MIME_PARSER_STATE_FROM_END) {
const gchar *cl;
gint length;
gint start, body;
goffset newpos;
ret = 0;
start = camel_mime_parser_tell_start_from (mp);
body = camel_mime_parser_tell (mp);
if (write (dfd, from, strlen (from)) != strlen (from))
goto fail;
/* write out headers, but NOT content-length header */
if (solaris_header_write (dfd, camel_mime_parser_headers_raw (mp)) == -1)
goto fail;
cl = camel_mime_parser_header (mp, "content-length", NULL);
if (cl == NULL) {
g_warning ("Required Content-Length header is missing from solaris mail box @ %d", (gint) camel_mime_parser_tell (mp));
camel_mime_parser_drop_step (mp);
camel_mime_parser_drop_step (mp);
camel_mime_parser_step (mp, &buffer, &len);
camel_mime_parser_unstep (mp);
length = camel_mime_parser_tell_start_from (mp) - body;
newpos = -1;
} else {
length = atoi (cl);
camel_mime_parser_drop_step (mp);
camel_mime_parser_drop_step (mp);
newpos = length + body;
}
/* copy body->length converting From lines */
if (camel_movemail_copy_filter (sfd, dfd, body, length, ffrom) == -1)
goto fail;
if (newpos != -1)
camel_mime_parser_seek (mp, newpos, SEEK_SET);
} else {
g_error ("Inalid parser state: %d", camel_mime_parser_state (mp));
}
g_free (from);
}
g_object_unref (mp);
g_object_unref (ffrom);
return ret;
fail:
g_free (from);
g_set_error (
error, G_IO_ERROR,
g_io_error_from_errno (errno),
_("Error copying mail temp file: %s"),
g_strerror (errno));
g_object_unref (mp);
g_object_unref (ffrom);
return -1;
}
#endif /* HAVE_BROKEN_SPOOL */