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);
638 #endif /* not DISABLE_DIRECT_ACCESS */
640 static void unlock_dot(char *filename)
642 unlink(dot_filename(filename));
645 static void maybe_unlock_dot(void)
647 if (dotlock_filename)
648 unlock_dot(dotlock_filename);
651 /* Print error message and exit. */
653 static void fatal(char *s1, char *s2)
660 /* Print error message. `s1' is printf control string, `s2' is arg for it. */
662 static void error(const char *s1, const char *s2, const char *s3)
664 fprintf(stderr, "movemail: ");
665 fprintf(stderr, s1, s2, s3);
666 fprintf(stderr, "\n");
669 static void pfatal_with_name(char *name)
671 char *s = concat("", strerror(errno), " for %s");
675 static void pfatal_and_delete(char *name)
677 char *s = concat("", strerror(errno), " for %s");
682 /* Return a newly-allocated string whose contents concatenate those of
685 static char *concat(char *s1, char *s2, char *s3)
687 int len1 = strlen(s1), len2 = strlen(s2), len3 = strlen(s3);
688 char *result = (char *)xmalloc(len1 + len2 + len3 + 1);
690 strncpy(result, s1, len1+1);
691 strncpy(result + len1, s2, len2+1);
692 strncpy(result + len1 + len2, s3, len3+1);
693 *(result + len1 + len2 + len3) = '\0';
698 /* Like malloc but get fatal error if memory is exhausted. */
700 static long *xmalloc(unsigned int size)
702 long *result = (long *)malloc(size);
704 fatal("virtual memory exhausted", 0);
708 /* This is the guts of the interface to the Post Office Protocol. */
712 #include <sys/socket.h>
713 #include <netinet/in.h>
716 #include "../src/syspwd.h"
718 #define POP_ERROR (-1)
719 #define POP_RETRIEVED (0)
725 char ibuffer[BUFSIZ];
726 char obuffer[BUFSIZ];
729 static int popmail(char *user, char *outfile, char *password)
734 short *retrieved_list;
738 VERBOSE(("opening server\n"));
739 server = pop_open(0, user, password, POP_NO_GETPASS);
741 error("%s", pop_error, NULL);
745 VERBOSE(("stat'ing messages\n"));
746 if (pop_stat(server, &nmsgs, &nbytes)) {
747 error("%s", pop_error, NULL);
752 VERBOSE(("closing server\n"));
757 /* build a retrieved table */
758 retrieved_list = (short *)xmalloc(sizeof(short) * (nmsgs + 1));
759 memset(retrieved_list, 0, sizeof(short) * (nmsgs + 1));
761 mbfi = open(outfile, O_WRONLY | O_CREAT | O_EXCL, 0666);
764 error("Error in open: %s, %s", strerror(errno), outfile);
767 fchown(mbfi, getuid(), (gid_t) - 1);
769 if ((mbf = fdopen(mbfi, "wb")) == NULL) {
771 error("Error in fdopen: %s", strerror(errno), NULL);
777 for (idx = 0; idx < nmsgs; idx++) {
778 i = reverse ? nmsgs - idx : idx + 1;
779 VERBOSE(("checking message %d \n", i));
783 pop_search_top(server, i, match_lines,
784 regexp_pattern) == POP_RETRIEVED) {
785 VERBOSE(("retrieving message %d \n", i));
786 mbx_delimit_begin(mbf);
787 if (pop_retr(server, i, mbx_write, mbf) !=
789 error("%s", Errmsg, NULL);
794 retrieved_list[i] = 1;
796 mbx_delimit_end(mbf);
799 error("Error in fflush: %s", strerror(errno),
808 /* On AFS, a call to write only modifies the file in the local
809 * workstation's AFS cache. The changes are not written to the server
810 * until a call to fsync or close is made. Users with AFS home
811 * directories have lost mail when over quota because these checks were
812 * not made in previous versions of movemail. */
815 if (fsync(mbfi) < 0) {
816 error("Error in fsync: %s", strerror(errno), NULL);
821 if (close(mbfi) == -1) {
822 error("Error in close: %s", strerror(errno), NULL);
826 if (!keep_messages) {
827 for (i = 1; i <= nmsgs; i++) {
828 if (retrieved_list[i] == 1) {
829 VERBOSE(("deleting message %d \n", i));
830 if (pop_delete(server, i)) {
831 error("%s", pop_error, NULL);
839 VERBOSE(("closing server \n"));
840 if (pop_quit(server)) {
841 error("%s", pop_error, NULL);
849 pop_retr(popserver server, int msgno, int (*action) (char *, FILE *),
855 if (pop_retrieve_first(server, msgno, &line)) {
856 strncpy(Errmsg, pop_error, sizeof(Errmsg));
857 Errmsg[sizeof(Errmsg) - 1] = '\0';
861 while (!(ret = pop_retrieve_next(server, &line))) {
865 if ((*action) (line, arg) != POP_RETRIEVED) {
866 strcpy(Errmsg, strerror(errno));
873 strncpy(Errmsg, pop_error, sizeof(Errmsg));
874 Errmsg[sizeof(Errmsg) - 1] = '\0';
878 return (POP_RETRIEVED);
881 /* search the top lines of each message looking for a match */
883 pop_search_top(popserver server, int msgno, int lines,
884 struct re_pattern_buffer *regexp)
888 int match = POP_DONE;
890 if (pop_top_first(server, msgno, lines, &line)) {
891 strncpy(Errmsg, pop_error, sizeof(Errmsg));
892 Errmsg[sizeof(Errmsg) - 1] = '\0';
896 while (!(ret = pop_top_next(server, &line))) {
900 /* VERBOSE (("checking %s\n", line)); */
901 if (match != POP_RETRIEVED) {
903 re_match(regexp, line, strlen(line), 0,
905 strcpy(Errmsg, "error in regular expression");
908 } else if (ret >= 0) {
909 match = POP_RETRIEVED;
915 strncpy(Errmsg, pop_error, sizeof(Errmsg));
916 Errmsg[sizeof(Errmsg) - 1] = '\0';
923 /* Do this as a macro instead of using strcmp to save on execution time. */
924 #define IS_FROM_LINE(a) ((a[0] == 'F') \
930 static int mbx_write(char *line, FILE * mbf)
932 if (IS_FROM_LINE(line)) {
933 if (fputc('>', mbf) == EOF)
936 if (fputs(line, mbf) == EOF)
938 if (fputc(0x0a, mbf) == EOF)
940 return (POP_RETRIEVED);
943 static int mbx_delimit_begin(FILE * mbf)
945 if (fputs("\f\n0, unseen,,\n", mbf) == EOF)
947 return (POP_RETRIEVED);
950 static int mbx_delimit_end(FILE * mbf)
952 if (putc('\037', mbf) == EOF)
954 return (POP_RETRIEVED);
957 /* Turn a name, which is an ed-style (but Emacs syntax) regular
958 expression, into a real regular expression by compiling it. */
959 static struct re_pattern_buffer *compile_regex(char *pattern)
962 struct re_pattern_buffer *patbuf = 0;
965 (struct re_pattern_buffer *)
966 xmalloc(sizeof(struct re_pattern_buffer));
967 patbuf->translate = NULL;
968 patbuf->fastmap = NULL;
969 patbuf->buffer = NULL;
970 patbuf->allocated = 0;
972 err = re_compile_pattern(pattern, strlen(pattern), patbuf);
974 error("%s while compiling pattern", err, NULL);
981 #endif /* MAIL_USE_POP */
983 #ifndef HAVE_STRERROR
984 char *strerror(int errnum)
986 extern char *sys_errlist[];
989 if (errnum >= 0 && errnum < sys_nerr)
990 return sys_errlist[errnum];
991 return (char *)"Unknown error";
994 #endif /* ! HAVE_STRERROR */