/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* camel-movemail.c: mbox copying function */ /* * Author: * Dan Winship * * Copyright 2000 Ximian, Inc. (www.ximian.com) * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public * License as published by the Free Software Foundation. * * 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 */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_ALLOCA_H #include #endif #include "camel-movemail.h" #include "camel-exception.h" #include "camel-mime-parser.h" #include "camel-mime-filter.h" #include "camel-mime-filter-from.h" #include "camel-lock-client.h" #define d(x) #ifdef MOVEMAIL_PATH #include static void movemail_external (const char *source, const char *dest, CamelException *ex); #endif #ifdef HAVE_BROKEN_SPOOL static int camel_movemail_copy_filter(int fromfd, int tofd, off_t start, size_t bytes, CamelMimeFilter *filter); static int camel_movemail_solaris (int oldsfd, int dfd, CamelException *ex); #else /* these could probably be exposed as a utility? (but only mbox needs it) */ static int camel_movemail_copy_file(int sfd, int dfd, CamelException *ex); #endif #if 0 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). * * Return Value: Returns -1 on error. **/ int camel_movemail(const char *source, const char *dest, CamelException *ex) { int lockid = -1; int res = -1; int sfd, dfd; struct stat st; 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, strerror (errno)); } return -1; } if (st.st_size == 0) return 0; /* open files */ sfd = open (source, O_RDWR); if (sfd == -1) { camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Could not open mail file %s: %s"), source, strerror (errno)); return -1; } 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, strerror (errno)); close (sfd); return -1; } /* lock our source mailbox */ lockid = camel_lock_helper_lock(source, ex); 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, ex); #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) { ftruncate (sfd, 0); } else { camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Failed to store mail in temp file %s: %s"), dest, strerror(errno)); res = -1; } } else close (dfd); close (sfd); camel_lock_helper_unlock(lockid); return res; } #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"), 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"), 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 #ifndef HAVE_BROKEN_SPOOL static int camel_movemail_copy_file(int sfd, int dfd, CamelException *ex) { int nread, nwrote; char buf[4096]; 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"), strerror (errno)); return -1; } 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"), strerror (errno)); return -1; } written += nwrote; nread -= nwrote; } } return 0; } #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) #ifdef HAVE_BROKEN_SPOOL 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; 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 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 oldsfd, int dfd, CamelException *ex) { CamelMimeParser *mp; char *buffer; int len; int sfd; CamelMimeFilterFrom *ffrom; int ret = 1; char *from = NULL; /* need to dup as the mime parser will close on finish */ sfd = dup(oldsfd); if (sfd == -1) { camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Error copying mail temp file: %s"), 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) == HSCAN_FROM) { g_assert(camel_mime_parser_from_line(mp)); from = g_strdup(camel_mime_parser_from_line(mp)); 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); 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", (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)); } g_free(from); } camel_object_unref((CamelObject *)mp); camel_object_unref((CamelObject *)ffrom); return ret; fail: g_free(from); camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Error copying mail temp file: %s"), strerror (errno)); camel_object_unref((CamelObject *)mp); camel_object_unref((CamelObject *)ffrom); return -1; } #endif /* HAVE_BROKEN_SPOOL */