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 /* Remove all group and other permissions.. */
603 umask(S_IRWXG|S_IRWXO);
604 desc = mkstemp(tempname);
606 desc = open(tempname, O_WRONLY | O_CREAT | O_EXCL, 0666);
610 int msz = strlen(tempname) + 50;
611 char *message = (char *)xmalloc(msz);
612 int sz = snprintf(message, msz,
613 "%s--see source file lib-src/movemail.c",
615 assert(sz>=0 && sz < msz);
616 pfatal_with_name(message);
620 tem = link(tempname, lockname);
626 /* If lock file is five minutes old, unlock it.
627 Five minutes should be good enough to cope with crashes
628 and wedgitude, and long enough to avoid being fooled
629 by time differences between machines. */
630 if (stat(lockname, &st) >= 0) {
632 if (st.st_ctime < now - 300)
636 strcpy(dotlock_filename, filename);
639 #endif /* not DISABLE_DIRECT_ACCESS */
641 static void unlock_dot(char *filename)
643 unlink(dot_filename(filename));
646 static void maybe_unlock_dot(void)
648 if (dotlock_filename)
649 unlock_dot(dotlock_filename);
652 /* Print error message and exit. */
654 static void fatal(char *s1, char *s2)
661 /* Print error message. `s1' is printf control string, `s2' is arg for it. */
663 static void error(const char *s1, const char *s2, const char *s3)
665 fprintf(stderr, "movemail: ");
666 fprintf(stderr, s1, s2, s3);
667 fprintf(stderr, "\n");
670 static void pfatal_with_name(char *name)
672 char *s = concat("", strerror(errno), " for %s");
676 static void pfatal_and_delete(char *name)
678 char *s = concat("", strerror(errno), " for %s");
683 /* Return a newly-allocated string whose contents concatenate those of
686 static char *concat(char *s1, char *s2, char *s3)
688 int len1 = strlen(s1), len2 = strlen(s2), len3 = strlen(s3);
689 char *result = (char *)xmalloc(len1 + len2 + len3 + 1);
691 strncpy(result, s1, len1+1);
692 strncpy(result + len1, s2, len2+1);
693 strncpy(result + len1 + len2, s3, len3+1);
694 *(result + len1 + len2 + len3) = '\0';
699 /* Like malloc but get fatal error if memory is exhausted. */
701 static long *xmalloc(unsigned int size)
703 long *result = (long *)malloc(size);
705 fatal("virtual memory exhausted", 0);
709 /* This is the guts of the interface to the Post Office Protocol. */
713 #include <sys/socket.h>
714 #include <netinet/in.h>
717 #include "../src/syspwd.h"
719 #define POP_ERROR (-1)
720 #define POP_RETRIEVED (0)
726 char ibuffer[BUFSIZ];
727 char obuffer[BUFSIZ];
730 static int popmail(char *user, char *outfile, char *password)
735 short *retrieved_list;
739 VERBOSE(("opening server\n"));
740 server = pop_open(0, user, password, POP_NO_GETPASS);
742 error("%s", pop_error, NULL);
746 VERBOSE(("stat'ing messages\n"));
747 if (pop_stat(server, &nmsgs, &nbytes)) {
748 error("%s", pop_error, NULL);
753 VERBOSE(("closing server\n"));
758 /* build a retrieved table */
759 retrieved_list = (short *)xmalloc(sizeof(short) * (nmsgs + 1));
760 memset(retrieved_list, 0, sizeof(short) * (nmsgs + 1));
762 mbfi = open(outfile, O_WRONLY | O_CREAT | O_EXCL, 0666);
765 error("Error in open: %s, %s", strerror(errno), outfile);
768 fchown(mbfi, getuid(), (gid_t) - 1);
770 if ((mbf = fdopen(mbfi, "wb")) == NULL) {
772 error("Error in fdopen: %s", strerror(errno), NULL);
778 for (idx = 0; idx < nmsgs; idx++) {
779 i = reverse ? nmsgs - idx : idx + 1;
780 VERBOSE(("checking message %d \n", i));
784 pop_search_top(server, i, match_lines,
785 regexp_pattern) == POP_RETRIEVED) {
786 VERBOSE(("retrieving message %d \n", i));
787 mbx_delimit_begin(mbf);
788 if (pop_retr(server, i, mbx_write, mbf) !=
790 error("%s", Errmsg, NULL);
795 retrieved_list[i] = 1;
797 mbx_delimit_end(mbf);
800 error("Error in fflush: %s", strerror(errno),
809 /* On AFS, a call to write only modifies the file in the local
810 * workstation's AFS cache. The changes are not written to the server
811 * until a call to fsync or close is made. Users with AFS home
812 * directories have lost mail when over quota because these checks were
813 * not made in previous versions of movemail. */
816 if (fsync(mbfi) < 0) {
817 error("Error in fsync: %s", strerror(errno), NULL);
822 if (close(mbfi) == -1) {
823 error("Error in close: %s", strerror(errno), NULL);
827 if (!keep_messages) {
828 for (i = 1; i <= nmsgs; i++) {
829 if (retrieved_list[i] == 1) {
830 VERBOSE(("deleting message %d \n", i));
831 if (pop_delete(server, i)) {
832 error("%s", pop_error, NULL);
840 VERBOSE(("closing server \n"));
841 if (pop_quit(server)) {
842 error("%s", pop_error, NULL);
850 pop_retr(popserver server, int msgno, int (*action) (char *, FILE *),
856 if (pop_retrieve_first(server, msgno, &line)) {
857 strncpy(Errmsg, pop_error, sizeof(Errmsg));
858 Errmsg[sizeof(Errmsg) - 1] = '\0';
862 while (!(ret = pop_retrieve_next(server, &line))) {
866 if ((*action) (line, arg) != POP_RETRIEVED) {
867 strcpy(Errmsg, strerror(errno));
874 strncpy(Errmsg, pop_error, sizeof(Errmsg));
875 Errmsg[sizeof(Errmsg) - 1] = '\0';
879 return (POP_RETRIEVED);
882 /* search the top lines of each message looking for a match */
884 pop_search_top(popserver server, int msgno, int lines,
885 struct re_pattern_buffer *regexp)
889 int match = POP_DONE;
891 if (pop_top_first(server, msgno, lines, &line)) {
892 strncpy(Errmsg, pop_error, sizeof(Errmsg));
893 Errmsg[sizeof(Errmsg) - 1] = '\0';
897 while (!(ret = pop_top_next(server, &line))) {
901 /* VERBOSE (("checking %s\n", line)); */
902 if (match != POP_RETRIEVED) {
904 re_match(regexp, line, strlen(line), 0,
906 strcpy(Errmsg, "error in regular expression");
909 } else if (ret >= 0) {
910 match = POP_RETRIEVED;
916 strncpy(Errmsg, pop_error, sizeof(Errmsg));
917 Errmsg[sizeof(Errmsg) - 1] = '\0';
924 /* Do this as a macro instead of using strcmp to save on execution time. */
925 #define IS_FROM_LINE(a) ((a[0] == 'F') \
931 static int mbx_write(char *line, FILE * mbf)
933 if (IS_FROM_LINE(line)) {
934 if (fputc('>', mbf) == EOF)
937 if (fputs(line, mbf) == EOF)
939 if (fputc(0x0a, mbf) == EOF)
941 return (POP_RETRIEVED);
944 static int mbx_delimit_begin(FILE * mbf)
946 if (fputs("\f\n0, unseen,,\n", mbf) == EOF)
948 return (POP_RETRIEVED);
951 static int mbx_delimit_end(FILE * mbf)
953 if (putc('\037', mbf) == EOF)
955 return (POP_RETRIEVED);
958 /* Turn a name, which is an ed-style (but Emacs syntax) regular
959 expression, into a real regular expression by compiling it. */
960 static struct re_pattern_buffer *compile_regex(char *pattern)
963 struct re_pattern_buffer *patbuf = 0;
966 (struct re_pattern_buffer *)
967 xmalloc(sizeof(struct re_pattern_buffer));
968 patbuf->translate = NULL;
969 patbuf->fastmap = NULL;
970 patbuf->buffer = NULL;
971 patbuf->allocated = 0;
973 err = re_compile_pattern(pattern, strlen(pattern), patbuf);
975 error("%s while compiling pattern", err, NULL);
982 #endif /* MAIL_USE_POP */
984 #ifndef HAVE_STRERROR
985 char *strerror(int errnum)
987 extern char *sys_errlist[];
990 if (errnum >= 0 && errnum < sys_nerr)
991 return sys_errlist[errnum];
992 return (char *)"Unknown error";
995 #endif /* ! HAVE_STRERROR */