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"
84 extern int optind, opterr;
87 char *strerror(int errnum);
88 #endif /* HAVE_STRERROR */
91 #define DIRECTORY_SEP '/'
93 #ifndef IS_DIRECTORY_SEP
94 #define IS_DIRECTORY_SEP(_c_) ((_c_) == DIRECTORY_SEP)
97 #if defined (HAVE_UNISTD_H)
107 #if defined (HAVE_FCNTL_H)
112 #include <sys/locking.h>
116 extern int lk_open(), lk_close();
119 /* Cancel substitutions made by config.h for Emacs. */
125 static void fatal(char *, char *);
126 static void error(const char *, const char *, const char *);
127 static void usage(int);
128 static void pfatal_with_name(char *);
129 static void pfatal_and_delete(char *);
130 static char *concat(char *, char *, char *);
131 static long *xmalloc(unsigned int);
133 static int popmail(char *, char *, char *);
134 static int pop_retr(popserver server, int msgno,
135 int (*action) (char *, FILE *), FILE * arg);
136 static int mbx_write(char *, FILE *);
137 static int mbx_delimit_begin(FILE *);
138 static int mbx_delimit_end(FILE *);
139 static struct re_pattern_buffer *compile_regex(char *regexp_pattern);
140 static int pop_search_top(popserver server, int msgno, int lines,
141 struct re_pattern_buffer *regexp);
147 int keep_messages = 0;
148 struct re_pattern_buffer *regexp_pattern = 0;
149 int match_lines = 10;
152 #define VERBOSE(x) if (verbose) { printf x; fflush(stdout); }
154 #ifdef HAVE_GETOPT_LONG
155 struct option longopts[] = {
156 {"inbox", required_argument, NULL, 'i'},
157 {"outfile", required_argument, NULL, 'o'},
159 {"password", required_argument, NULL, 'p'},
160 {"reverse-pop-order", no_argument, NULL, 'x'},
161 {"keep-messages", no_argument, NULL, 'k'},
162 {"regex", required_argument, NULL, 'r'},
163 {"match-lines", required_argument, NULL, 'l'},
165 {"lock-method", required_argument, NULL, 'm'},
166 {"help", no_argument, NULL, 'h'},
167 {"verbose", no_argument, NULL, 'v'},
178 #if defined(MAIL_LOCK_FLOCK) && defined(HAVE_FLOCK)
179 #define DEFAULT_LOCKING FLOCKING
180 #elif defined(MAIL_LOCK_LOCKF) && defined(HAVE_LOCKF)
181 #define DEFAULT_LOCKING LOCKFING
182 #elif defined(MAIL_LOCK_MMDF) && defined(HAVE_MMDF)
183 #define DEFAULT_LOCKING MMDF
184 #elif defined(MAIL_LOCK_LOCKING) && defined(HAVE_LOCKING)
185 #define DEFAULT_LOCKING LOCKING
187 #define DEFAULT_LOCKING DOTLOCKING
190 #ifndef DISABLE_DIRECT_ACCESS
191 static void lock_dot(char *);
193 static void unlock_dot(char *);
194 static int parse_lock_method(char *);
195 static char *unparse_lock_method(int);
197 int main(int argc, char *argv[])
199 char *inname = 0, *outname = 0, *poppass = 0;
200 #ifndef DISABLE_DIRECT_ACCESS
206 int lock_method = DEFAULT_LOCKING;
208 char *maybe_lock_env;
210 maybe_lock_env = getenv("EMACSLOCKMETHOD");
211 if (maybe_lock_env) {
212 printf("maybe-lock_env: %s\n", maybe_lock_env);
213 lock_method = parse_lock_method(maybe_lock_env);
218 char *optstring = "i:o:m:p:l:r:xvhk";
220 char *optstring = "i:o:m:vh";
224 int opt = getopt_long(argc, argv, optstring, longopts, 0);
226 int opt = getopt(argc, argv, optstring);
228 # error "movemail cannot be built without getopt, preferably getopt_long"
236 case 1: /* one of the standard arguments seen */
245 case 'i': /* infile */
249 case 'o': /* outfile */
253 case 'p': /* pop password */
262 case 'l': /* lines to match */
263 match_lines = atoi(optarg);
266 case 'r': /* regular expression */
267 regexp_pattern = compile_regex(optarg);
272 lock_method = parse_lock_method(optarg);
285 while (optind < argc) {
287 inname = argv[optind];
289 outname = argv[optind];
291 poppass = argv[optind];
295 if (!inname || !outname) {
300 if (lock_method == MMDF)
305 fatal("Destination file name is empty", 0);
307 VERBOSE(("checking access to output file\n"));
308 /* Check access to output file. */
309 if (access(outname, F_OK) == 0 && access(outname, W_OK) != 0)
310 pfatal_with_name(outname);
312 /* Also check that outname's directory is writable to the real uid. */
314 char *buf = (char *)xmalloc(strlen(outname) + 1);
316 strcpy(buf, outname);
317 cp = buf + strlen(buf);
318 while (cp > buf && !IS_DIRECTORY_SEP(cp[-1]))
322 if (access(buf, W_OK) != 0)
323 pfatal_with_name(buf);
328 if (!strncmp(inname, "po:", 3)) {
329 int retcode = popmail(inname + 3, outname, poppass);
333 #endif /* MAIL_USE_POP */
335 #ifndef DISABLE_DIRECT_ACCESS
337 /* Check access to input file. */
338 if (access(inname, R_OK | W_OK) != 0)
339 pfatal_with_name(inname);
344 VERBOSE(("opening input file\n"));
346 switch (lock_method) {
348 indesc = open(inname, O_RDONLY);
352 indesc = open(inname, O_RDWR);
357 indesc = open(inname, O_RDWR);
362 indesc = open(inname, O_RDWR);
367 indesc = lk_open(inname, O_RDONLY, 0, 0, 10);
375 pfatal_with_name(inname);
378 /* In case movemail is setuid to root, make sure the user can
379 read the output file. */
380 umask(umask(0) & 0333);
383 outdesc = open(outname, O_WRONLY | O_CREAT | O_EXCL, 0666);
385 pfatal_with_name(outname);
387 VERBOSE(("locking input file\n"));
389 switch (lock_method) {
392 if (lockf(indesc, F_LOCK, 0) < 0)
393 pfatal_with_name(inname);
398 if (flock(indesc, LOCK_EX) < 0)
399 pfatal_with_name(inname);
404 if (locking(indesc, LK_RLCK, -1L) < 0)
405 pfatal_with_name(inname);
415 VERBOSE(("copying input file to output file\n"));
421 nread = read(indesc, buf, sizeof buf);
422 if (nread != write(outdesc, buf, nread)) {
423 int saved_errno = errno;
426 pfatal_with_name(outname);
428 if (nread < (int)sizeof buf)
434 if (fsync(outdesc) < 0)
435 pfatal_and_delete(outname);
438 /* Check to make sure no errors before we zap the inbox. */
439 if (close(outdesc) != 0)
440 pfatal_and_delete(outname);
442 VERBOSE(("deleting or truncating input file\n"));
444 switch (lock_method) {
448 #ifdef HAVE_FTRUNCATE
449 ftruncate(indesc, 0L);
451 close(open(inname, O_CREAT | O_TRUNC | O_RDWR, 0666));
457 lk_close(indesc, 0, 0, 0);
471 if (!WIFEXITED(status))
473 else if (WEXITSTATUS(status) != 0)
474 exit(WEXITSTATUS(status));
476 if (lock_method == DOTLOCKING)
479 #endif /* not DISABLE_DIRECT_ACCESS */
484 static void usage(int lock_method)
486 printf ("Usage: movemail [-rvxkh] [-l lines ] [-m method ] [-i] "
487 "inbox [-o] destfile [[-p] POP-password]\n");
488 printf("where method is one of: dot");
501 printf("\nDefault is: %s\n", unparse_lock_method(lock_method));
505 static char *unparse_lock_method(int lock_method)
507 switch (lock_method) {
524 static int parse_lock_method(char *method_name)
526 if (!strcmp("dot", method_name) || !strcmp("file", method_name))
529 else if (!strcmp("lockf", method_name))
533 else if (!strcmp("flock", method_name))
537 else if (!strcmp("mmdf", method_name))
541 else if (!strcmp("locking", method_name))
545 fatal("invalid lock method: %s", method_name);
546 return 0; /* unreached */
549 static char *dot_filename(char *filename)
551 return concat(filename, ".lock", "");
554 static char *dotlock_filename = NULL;
556 #ifndef DISABLE_DIRECT_ACCESS
557 static void lock_dot(char *filename)
566 dotlock_filename = (char *)xmalloc(strlen(filename) + 1);
568 /* Use a lock file named after our first argument with .lock appended:
569 If it exists, the mail file is locked. */
571 lockname = dot_filename(filename);
572 tempname = (char *)xmalloc(strlen(filename) + strlen("EXXXXXX") + 1);
573 strcpy(tempname, filename);
574 p = tempname + strlen(tempname);
575 while (p != tempname && !IS_DIRECTORY_SEP(p[-1]))
578 strcpy(p, "EXXXXXX");
586 /* Create the lock file, but not under the lock file name. */
587 /* Give up if cannot do that. */
590 desc = mkstemp(tempname);
592 desc = open(tempname, O_WRONLY | O_CREAT | O_EXCL, 0666);
596 char *message = (char *)xmalloc(strlen(tempname) + 50);
598 "%s--see source file lib-src/movemail.c",
600 pfatal_with_name(message);
604 tem = link(tempname, lockname);
610 /* If lock file is five minutes old, unlock it.
611 Five minutes should be good enough to cope with crashes
612 and wedgitude, and long enough to avoid being fooled
613 by time differences between machines. */
614 if (stat(lockname, &st) >= 0) {
616 if (st.st_ctime < now - 300)
620 strcpy(dotlock_filename, filename);
622 #endif /* not DISABLE_DIRECT_ACCESS */
624 static void unlock_dot(char *filename)
626 unlink(dot_filename(filename));
629 static void maybe_unlock_dot(void)
631 if (dotlock_filename)
632 unlock_dot(dotlock_filename);
635 /* Print error message and exit. */
637 static void fatal(char *s1, char *s2)
644 /* Print error message. `s1' is printf control string, `s2' is arg for it. */
646 static void error(const char *s1, const char *s2, const char *s3)
648 fprintf(stderr, "movemail: ");
649 fprintf(stderr, s1, s2, s3);
650 fprintf(stderr, "\n");
653 static void pfatal_with_name(char *name)
655 char *s = concat("", strerror(errno), " for %s");
659 static void pfatal_and_delete(char *name)
661 char *s = concat("", strerror(errno), " for %s");
666 /* Return a newly-allocated string whose contents concatenate those of
669 static char *concat(char *s1, char *s2, char *s3)
671 int len1 = strlen(s1), len2 = strlen(s2), len3 = strlen(s3);
672 char *result = (char *)xmalloc(len1 + len2 + len3 + 1);
675 strcpy(result + len1, s2);
676 strcpy(result + len1 + len2, s3);
677 *(result + len1 + len2 + len3) = 0;
682 /* Like malloc but get fatal error if memory is exhausted. */
684 static long *xmalloc(unsigned int size)
686 long *result = (long *)malloc(size);
688 fatal("virtual memory exhausted", 0);
692 /* This is the guts of the interface to the Post Office Protocol. */
696 #include <sys/socket.h>
697 #include <netinet/in.h>
700 #include "../src/syspwd.h"
702 #define POP_ERROR (-1)
703 #define POP_RETRIEVED (0)
709 char ibuffer[BUFSIZ];
710 char obuffer[BUFSIZ];
713 static int popmail(char *user, char *outfile, char *password)
718 short *retrieved_list;
722 VERBOSE(("opening server\n"));
723 server = pop_open(0, user, password, POP_NO_GETPASS);
725 error("%s", pop_error, NULL);
729 VERBOSE(("stat'ing messages\n"));
730 if (pop_stat(server, &nmsgs, &nbytes)) {
731 error("%s", pop_error, NULL);
736 VERBOSE(("closing server\n"));
741 /* build a retrieved table */
742 retrieved_list = (short *)xmalloc(sizeof(short) * (nmsgs + 1));
743 memset(retrieved_list, 0, sizeof(short) * (nmsgs + 1));
745 mbfi = open(outfile, O_WRONLY | O_CREAT | O_EXCL, 0666);
748 error("Error in open: %s, %s", strerror(errno), outfile);
751 fchown(mbfi, getuid(), (gid_t) - 1);
753 if ((mbf = fdopen(mbfi, "wb")) == NULL) {
755 error("Error in fdopen: %s", strerror(errno), NULL);
761 for (idx = 0; idx < nmsgs; idx++) {
762 i = reverse ? nmsgs - idx : idx + 1;
763 VERBOSE(("checking message %d \n", i));
767 pop_search_top(server, i, match_lines,
768 regexp_pattern) == POP_RETRIEVED) {
769 VERBOSE(("retrieving message %d \n", i));
770 mbx_delimit_begin(mbf);
771 if (pop_retr(server, i, mbx_write, mbf) !=
773 error("%s", Errmsg, NULL);
778 retrieved_list[i] = 1;
780 mbx_delimit_end(mbf);
783 error("Error in fflush: %s", strerror(errno),
792 /* On AFS, a call to write only modifies the file in the local
793 * workstation's AFS cache. The changes are not written to the server
794 * until a call to fsync or close is made. Users with AFS home
795 * directories have lost mail when over quota because these checks were
796 * not made in previous versions of movemail. */
799 if (fsync(mbfi) < 0) {
800 error("Error in fsync: %s", strerror(errno), NULL);
805 if (close(mbfi) == -1) {
806 error("Error in close: %s", strerror(errno), NULL);
810 if (!keep_messages) {
811 for (i = 1; i <= nmsgs; i++) {
812 if (retrieved_list[i] == 1) {
813 VERBOSE(("deleting message %d \n", i));
814 if (pop_delete(server, i)) {
815 error("%s", pop_error, NULL);
823 VERBOSE(("closing server \n"));
824 if (pop_quit(server)) {
825 error("%s", pop_error, NULL);
833 pop_retr(popserver server, int msgno, int (*action) (char *, FILE *),
839 if (pop_retrieve_first(server, msgno, &line)) {
840 strncpy(Errmsg, pop_error, sizeof(Errmsg));
841 Errmsg[sizeof(Errmsg) - 1] = '\0';
845 while (!(ret = pop_retrieve_next(server, &line))) {
849 if ((*action) (line, arg) != POP_RETRIEVED) {
850 strcpy(Errmsg, strerror(errno));
857 strncpy(Errmsg, pop_error, sizeof(Errmsg));
858 Errmsg[sizeof(Errmsg) - 1] = '\0';
862 return (POP_RETRIEVED);
865 /* search the top lines of each message looking for a match */
867 pop_search_top(popserver server, int msgno, int lines,
868 struct re_pattern_buffer *regexp)
872 int match = POP_DONE;
874 if (pop_top_first(server, msgno, lines, &line)) {
875 strncpy(Errmsg, pop_error, sizeof(Errmsg));
876 Errmsg[sizeof(Errmsg) - 1] = '\0';
880 while (!(ret = pop_top_next(server, &line))) {
884 /* VERBOSE (("checking %s\n", line)); */
885 if (match != POP_RETRIEVED) {
887 re_match(regexp, line, strlen(line), 0,
889 strcpy(Errmsg, "error in regular expression");
892 } else if (ret >= 0) {
893 match = POP_RETRIEVED;
899 strncpy(Errmsg, pop_error, sizeof(Errmsg));
900 Errmsg[sizeof(Errmsg) - 1] = '\0';
907 /* Do this as a macro instead of using strcmp to save on execution time. */
908 #define IS_FROM_LINE(a) ((a[0] == 'F') \
914 static int mbx_write(char *line, FILE * mbf)
916 if (IS_FROM_LINE(line)) {
917 if (fputc('>', mbf) == EOF)
920 if (fputs(line, mbf) == EOF)
922 if (fputc(0x0a, mbf) == EOF)
924 return (POP_RETRIEVED);
927 static int mbx_delimit_begin(FILE * mbf)
929 if (fputs("\f\n0, unseen,,\n", mbf) == EOF)
931 return (POP_RETRIEVED);
934 static int mbx_delimit_end(FILE * mbf)
936 if (putc('\037', mbf) == EOF)
938 return (POP_RETRIEVED);
941 /* Turn a name, which is an ed-style (but Emacs syntax) regular
942 expression, into a real regular expression by compiling it. */
943 static struct re_pattern_buffer *compile_regex(char *pattern)
946 struct re_pattern_buffer *patbuf = 0;
949 (struct re_pattern_buffer *)
950 xmalloc(sizeof(struct re_pattern_buffer));
951 patbuf->translate = NULL;
952 patbuf->fastmap = NULL;
953 patbuf->buffer = NULL;
954 patbuf->allocated = 0;
956 err = re_compile_pattern(pattern, strlen(pattern), patbuf);
958 error("%s while compiling pattern", err, NULL);
965 #endif /* MAIL_USE_POP */
967 #ifndef HAVE_STRERROR
968 char *strerror(int errnum)
970 extern char *sys_errlist[];
973 if (errnum >= 0 && errnum < sys_nerr)
974 return sys_errlist[errnum];
975 return (char *)"Unknown error";
978 #endif /* ! HAVE_STRERROR */