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 /* Also check that outname's directory is writable to the real uid. */
320 char *buf = (char *)xmalloc(strlen(outname) + 1);
322 strcpy(buf, outname);
323 cp = buf + strlen(buf);
324 while (cp > buf && !IS_DIRECTORY_SEP(cp[-1]))
328 if (access(buf, W_OK) != 0)
329 pfatal_with_name(buf);
334 if (!strncmp(inname, "po:", 3)) {
335 int retcode = popmail(inname + 3, outname, poppass);
339 #endif /* MAIL_USE_POP */
341 #ifndef DISABLE_DIRECT_ACCESS
347 VERBOSE(("opening input file\n"));
349 switch (lock_method) {
351 indesc = open(inname, O_RDONLY);
355 indesc = open(inname, O_RDWR);
360 indesc = open(inname, O_RDWR);
365 indesc = open(inname, O_RDWR);
370 indesc = lk_open(inname, O_RDONLY, 0, 0, 10);
378 pfatal_with_name(inname);
381 /* In case movemail is setuid to root, make sure the user can
382 read the output file. */
383 umask(umask(0) & 0333);
386 outdesc = open(outname, O_WRONLY | O_CREAT | O_EXCL, 0666);
388 pfatal_with_name(outname);
390 VERBOSE(("locking input file\n"));
392 switch (lock_method) {
395 if (lockf(indesc, F_LOCK, 0) < 0)
396 pfatal_with_name(inname);
401 if (flock(indesc, LOCK_EX) < 0)
402 pfatal_with_name(inname);
407 if (locking(indesc, LK_RLCK, -1L) < 0)
408 pfatal_with_name(inname);
418 VERBOSE(("copying input file to output file\n"));
423 nread = read(indesc, buf, sizeof buf);
425 nread != write(outdesc, buf, nread)) {
426 int saved_errno = errno;
429 pfatal_with_name(outname);
431 if (nread < (int)sizeof(buf))
437 if (fsync(outdesc) < 0)
438 pfatal_and_delete(outname);
441 /* Check to make sure no errors before we zap the inbox. */
442 if (close(outdesc) != 0)
443 pfatal_and_delete(outname);
445 VERBOSE(("deleting or truncating input file\n"));
447 switch (lock_method) {
451 #ifdef HAVE_FTRUNCATE
452 if(ftruncate(indesc, 0L)!=0)
453 pfatal_and_delete(inname);
455 close(open(inname, O_CREAT | O_TRUNC | O_RDWR, 0666));
461 lk_close(indesc, 0, 0, 0);
475 if (!WIFEXITED(status))
477 else if (WEXITSTATUS(status) != 0)
478 exit(WEXITSTATUS(status));
480 if (lock_method == DOTLOCKING)
483 #endif /* not DISABLE_DIRECT_ACCESS */
488 static void usage(int lock_method)
490 printf ("Usage: movemail [-rvxkh] [-l lines ] [-m method ] [-i] "
491 "inbox [-o] destfile [[-p] POP-password]\n");
492 printf("where method is one of: dot");
505 printf("\nDefault is: %s\n", unparse_lock_method(lock_method));
509 static char *unparse_lock_method(int lock_method)
511 switch (lock_method) {
528 static int parse_lock_method(char *method_name)
530 if (!strcmp("dot", method_name) || !strcmp("file", method_name))
533 else if (!strcmp("lockf", method_name))
537 else if (!strcmp("flock", method_name))
541 else if (!strcmp("mmdf", method_name))
545 else if (!strcmp("locking", method_name))
549 fatal("invalid lock method: %s", method_name);
550 return 0; /* unreached */
553 static char *dot_filename(char *filename)
555 return concat(filename, ".lock", "");
558 static char *dotlock_filename = NULL;
560 #ifndef DISABLE_DIRECT_ACCESS
561 static void lock_dot(char *filename)
570 dotlock_filename = (char *)xmalloc(strlen(filename) + 1);
572 /* Use a lock file named after our first argument with .lock appended:
573 If it exists, the mail file is locked. */
575 lockname = dot_filename(filename);
576 tempname = (char *)xmalloc(strlen(filename) + strlen("EXXXXXX") + 1);
577 strcpy(tempname, filename);
578 p = tempname + strlen(tempname);
579 while (p != tempname && !IS_DIRECTORY_SEP(p[-1]))
582 strcpy(p, "EXXXXXX");
590 /* Create the lock file, but not under the lock file name. */
591 /* Give up if cannot do that. */
594 /* Remove all group and other permissions.. */
595 umask(S_IRWXG|S_IRWXO);
596 desc = mkstemp(tempname);
598 desc = open(tempname, O_WRONLY | O_CREAT | O_EXCL, 0666);
602 int msz = strlen(tempname) + 50;
603 char *message = (char *)xmalloc(msz);
604 int sz = snprintf(message, msz,
605 "%s--see source file lib-src/movemail.c",
607 assert(sz>=0 && sz < msz);
608 pfatal_with_name(message);
612 tem = link(tempname, lockname);
618 /* If lock file is five minutes old, unlock it.
619 Five minutes should be good enough to cope with crashes
620 and wedgitude, and long enough to avoid being fooled
621 by time differences between machines. */
622 if (stat(lockname, &st) >= 0) {
624 if (st.st_ctime < now - 300)
628 strcpy(dotlock_filename, filename);
631 #endif /* not DISABLE_DIRECT_ACCESS */
633 static void unlock_dot(char *filename)
635 unlink(dot_filename(filename));
638 static void maybe_unlock_dot(void)
640 if (dotlock_filename)
641 unlock_dot(dotlock_filename);
644 /* Print error message and exit. */
646 static void fatal(char *s1, char *s2)
653 /* Print error message. `s1' is printf control string, `s2' is arg for it. */
655 static void error(const char *s1, const char *s2, const char *s3)
657 fprintf(stderr, "movemail: ");
658 fprintf(stderr, s1, s2, s3);
659 fprintf(stderr, "\n");
662 static void pfatal_with_name(char *name)
664 char *s = concat("", strerror(errno), " for %s");
668 static void pfatal_and_delete(char *name)
670 char *s = concat("", strerror(errno), " for %s");
675 /* Return a newly-allocated string whose contents concatenate those of
678 static char *concat(char *s1, char *s2, char *s3)
680 int len1 = strlen(s1), len2 = strlen(s2), len3 = strlen(s3);
681 char *result = (char *)xmalloc(len1 + len2 + len3 + 1);
683 strncpy(result, s1, len1+1);
684 strncpy(result + len1, s2, len2+1);
685 strncpy(result + len1 + len2, s3, len3+1);
686 *(result + len1 + len2 + len3) = '\0';
691 /* Like malloc but get fatal error if memory is exhausted. */
693 static long *xmalloc(unsigned int size)
695 long *result = (long *)malloc(size);
697 fatal("virtual memory exhausted", 0);
701 /* This is the guts of the interface to the Post Office Protocol. */
705 #include <sys/socket.h>
706 #include <netinet/in.h>
709 #include "../src/syspwd.h"
711 #define POP_ERROR (-1)
712 #define POP_RETRIEVED (0)
718 char ibuffer[BUFSIZ];
719 char obuffer[BUFSIZ];
722 static int popmail(char *user, char *outfile, char *password)
727 short *retrieved_list;
731 VERBOSE(("opening server\n"));
732 server = pop_open(0, user, password, POP_NO_GETPASS);
734 error("%s", pop_error, NULL);
738 VERBOSE(("stat'ing messages\n"));
739 if (pop_stat(server, &nmsgs, &nbytes)) {
740 error("%s", pop_error, NULL);
745 VERBOSE(("closing server\n"));
750 /* build a retrieved table */
751 retrieved_list = (short *)xmalloc(sizeof(short) * (nmsgs + 1));
752 memset(retrieved_list, 0, sizeof(short) * (nmsgs + 1));
754 mbfi = open(outfile, O_WRONLY | O_CREAT | O_EXCL, 0666);
757 error("Error in open: %s, %s", strerror(errno), outfile);
760 fchown(mbfi, getuid(), (gid_t) - 1);
762 if ((mbf = fdopen(mbfi, "wb")) == NULL) {
764 error("Error in fdopen: %s", strerror(errno), NULL);
770 for (idx = 0; idx < nmsgs; idx++) {
771 i = reverse ? nmsgs - idx : idx + 1;
772 VERBOSE(("checking message %d \n", i));
776 pop_search_top(server, i, match_lines,
777 regexp_pattern) == POP_RETRIEVED) {
778 VERBOSE(("retrieving message %d \n", i));
779 mbx_delimit_begin(mbf);
780 if (pop_retr(server, i, mbx_write, mbf) !=
782 error("%s", Errmsg, NULL);
787 retrieved_list[i] = 1;
789 mbx_delimit_end(mbf);
792 error("Error in fflush: %s", strerror(errno),
801 /* On AFS, a call to write only modifies the file in the local
802 * workstation's AFS cache. The changes are not written to the server
803 * until a call to fsync or close is made. Users with AFS home
804 * directories have lost mail when over quota because these checks were
805 * not made in previous versions of movemail. */
808 if (fsync(mbfi) < 0) {
809 error("Error in fsync: %s", strerror(errno), NULL);
814 if (close(mbfi) == -1) {
815 error("Error in close: %s", strerror(errno), NULL);
819 if (!keep_messages) {
820 for (i = 1; i <= nmsgs; i++) {
821 if (retrieved_list[i] == 1) {
822 VERBOSE(("deleting message %d \n", i));
823 if (pop_delete(server, i)) {
824 error("%s", pop_error, NULL);
832 VERBOSE(("closing server \n"));
833 if (pop_quit(server)) {
834 error("%s", pop_error, NULL);
842 pop_retr(popserver server, int msgno, int (*action) (char *, FILE *),
848 if (pop_retrieve_first(server, msgno, &line)) {
849 strncpy(Errmsg, pop_error, sizeof(Errmsg));
850 Errmsg[sizeof(Errmsg) - 1] = '\0';
854 while (!(ret = pop_retrieve_next(server, &line))) {
858 if ((*action) (line, arg) != POP_RETRIEVED) {
859 strcpy(Errmsg, strerror(errno));
866 strncpy(Errmsg, pop_error, sizeof(Errmsg));
867 Errmsg[sizeof(Errmsg) - 1] = '\0';
871 return (POP_RETRIEVED);
874 /* search the top lines of each message looking for a match */
876 pop_search_top(popserver server, int msgno, int lines,
877 struct re_pattern_buffer *regexp)
881 int match = POP_DONE;
883 if (pop_top_first(server, msgno, lines, &line)) {
884 strncpy(Errmsg, pop_error, sizeof(Errmsg));
885 Errmsg[sizeof(Errmsg) - 1] = '\0';
889 while (!(ret = pop_top_next(server, &line))) {
893 /* VERBOSE (("checking %s\n", line)); */
894 if (match != POP_RETRIEVED) {
896 re_match(regexp, line, strlen(line), 0,
898 strcpy(Errmsg, "error in regular expression");
901 } else if (ret >= 0) {
902 match = POP_RETRIEVED;
908 strncpy(Errmsg, pop_error, sizeof(Errmsg));
909 Errmsg[sizeof(Errmsg) - 1] = '\0';
916 /* Do this as a macro instead of using strcmp to save on execution time. */
917 #define IS_FROM_LINE(a) ((a[0] == 'F') \
923 static int mbx_write(char *line, FILE * mbf)
925 if (IS_FROM_LINE(line)) {
926 if (fputc('>', mbf) == EOF)
929 if (fputs(line, mbf) == EOF)
931 if (fputc(0x0a, mbf) == EOF)
933 return (POP_RETRIEVED);
936 static int mbx_delimit_begin(FILE * mbf)
938 if (fputs("\f\n0, unseen,,\n", mbf) == EOF)
940 return (POP_RETRIEVED);
943 static int mbx_delimit_end(FILE * mbf)
945 if (putc('\037', mbf) == EOF)
947 return (POP_RETRIEVED);
950 /* Turn a name, which is an ed-style (but Emacs syntax) regular
951 expression, into a real regular expression by compiling it. */
952 static struct re_pattern_buffer *compile_regex(char *pattern)
955 struct re_pattern_buffer *patbuf = 0;
958 (struct re_pattern_buffer *)
959 xmalloc(sizeof(struct re_pattern_buffer));
960 patbuf->translate = NULL;
961 patbuf->fastmap = NULL;
962 patbuf->buffer = NULL;
963 patbuf->allocated = 0;
965 err = re_compile_pattern(pattern, strlen(pattern), patbuf);
967 error("%s while compiling pattern", err, NULL);
974 #endif /* MAIL_USE_POP */
976 #ifndef HAVE_STRERROR
977 char *strerror(int errnum)
979 extern char *sys_errlist[];
982 if (errnum >= 0 && errnum < sys_nerr)
983 return sys_errlist[errnum];
984 return (char *)"Unknown error";
987 #endif /* ! HAVE_STRERROR */