aboutsummaryrefslogblamecommitdiffstats
path: root/camel/camel-movemail.c
blob: bd5022f42155b41b685370153c0bfb9995c49c13 (plain) (tree)



























                                                                           
                    





                   
                   
                   
                   



                            





                                   






                                                                    
                                                                            
     

                                                                                                                
      
 










                                                                       
    
    

                                                                         
                        
















                                                                      

                                                                         

                                                                            


                                                                  

                            
                       












                                                                     














                                                                          
                                                                 


                                                                     
                       


                      


                                                                 
                                                                           








                                                                            


                                                                        





                                     

















                                                                         

                                                                           











                                                                         

                                                                           

















                                                                         


                                                                             





                                     
                       





                                                         






                                                      

                                           
                                                                         
                                                                               
                                                                  





                                                                   

                                                                            
                                                                                 

                                                                              
                                                                          








                                                                      
           
                                           



                                                                         

                                                                          
                                                                  
                 
              
                            






                                  


















                                                                            
                                                                     










                                                                 
                                                              

































                                                                         
 

                                                                 

                                                                              

                        
 

      
 
     














































                                                                    
      


                     
     


































































































































































                                                                                                                                                   

                                                 









                                                         

                                                 


                  
      
 
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* camel-movemail.c: mbox copying function */

/*
 * Author:
 *  Dan Winship <danw@helixcode.com>
 *
 * Copyright 2000 Helix Code, Inc. (http://www.helixcode.com)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 */

#include <config.h>

#include <sys/stat.h>
#include <sys/uio.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <alloca.h>

#include "camel-movemail.h"
#include "camel-exception.h"

#include "camel-mime-parser.h"
#include "camel-mime-filter.h"
#include "camel-mime-filter-from.h"

#define d(x)

#ifdef MOVEMAIL_PATH
#include <sys/wait.h>

static void movemail_external (const char *source, const char *dest,
                   CamelException *ex);
#endif

/* these could probably be exposed as a utility? (but only mbox needs it) */
#if 0
static int camel_movemail_copy_filter(int fromfd, int tofd, off_t start, size_t bytes, CamelMimeFilter *filter);
static int camel_movemail_copy(int fromfd, int tofd, off_t start, size_t bytes);
#endif

/**
 * camel_movemail: Copy an mbox file from a shared spool directory to a
 * new folder in a Camel store
 * @source: source file
 * @dest: destination file
 * @ex: a CamelException
 *
 * 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).
 **/
