1 /* movemail foo bar -- move file foo to file bar,
3 Copyright (C) 1986, 1992, 1993, 1994, 1996 Free Software Foundation, Inc.
5 Copyright (C) 2005 Johann "Myrkraverk" Oskarsson <johann@myrkraverk.com>
7 This file is part of SXEmacs.
9 SXEmacs is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
14 SXEmacs 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 General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
22 Please mail bugs and suggestions to the SXEmacs maintainer.
27 * You *must* coordinate the locking method used by movemail with that
28 * used by your mail delivery agent, as well as that of the other mail
29 * user agents on your system. movemail allows you to do this at run
30 * time via the -m flag. Moreover, it uses a default determined by
31 * the MAIL_LOCK_DOT, MAIL_LOCK_LOCKF, MAIL_LOCK_FLOCK,
32 * MAIL_LOCK_LOCKING, and MAIL_LOCK_MMDF preprocessor settings.
36 * Mike Sperber <sperber@informatik.uni-tuebingen.de> reorganized
37 * everything that has to with locking in December 1999.
41 * Modified January, 1986 by Michael R. Gretzinger (Project Athena)
43 * Added POP (Post Office Protocol) service. When compiled
44 * -DMAIL_USE_POP movemail will accept input filename arguments of the
45 * form "po:username". This will cause movemail to open a connection
46 * to a pop server running on $MAILHOST (environment variable).
47 * Movemail must be setuid to root in order to work with POP.
49 * New module: popmail.c
51 * main - added code within #ifdef MAIL_USE_POP; added setuid
52 * (getuid ()) after POP code.
53 * New routines in movemail.c:
54 * get_errmsg - return pointer to system error message
56 * Modified August, 1993 by Jonathan Kamens (OpenVision Technologies)
58 * Move all of the POP code into a separate file, "pop.c".
59 * Use strerror instead of get_errmsg.
63 #define NO_SHORTNAMES /* Tell config not to load remap.h */
64 #define DONT_ENCAPSULATE
66 #include <sys/types.h>
70 #include "../src/sysfile.h"
71 #include "../src/syswait.h"
72 #include "../src/systime.h"
80 #include "../src/regex.h"
86 extern int optind, opterr;
89 char *strerror(int errnum);
90 #endif /* HAVE_STRERROR */
93 #define DIRECTORY_SEP '/'
95 #ifndef IS_DIRECTORY_SEP
96 #define IS_DIRECTORY_SEP(_c_) ((_c_) == DIRECTORY_SEP)
99 #if defined (HAVE_UNISTD_H)
101 #endif /* unistd.h */
109 #if defined (HAVE_FCNTL_H)
114 #include <sys/locking.h>
118 extern int lk_open(), lk_close();
121 /* Cancel substitutions made by config.h for Emacs. */
127 static void fatal(char *, char *);
128 static void error(const char *, const char *, const char *);
129 static void usage(int);
130 static void pfatal_with_name(char *);
131 static void pfatal_and_delete(char *);
132 static char *concat(char *, char *, char *);
133 static long *xmalloc(unsigned int);
135 static int popmail(char *, char *, char *);
136 static int pop_retr(popserver server, int msgno,
137 int (*action) (char *, FILE *), FILE * arg);
138 static int mbx_write(char *, FILE *);
139 static int mbx_delimit_begin(FILE *);
140 static int mbx_delimit_end(FILE *);
141 static struct re_pattern_buffer *compile_regex(char *regexp_pattern);
142 static int pop_search_top(popserver server, int msgno, int lines,
143 struct re_pattern_buffer *regexp);
149 int keep_messages = 0;
150 struct re_pattern_buffer *regexp_pattern = 0;
151 int match_lines = 10;
154 #define VERBOSE(x) if (verbose) { printf x; fflush(stdout); }
156 #ifdef HAVE_GETOPT_LONG
157 struct option longopts[] = {
158 {"inbox", required_argument, NULL, 'i'},
159 {"outfile", required_argument, NULL, 'o'},
161 {"password", required_argument, NULL, 'p'},
162 {"reverse-pop-order", no_argument, NULL, 'x'},
163 {"keep-messages", no_argument, NULL, 'k'},
164 {"regex", required_argument, NULL, 'r'},
165 {"match-lines", required_argument, NULL, 'l'},
167 {"lock-method", required_argument, NULL, 'm'},
168 {"help", no_argument, NULL, 'h'},
169 {"verbose", no_argument, NULL, 'v'},
180 #if defined(MAIL_LOCK_FLOCK) && defined(HAVE_FLOCK)
181 #define DEFAULT_LOCKING FLOCKING
182 #elif defined(MAIL_LOCK_LOCKF) && defined(HAVE_LOCKF)
183 #define DEFAULT_LOCKING LOCKFING
184 #elif defined(MAIL_LOCK_MMDF) && defined(HAVE_MMDF)
185 #define DEFAULT_LOCKING MMDF
186 #elif defined(MAIL_LOCK_LOCKING) && defined(HAVE_LOCKING)
187 #define DEFAULT_LOCKING LOCKING
189 #define DEFAULT_LOCKING DOTLOCKING
192 #ifndef DISABLE_DIRECT_ACCESS
193 static void lock_dot(char *);
195 static void unlock_dot(char *);
196 static int parse_lock_method(char *);
197 static char *unparse_lock_method(int);
199 int main(int argc, char *argv[])
201 char *inname = 0, *outname = 0;
202 #if defined MAIL_USE_POP
204 #endif /* MAIL_USE_POP */
205 #ifndef DISABLE_DIRECT_ACCESS
211 int lock_method = DEFAULT_LOCKING;
213 char *maybe_lock_env;
215 maybe_lock_env = getenv("EMACSLOCKMETHOD");
216 if (maybe_lock_env) {
217 printf("maybe-lock_env: %s\n", maybe_lock_env);
218 lock_method = parse_lock_method(maybe_lock_env);
223 char *optstring = "i:o:m:p:l:r:xvhk";
225 char *optstring = "i:o:m:vh";
229 int opt = getopt_long(argc, argv, optstring, longopts, 0);
231 int opt = getopt(argc, argv, optstring);
233 # error "movemail cannot be built without getopt, preferably getopt_long"
241 case 1: /* one of the standard arguments seen */
244 } else if (!outname) {
246 #if defined MAIL_USE_POP
249 #endif /* MAIL_USE_POP */
253 case 'i': /* infile */
257 case 'o': /* outfile */
261 case 'p': /* pop password */
270 case 'l': /* lines to match */
271 match_lines = atoi(optarg);
274 case 'r': /* regular expression */
275 regexp_pattern = compile_regex(optarg);
280 lock_method = parse_lock_method(optarg);
293 while (optind < argc) {
295 inname = argv[optind];
296 } else if (!outname) {
297 outname = argv[optind];
298 #if defined MAIL_USE_POP
300 poppass = argv[optind];
301 #endif /* MAIL_USE_POP */
306 if (!inname || !outname) {
311 if (lock_method == MMDF)
316 fatal("Destination file name is empty", 0);
318 VERBOSE(("checking access to output file\n"));
319 /* Check access to output file. */
320 if (access(outname, F_OK) == 0 && access(outname, W_OK) != 0)
321 pfatal_with_name(outname);
323 /* Also check that outname's directory is writable to the real uid. */
325 char *buf = (char *)xmalloc(strlen(outname) + 1);
327 strcpy(buf, outname);
328 cp = buf + strlen(buf);
329 while (cp > buf && !IS_DIRECTORY_SEP(cp[-1]))
333 if (access(buf, W_OK) != 0)
334 pfatal_with_name(buf);
339 if (!strncmp(inname, "po:", 3)) {
340 int retcode = popmail(inname + 3, outname, poppass);
344 #endif /* MAIL_USE_POP */
346 #ifndef DISABLE_DIRECT_ACCESS
348 /* Check access to input file. */
349 if (access(inname, R_OK | W_OK) != 0)
350 pfatal_with_name(inname);
355 VERBOSE(("opening input file\n"));
357 switch (lock_method) {
359 indesc = open(inname, O_RDONLY);
363 indesc = open(inname, O_RDWR);
368 indesc = open(inname, O_RDWR);
373 indesc = open(inname, O_RDWR);
378 indesc = lk_open(inname, O_RDONLY, 0, 0, 10);
386 pfatal_with_name(inname);
389 /* In case movemail is setuid to root, make sure the user can
390 read the output file. */
391 umask(umask(0) & 0333);
394 outdesc = open(outname, O_WRONLY | O_CREAT | O_EXCL, 0666);
396 pfatal_with_name(outname);
398 VERBOSE(("locking input file\n"));
400 switch (lock_method) {
403 if (lockf(indesc, F_LOCK, 0) < 0)
404 pfatal_with_name(inname);
409 if (flock(indesc, LOCK_EX) < 0)
410 pfatal_with_name(inname);
415 if (locking(indesc, LK_RLCK, -1L) < 0)
416 pfatal_with_name(inname);
426 VERBOSE(("copying input file to output file\n"));
431 nread = read(indesc, buf, sizeof buf);
433 nread != write(outdesc, buf, nread)) {
434 int saved_errno = errno;
437 pfatal_with_name(outname);
439 if (nread < (int)sizeof(buf))
445 if (fsync(outdesc) < 0)
446 pfatal_and_delete(outname);
449 /* Check to make sure no errors before we zap the inbox. */
450 if (close(outdesc) != 0)
451 pfatal_and_delete(outname);
453 VERBOSE(("deleting or truncating input file\n"));
455 switch (lock_method) {
459 #ifdef HAVE_FTRUNCATE
460 if(ftruncate(indesc, 0L)!=0)
461 pfatal_and_delete(inname);
463 close(open(inname, O_CREAT | O_TRUNC | O_RDWR, 0666));
469 lk_close(indesc, 0, 0, 0);
483 if (!WIFEXITED(status))
485 else if (WEXITSTATUS(status) != 0)
486 exit(WEXITSTATUS(status));
488 if (lock_method == DOTLOCKING)
491 #endif /* not DISABLE_DIRECT_ACCESS */
496 static void usage(int lock_method)
498 printf ("Usage: movemail [-rvxkh] [-l lines ] [-m method ] [-i] "
499 "inbox [-o] destfile [[-p] POP-password]\n");
500 printf("where method is one of: dot");
513 printf("\nDefault is: %s\n", unparse_lock_method(lock_method));
517 static char *unparse_lock_method(int lock_method)
519 switch (lock_method) {
536 static int parse_lock_method(char *method_name)
538 if (!strcmp("dot", method_name) || !strcmp("file", method_name))
541 else if (!strcmp("lockf", method_name))
545 else if (!strcmp("flock", method_name))
549 else if (!strcmp("mmdf", method_name))
553 else if (!strcmp("locking", method_name))
557 fatal("invalid lock method: %s", method_name);
558 return 0; /* unreached */
561 static char *dot_filename(char *filename)
563 return concat(filename, ".lock", "");
566 static char *dotlock_filename = NULL;
568 #ifndef DISABLE_DIRECT_ACCESS
569 static void lock_dot(char *filename)
578 dotlock_filename = (char *)xmalloc(strlen(filename) + 1);
580 /* Use a lock file named after our first argument with .lock appended:
581 If it exists, the mail file is locked. */
583 lockname = dot_filename(filename);
584 tempname = (char *)xmalloc(strlen(filename) + strlen("EXXXXXX") + 1);
585 strcpy(tempname, filename);
586 p = tempname + strlen(tempname);
587 while (p != tempname && !IS_DIRECTORY_SEP(p[-1]))
590 strcpy(p, "EXXXXXX");
598 /* Create the lock file, but not under the lock file name. */
599 /* Give up if cannot do that. */
602 desc = mkstemp(tempname);
604 desc = open(tempname, O_WRONLY | O_CREAT | O_EXCL, 0666);
608 int msz = strlen(tempname) + 50;
609 char *message = (char *)xmalloc(msz);
610 int sz = snprintf(message, msz,
611 "%s--see source file lib-src/movemail.c",
613 assert(sz>=0 && sz < msz);
614 pfatal_with_name(message);
618 tem = link(tempname, lockname);
624 /* If lock file is five minutes old, unlock it.
625 Five minutes should be good enough to cope with crashes
626 and wedgitude, and long enough to avoid being fooled
627 by time differences between machines. */
628 if (stat(lockname, &st) >= 0) {
630 if (st.st_ctime < now - 300)
634 strcpy(dotlock_filename, filename);
636 #endif /* not DISABLE_DIRECT_ACCESS */
638 static void unlock_dot(char *filename)
640 unlink(dot_filename(filename));
643 static void maybe_unlock_dot(void)
645 if (dotlock_filename)
646 unlock_dot(dotlock_filename);
649 /* Print error message and exit. */
651 static void fatal(char *s1, char *s2)
658 /* Print error message. `s1' is printf control string, `s2' is arg for it. */
660 static void error(const char *s1, const char *s2, const char *s3)
662 fprintf(stderr, "movemail: ");
663 fprintf(stderr, s1, s2, s3);
664 fprintf(stderr, "\n");
667 static void pfatal_with_name(char *name)
669 char *s = concat("", strerror(errno), " for %s");
673 static void pfatal_and_delete(char *name)
675 char *s = concat("", strerror(errno), " for %s");
680 /* Return a newly-allocated string whose contents concatenate those of
683 static char *concat(char *s1, char *s2, char *s3)
685 int len1 = strlen(s1), len2 = strlen(s2), len3 = strlen(s3);
686 char *result = (char *)xmalloc(len1 + len2 + len3 + 1);
688 strncpy(result, s1, len1+1);
689 strncpy(result + len1, s2, len2+1);
690 strncpy(result + len1 + len2, s3, len3+1);
691 *(result + len1 + len2 + len3) = '\0';
696 /* Like malloc but get fatal error if memory is exhausted. */
698 static long *xmalloc(unsigned int size)
700 long *result = (long *)malloc(size);
702 fatal("virtual memory exhausted", 0);
706 /* This is the guts of the interface to the Post Office Protocol. */
710 #include <sys/socket.h>
711 #include <netinet/in.h>
714 #include "../src/syspwd.h"
716 #define POP_ERROR (-1)
717 #define POP_RETRIEVED (0)
723 char ibuffer[BUFSIZ];
724 char obuffer[BUFSIZ];
727 static int popmail(char *user, char *outfile, char *password)
732 short *retrieved_list;
736 VERBOSE(("opening server\n"));
737 server = pop_open(0, user, password, POP_NO_GETPASS);
739 error("%s", pop_error, NULL);
743 VERBOSE(("stat'ing messages\n"));
744 if (pop_stat(server, &nmsgs, &nbytes)) {
745 error("%s", pop_error, NULL);
750 VERBOSE(("closing server\n"));
755 /* build a retrieved table */
756 retrieved_list = (short *)xmalloc(sizeof(short) * (nmsgs + 1));
757 memset(retrieved_list, 0, sizeof(short) * (nmsgs + 1));
759 mbfi = open(outfile, O_WRONLY | O_CREAT | O_EXCL, 0666);
762 error("Error in open: %s, %s", strerror(errno), outfile);
765 fchown(mbfi, getuid(), (gid_t) - 1);
767 if ((mbf = fdopen(mbfi, "wb")) == NULL) {
769 error("Error in fdopen: %s", strerror(errno), NULL);
775 for (idx = 0; idx < nmsgs; idx++) {
776 i = reverse ? nmsgs - idx : idx + 1;
777 VERBOSE(("checking message %d \n", i));
781 pop_search_top(server, i, match_lines,
782 regexp_pattern) == POP_RETRIEVED) {
783 VERBOSE(("retrieving message %d \n", i));
784 mbx_delimit_begin(mbf);
785 if (pop_retr(server, i, mbx_write, mbf) !=
787 error("%s", Errmsg, NULL);
792 retrieved_list[i] = 1;
794 mbx_delimit_end(mbf);
797 error("Error in fflush: %s", strerror(errno),
806 /* On AFS, a call to write only modifies the file in the local
807 * workstation's AFS cache. The changes are not written to the server
808 * until a call to fsync or close is made. Users with AFS home
809 * directories have lost mail when over quota because these checks were
810 * not made in previous versions of movemail. */
813 if (fsync(mbfi) < 0) {
814 error("Error in fsync: %s", strerror(errno), NULL);
819 if (close(mbfi) == -1) {
820 error("Error in close: %s", strerror(errno), NULL);
824 if (!keep_messages) {
825 for (i = 1; i <= nmsgs; i++) {
826 if (retrieved_list[i] == 1) {
827 VERBOSE(("deleting message %d \n", i));
828 if (pop_delete(server, i)) {
829 error("%s", pop_error, NULL);
837 VERBOSE(("closing server \n"));
838 if (pop_quit(server)) {
839 error("%s", pop_error, NULL);
847 pop_retr(popserver server, int msgno, int (*action) (char *, FILE *),
853 if (pop_retrieve_first(server, msgno, &line)) {
854 strncpy(Errmsg, pop_error, sizeof(Errmsg));
855 Errmsg[sizeof(Errmsg) - 1] = '\0';
859 while (!(ret = pop_retrieve_next(server, &line))) {
863 if ((*action) (line, arg) != POP_RETRIEVED) {
864 strcpy(Errmsg, strerror(errno));
871 strncpy(Errmsg, pop_error, sizeof(Errmsg));
872 Errmsg[sizeof(Errmsg) - 1] = '\0';
876 return (POP_RETRIEVED);
879 /* search the top lines of each message looking for a match */
881 pop_search_top(popserver server, int msgno, int lines,
882 struct re_pattern_buffer *regexp)
886 int match = POP_DONE;
888 if (pop_top_first(server, msgno, lines, &line)) {
889 strncpy(Errmsg, pop_error, sizeof(Errmsg));
890 Errmsg[sizeof(Errmsg) - 1] = '\0';
894 while (!(ret = pop_top_next(server, &line))) {
898 /* VERBOSE (("checking %s\n", line)); */
899 if (match != POP_RETRIEVED) {
901 re_match(regexp, line, strlen(line), 0,
903 strcpy(Errmsg, "error in regular expression");
906 } else if (ret >= 0) {
907 match = POP_RETRIEVED;
913 strncpy(Errmsg, pop_error, sizeof(Errmsg));
914 Errmsg[sizeof(Errmsg) - 1] = '\0';
921 /* Do this as a macro instead of using strcmp to save on execution time. */
922 #define IS_FROM_LINE(a) ((a[0] == 'F') \
928 static int mbx_write(char *line, FILE * mbf)
930 if (IS_FROM_LINE(line)) {
931 if (fputc('>', mbf) == EOF)
934 if (fputs(line, mbf) == EOF)
936 if (fputc(0x0a, mbf) == EOF)
938 return (POP_RETRIEVED);
941 static int mbx_delimit_begin(FILE * mbf)
943 if (fputs("\f\n0, unseen,,\n", mbf) == EOF)
945 return (POP_RETRIEVED);
948 static int mbx_delimit_end(FILE * mbf)
950 if (putc('\037', mbf) == EOF)
952 return (POP_RETRIEVED);
955 /* Turn a name, which is an ed-style (but Emacs syntax) regular
956 expression, into a real regular expression by compiling it. */
957 static struct re_pattern_buffer *compile_regex(char *pattern)
960 struct re_pattern_buffer *patbuf = 0;
963 (struct re_pattern_buffer *)
964 xmalloc(sizeof(struct re_pattern_buffer));
965 patbuf->translate = NULL;
966 patbuf->fastmap = NULL;
967 patbuf->buffer = NULL;
968 patbuf->allocated = 0;
970 err = re_compile_pattern(pattern, strlen(pattern), patbuf);
972 error("%s while compiling pattern", err, NULL);
979 #endif /* MAIL_USE_POP */
981 #ifndef HAVE_STRERROR
982 char *strerror(int errnum)
984 extern char *sys_errlist[];
987 if (errnum >= 0 && errnum < sys_nerr)
988 return sys_errlist[errnum];
989 return (char *)"Unknown error";
992 #endif /* ! HAVE_STRERROR */