void
camel_movemail (const char *source, const char *dest, CamelException *ex)
{
    gboolean locked;
    int sfd, dfd, tmpfd;
    char *locktmpfile, *lockfile;
    struct stat st;
    time_t now, timeout;
    int nread, nwrote;
    char buf[BUFSIZ];

    camel_exception_clear (ex);

    /* Stat and then open 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 (stat (source, &st) == -1) {
        if (errno != ENOENT) {
            camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
                          _("Could not check mail file "
                        "%s: %s"), source,
                          g_strerror (errno));
        }
        return;
    }
    if (st.st_size == 0)
        return;

    /* Create the unique lock file. */
    locktmpfile = g_strdup_printf ("%s.lock.XXXXXX", source);
#ifdef HAVE_MKSTEMP
    tmpfd = mkstemp (locktmpfile);
#else
    if (mktemp (locktmpfile)) {
        tmpfd = open (locktmpfile, O_RDWR | O_CREAT | O_EXCL,
                  S_IRUSR | S_IWUSR);
    } else
        tmpfd = -1;
#endif
    if (tmpfd == -1) {
        g_free (locktmpfile);
#ifdef MOVEMAIL_PATH
        if (errno == EACCES) {
            /* movemail_external will fail if the dest file
             * already exists, so if it does, return now,
             * let the fetch code process the mail that's
             * already there, and then the user can try again.
             */
            if (stat (dest, &st) == 0)
                return;

            movemail_external (source, dest, ex);
            return;
        }
#endif
        camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
                      _("Could not create lock file "
                    "for %s: %s"), source,
                      g_strerror (errno));
        return;
    }
    close (tmpfd);

    sfd = open (source, O_RDWR);
    if (sfd == -1) {
        camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
                      _("Could not open mail file %s: %s"),
                      source, g_strerror (errno));
        unlink (locktmpfile);
        g_free (locktmpfile);
        return;
    }

    dfd = open (dest, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);
    if (dfd == -1) {
        camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
                      _("Could not open temporary mail "
                    "file %s: %s"), dest,
                      g_strerror (errno));
        close (sfd);
        unlink (locktmpfile);
        g_free (locktmpfile);
        return;
    }

    lockfile = g_strdup_printf ("%s.lock", source);
    locked = FALSE;
    time (&timeout);
    timeout += 30;

    /* Loop trying to lock the file for 30 seconds. */
    while (time (&now) < timeout) {
        /* Try to make the lock. */
        if (symlink (locktmpfile, lockfile) == 0) {
            locked = TRUE;
            break;
        }

        /* If we fail for a reason other than that someone
         * else has the lock, then abort.
         */
        if (errno != EEXIST) {
            camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
                          _("Could not create lock "
                        "file for %s: %s"), source,
                          g_strerror (errno));
            break;
        }

        /* Check the modtime on the lock file. */
        if (stat (lockfile, &st) == -1) {
            /* If the lockfile disappeared, try again. */
            if (errno == ENOENT)
                continue;

            /* Some other error. Abort. */
            camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
                          _("Could not test lock "
                        "file for %s: %s"), source,
                          g_strerror (errno));
            break;
        }

        /* If the lock file is stale, remove it and try again. */
        if (st.st_mtime < now - 60) {
            unlink (lockfile);
            continue;
        }

        /* Otherwise, sleep and try again. */
        sleep (5);
    }

    if (!locked) {
        /* Something has gone awry. */
        if (now >= timeout) {
            camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
                          _("Timed out trying to get "
                        "lock file on %s. Try again "
                        "later."), source);
        }
        g_free (lockfile);
        unlink (locktmpfile);
        g_free (locktmpfile);
        close (sfd);
        close (dfd);
        return;
    }

    /* OK. We have the file locked now. */

    /* FIXME: Set a timer to keep the file locked. */

    while (1) {
        int written = 0;

        nread = read (sfd, buf, sizeof (buf));
        if (nread == 0)
            break;
        else if (nread == -1) {
            if (errno == EINTR)
                continue;
            camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
                          _("Error reading mail file: %s"),
                          g_strerror (errno));
            break;
        }

        while (nread) {
            nwrote = write (dfd, buf + written, nread);
            if (nwrote == -1) {
                if (errno == EINTR)
                    continue; /* continues inner loop */
                camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
                              _("Error writing "
                            "mail temp file: %s"),
                              g_strerror (errno));
                break;
            }
            written += nwrote;
            nread -= nwrote;
        }
    }

    /* If no errors occurred copying the data, and we successfully
     * close the destination file, then truncate the source file.
     */
    if (!camel_exception_is_set (ex)) {
        if (close (dfd) == 0)
            ftruncate (sfd, 0);
        else {
            camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
                          _("Failed to store mail in "
                        "temp file %s: %s"), dest,
                          g_strerror (errno));
        }
    } else
        close (dfd);
    close (sfd);

    /* Clean up lock files. */
    unlink (lockfile);
    g_free (lockfile);
    unlink (locktmpfile);
    g_free (locktmpfile);
}

#ifdef MOVEMAIL_PATH
static void
movemail_external (const char *source, const char *dest, CamelException *ex)
{
    sigset_t mask, omask;
    pid_t pid;
    int fd[2], len = 0, nread, status;
    char 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);
        camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
                      _("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);
        camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
                      _("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) {
        camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
                      _("Movemail program failed: %s"),
                      output ? output : _("(Unknown error)"));
    }
    g_free (output);
}
#endif


#if 0
static int
camel_movemail_copy(int fromfd, int tofd, off_t start, size_t bytes)
{
        char buffer[4096];
        int written = 0;

    d(printf("writing %d bytes ... ", bytes));

    if (lseek(fromfd, start, SEEK_SET) != start)
        return -1;

        while (bytes>0) {
                int 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)

#if 0
static int
camel_movemail_copy_filter(int fromfd, int tofd, off_t start, size_t bytes, CamelMimeFilter *filter)
{
        char buffer[4096+PRE_SIZE];
        int written = 0;
    char *filterbuffer;
    int 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) {
                int 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;

                /* 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;
        }

        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 int
solaris_header_write(int fd, struct _header_raw *header)
{
        struct iovec iv[4];
        int 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 (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 int
camel_movemail_solaris (int sfd, int dfd, CamelException *ex)
{
    CamelMimeParser *mp;
    char *buffer;
    int len;
    CamelMimeFilterFrom *ffrom;
    int ret = 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) == HSCAN_FROM) {
        if (camel_mime_parser_step(mp, &buffer, &len) != HSCAN_FROM_END) {
            const char *cl;
            int length;
            int start, body;
            off_t newpos;

            ret = 0;

            start = camel_mime_parser_tell_start_from(mp);
            body = camel_mime_parser_tell(mp);

            /* write out headers, but NOT content-length header */
            solaris_header_write(dfd, camel_mime_parser_headers_raw(mp));

            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", (int)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, (CamelMimeFilter *)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));
        }
    }

    camel_object_unref((CamelObject *)mp);
    camel_object_unref((CamelObject *)ffrom);

    return ret;

fail:
    camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
                  "Error copying "
                  "mail temp file: %s",
                  g_strerror (errno));


    camel_object_unref((CamelObject *)mp);
    camel_object_unref((CamelObject *)ffrom);

    return -1;
}
#endif