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);
147 #define xstrncpy(d_,s_,l_) \
151 strncat((dst_),(s_),(l_)-1); \
157 int keep_messages = 0;
158 struct re_pattern_buffer *regexp_pattern = 0;
159 int match_lines = 10;
162 #define VERBOSE(x) if (verbose) { printf x; fflush(stdout); }
164 #ifdef HAVE_GETOPT_LONG
165 struct option longopts[] = {
166 {"inbox", required_argument, NULL, 'i'},
167 {"outfile", required_argument, NULL, 'o'},
169 {"password", required_argument, NULL, 'p'},
170 {"reverse-pop-order", no_argument, NULL, 'x'},
171 {"keep-messages", no_argument, NULL, 'k'},
172 {"regex", required_argument, NULL, 'r'},
173 {"match-lines", required_argument, NULL, 'l'},
175 {"lock-method", required_argument, NULL, 'm'},
176 {"help", no_argument, NULL, 'h'},
177 {"verbose", no_argument, NULL, 'v'},
188 #if defined(MAIL_LOCK_FLOCK) && defined(HAVE_FLOCK)
189 #define DEFAULT_LOCKING FLOCKING
190 #elif defined(MAIL_LOCK_LOCKF) && defined(HAVE_LOCKF)
191 #define DEFAULT_LOCKING LOCKFING
192 #elif defined(MAIL_LOCK_MMDF) && defined(HAVE_MMDF)
193 #define DEFAULT_LOCKING MMDF
194 #elif defined(MAIL_LOCK_LOCKING) && defined(HAVE_LOCKING)
195 #define DEFAULT_LOCKING LOCKING
197 #define DEFAULT_LOCKING DOTLOCKING
200 #ifndef DISABLE_DIRECT_ACCESS
201 static void lock_dot(char *);
203 static void unlock_dot(char *);
204 static int parse_lock_method(char *);
205 static char *unparse_lock_method(int);
207 int main(int argc, char *argv[])
209 char *inname = 0, *outname = 0;
210 #if defined MAIL_USE_POP
212 #endif /* MAIL_USE_POP */
213 #ifndef DISABLE_DIRECT_ACCESS
219 int lock_method = DEFAULT_LOCKING;
221 char *maybe_lock_env;
223 maybe_lock_env = getenv("EMACSLOCKMETHOD");
224 if (maybe_lock_env) {
225 printf("maybe-lock_env: %s\n", maybe_lock_env);
226 lock_method = parse_lock_method(maybe_lock_env);
231 char *optstring = "i:o:m:p:l:r:xvhk";
233 char *optstring = "i:o:m:vh";
237 int opt = getopt_long(argc, argv, optstring, longopts, 0);
239 int opt = getopt(argc, argv, optstring);
241 # error "movemail cannot be built without getopt, preferably getopt_long"
249 case 1: /* one of the standard arguments seen */
252 } else if (!outname) {
254 #if defined MAIL_USE_POP
257 #endif /* MAIL_USE_POP */
261 case 'i': /* infile */
265 case 'o': /* outfile */
269 case 'p': /* pop password */
278 case 'l': /* lines to match */
279 match_lines = atoi(optarg);
282 case 'r': /* regular expression */
283 regexp_pattern = compile_regex(optarg);
288 lock_method = parse_lock_method(optarg);
301 while (optind < argc) {
302 assert(argv[optind] != NULL);
304 inname = argv[optind];
305 } else if (!outname) {
306 outname = argv[optind];
307 #if defined MAIL_USE_POP
309 poppass = argv[optind];
310 #endif /* MAIL_USE_POP */
315 if (!inname || !outname) {
320 if (lock_method == MMDF)
325 fatal("Destination file name is empty", 0);
327 /* Also check that outname's directory is writable to the real uid. */
329 char *buf = (char *)xmalloc(strlen(outname) + 1);
331 strcpy(buf, outname);
332 cp = buf + strlen(buf);
333 while (cp > buf && !IS_DIRECTORY_SEP(cp[-1]))
337 if (access(buf, W_OK) != 0)
338 pfatal_with_name(buf);
343 if (!strncmp(inname, "po:", 3)) {
344 int retcode = popmail(inname + 3, outname, poppass);
347 if (0 != setuid(getuid())) {
350 #endif /* MAIL_USE_POP */
352 #ifndef DISABLE_DIRECT_ACCESS
356 if (0 != setuid(getuid())) {
360 VERBOSE(("opening input file\n"));
362 switch (lock_method) {
364 indesc = open(inname, O_RDONLY);
368 indesc = open(inname, O_RDWR);
373 indesc = open(inname, O_RDWR);
378 indesc = open(inname, O_RDWR);
383 indesc = lk_open(inname, O_RDONLY, 0, 0, 10);
391 pfatal_with_name(inname);
394 /* In case movemail is setuid to root, make sure the user can
395 read the output file. */
396 umask(umask(0) & 0333);
399 outdesc = open(outname, O_WRONLY | O_CREAT | O_EXCL, 0666);
401 pfatal_with_name(outname);
403 VERBOSE(("locking input file\n"));
405 switch (lock_method) {
408 if (lockf(indesc, F_LOCK, 0) < 0)
409 pfatal_with_name(inname);
414 if (flock(indesc, LOCK_EX) < 0)
415 pfatal_with_name(inname);
420 if (locking(indesc, LK_RLCK, -1L) < 0)
421 pfatal_with_name(inname);
431 VERBOSE(("copying input file to output file\n"));
436 nread = read(indesc, buf, sizeof buf);
438 nread != write(outdesc, buf, nread)) {
439 int saved_errno = errno;
442 pfatal_with_name(outname);
444 if (nread < (int)sizeof(buf))
450 if (fsync(outdesc) < 0)
451 pfatal_and_delete(outname);
454 /* Check to make sure no errors before we zap the inbox. */
455 if (close(outdesc) != 0)
456 pfatal_and_delete(outname);
458 VERBOSE(("deleting or truncating input file\n"));
460 switch (lock_method) {
464 #ifdef HAVE_FTRUNCATE
465 if(ftruncate(indesc, 0L)!=0)
466 pfatal_and_delete(inname);
468 close(open(inname, O_CREAT | O_TRUNC | O_RDWR, 0666));
474 lk_close(indesc, 0, 0, 0);
488 if (!WIFEXITED(status))
490 else if (WEXITSTATUS(status) != 0)
491 exit(WEXITSTATUS(status));
493 if (lock_method == DOTLOCKING)
496 #endif /* not DISABLE_DIRECT_ACCESS */
501 static void usage(int lock_method)
503 printf ("Usage: movemail [-rvxkh] [-l lines ] [-m method ] [-i] "
504 "inbox [-o] destfile [[-p] POP-password]\n");
505 printf("where method is one of: dot");
518 printf("\nDefault is: %s\n", unparse_lock_method(lock_method));
522 static char *unparse_lock_method(int lock_method)
524 switch (lock_method) {
541 static int parse_lock_method(char *method_name)
543 if (!strcmp("dot", method_name) || !strcmp("file", method_name))
546 else if (!strcmp("lockf", method_name))
550 else if (!strcmp("flock", method_name))
554 else if (!strcmp("mmdf", method_name))
558 else if (!strcmp("locking", method_name))
562 fatal("invalid lock method: %s", method_name);
563 return 0; /* unreached */
566 static char *dot_filename(char *filename)
568 return concat(filename, ".lock", "");
571 static char *dotlock_filename = NULL;
573 #ifndef DISABLE_DIRECT_ACCESS
574 static void lock_dot(char *filename)
583 dotlock_filename = (char *)xmalloc(strlen(filename) + 1);
585 /* Use a lock file named after our first argument with .lock appended:
586 If it exists, the mail file is locked. */
588 lockname = dot_filename(filename);
589 tempname = (char *)xmalloc(strlen(filename) + strlen("EXXXXXX") + 1);
590 strcpy(tempname, filename);
591 p = tempname + strlen(tempname);
592 while (p != tempname && !IS_DIRECTORY_SEP(p[-1]))
595 strcpy(p, "EXXXXXX");
603 /* Create the lock file, but not under the lock file name. */
604 /* Give up if cannot do that. */
607 /* Remove all group and other permissions.. */
608 umask(S_IRWXG|S_IRWXO);
609 desc = mkstemp(tempname);
611 desc = open(tempname, O_WRONLY | O_CREAT | O_EXCL, 0666);
615 int msz = strlen(tempname) + 50;
616 char *message = (char *)xmalloc(msz);
617 int sz = snprintf(message, msz,
618 "%s--see source file lib-src/movemail.c",
620 assert(sz>=0 && sz < msz);
621 pfatal_with_name(message);
625 tem = link(tempname, lockname);
631 /* If lock file is five minutes old, unlock it.
632 Five minutes should be good enough to cope with crashes
633 and wedgitude, and long enough to avoid being fooled
634 by time differences between machines. */
635 if (stat(lockname, &st) >= 0) {
637 if (st.st_ctime < now - 300)
641 strcpy(dotlock_filename, filename);
644 #endif /* not DISABLE_DIRECT_ACCESS */
646 static void unlock_dot(char *filename)
648 unlink(dot_filename(filename));
651 static void maybe_unlock_dot(void)
653 if (dotlock_filename)
654 unlock_dot(dotlock_filename);
657 /* Print error message and exit. */
659 static void fatal(char *s1, char *s2)
666 /* Print error message. `s1' is printf control string, `s2' is arg for it. */
668 static void error(const char *s1, const char *s2, const char *s3)
670 fprintf(stderr, "movemail: ");
671 fprintf(stderr, s1, s2, s3);
672 fprintf(stderr, "\n");
675 static void pfatal_with_name(char *name)
677 char *s = concat("", strerror(errno), " for %s");
681 static void pfatal_and_delete(char *name)
683 char *s = concat("", strerror(errno), " for %s");
688 /* Return a newly-allocated string whose contents concatenate those of
691 static char *concat(char *s1, char *s2, char *s3)
693 int len1 = strlen(s1), len2 = strlen(s2), len3 = strlen(s3);
694 char *result = (char *)xmalloc(len1 + len2 + len3 + 1);
696 xstrncpy(result, s1, len1+1);
697 xstrncpy(result + len1, s2, len2+1);
698 xstrncpy(result + len1 + len2, s3, len3+1);
699 *(result + len1 + len2 + len3) = '\0';
704 /* Like malloc but get fatal error if memory is exhausted. */
706 static long *xmalloc(unsigned int size)
708 long *result = (long *)malloc(size);
710 fatal("virtual memory exhausted", 0);
714 /* This is the guts of the interface to the Post Office Protocol. */
718 #include <sys/socket.h>
719 #include <netinet/in.h>
722 #include "../src/syspwd.h"
724 #define POP_ERROR (-1)
725 #define POP_RETRIEVED (0)
731 char ibuffer[BUFSIZ];
732 char obuffer[BUFSIZ];
735 static int popmail(char *user, char *outfile, char *password)
740 short *retrieved_list;
744 VERBOSE(("opening server\n"));
745 server = pop_open(0, user, password, POP_NO_GETPASS);
747 error("%s", pop_error, NULL);
751 VERBOSE(("stat'ing messages\n"));
752 if (pop_stat(server, &nmsgs, &nbytes)) {
753 error("%s", pop_error, NULL);
758 VERBOSE(("closing server\n"));
763 /* build a retrieved table */
764 retrieved_list = (short *)xmalloc(sizeof(short) * (nmsgs + 1));
765 memset(retrieved_list, 0, sizeof(short) * (nmsgs + 1));
767 mbfi = open(outfile, O_WRONLY | O_CREAT | O_EXCL, 0666);
770 error("Error in open: %s, %s", strerror(errno), outfile);
773 fchown(mbfi, getuid(), (gid_t) - 1);
775 if ((mbf = fdopen(mbfi, "wb")) == NULL) {
777 error("Error in fdopen: %s", strerror(errno), NULL);
783 for (idx = 0; idx < nmsgs; idx++) {
784 i = reverse ? nmsgs - idx : idx + 1;
785 VERBOSE(("checking message %d \n", i));
789 pop_search_top(server, i, match_lines,
790 regexp_pattern) == POP_RETRIEVED) {
791 VERBOSE(("retrieving message %d \n", i));
792 mbx_delimit_begin(mbf);
793 if (pop_retr(server, i, mbx_write, mbf) !=
795 error("%s", Errmsg, NULL);
800 retrieved_list[i] = 1;
802 mbx_delimit_end(mbf);
805 error("Error in fflush: %s", strerror(errno),
814 /* On AFS, a call to write only modifies the file in the local
815 * workstation's AFS cache. The changes are not written to the server
816 * until a call to fsync or close is made. Users with AFS home
817 * directories have lost mail when over quota because these checks were
818 * not made in previous versions of movemail. */
821 if (fsync(mbfi) < 0) {
822 error("Error in fsync: %s", strerror(errno), NULL);
827 if (close(mbfi) == -1) {
828 error("Error in close: %s", strerror(errno), NULL);
832 if (!keep_messages) {
833 for (i = 1; i <= nmsgs; i++) {
834 if (retrieved_list[i] == 1) {
835 VERBOSE(("deleting message %d \n", i));
836 if (pop_delete(server, i)) {
837 error("%s", pop_error, NULL);
845 VERBOSE(("closing server \n"));
846 if (pop_quit(server)) {
847 error("%s", pop_error, NULL);
855 pop_retr(popserver server, int msgno, int (*action) (char *, FILE *),
861 if (pop_retrieve_first(server, msgno, &line)) {
862 xstrncpy(Errmsg, pop_error, sizeof(Errmsg));
863 Errmsg[sizeof(Errmsg) - 1] = '\0';
867 while (!(ret = pop_retrieve_next(server, &line))) {
871 if ((*action) (line, arg) != POP_RETRIEVED) {
872 strcpy(Errmsg, strerror(errno));
879 xstrncpy(Errmsg, pop_error, sizeof(Errmsg));
880 Errmsg[sizeof(Errmsg) - 1] = '\0';
884 return (POP_RETRIEVED);
887 /* search the top lines of each message looking for a match */
889 pop_search_top(popserver server, int msgno, int lines,
890 struct re_pattern_buffer *regexp)
894 int match = POP_DONE;
896 if (pop_top_first(server, msgno, lines, &line)) {
897 xstrncpy(Errmsg, pop_error, sizeof(Errmsg));
898 Errmsg[sizeof(Errmsg) - 1] = '\0';
902 while (!(ret = pop_top_next(server, &line))) {
906 /* VERBOSE (("checking %s\n", line)); */
907 if (match != POP_RETRIEVED) {
909 re_match(regexp, line, strlen(line), 0,
911 strcpy(Errmsg, "error in regular expression");
914 } else if (ret >= 0) {
915 match = POP_RETRIEVED;
921 xstrncpy(Errmsg, pop_error, sizeof(Errmsg));
922 Errmsg[sizeof(Errmsg) - 1] = '\0';
929 /* Do this as a macro instead of using strcmp to save on execution time. */
930 #define IS_FROM_LINE(a) ((a[0] == 'F') \
936 static int mbx_write(char *line, FILE * mbf)
938 if (IS_FROM_LINE(line)) {
939 if (fputc('>', mbf) == EOF)
942 if (fputs(line, mbf) == EOF)
944 if (fputc(0x0a, mbf) == EOF)
946 return (POP_RETRIEVED);
949 static int mbx_delimit_begin(FILE * mbf)
951 if (fputs("\f\n0, unseen,,\n", mbf) == EOF)
953 return (POP_RETRIEVED);
956 static int mbx_delimit_end(FILE * mbf)
958 if (putc('\037', mbf) == EOF)
960 return (POP_RETRIEVED);
963 /* Turn a name, which is an ed-style (but Emacs syntax) regular
964 expression, into a real regular expression by compiling it. */
965 static struct re_pattern_buffer *compile_regex(char *pattern)
968 struct re_pattern_buffer *patbuf = 0;
971 (struct re_pattern_buffer *)
972 xmalloc(sizeof(struct re_pattern_buffer));
973 patbuf->translate = NULL;
974 patbuf->fastmap = NULL;
975 patbuf->buffer = NULL;
976 patbuf->allocated = 0;
978 err = re_compile_pattern(pattern, strlen(pattern), patbuf);
980 error("%s while compiling pattern", err, NULL);
987 #endif /* MAIL_USE_POP */
989 #ifndef HAVE_STRERROR
990 char *strerror(int errnum)
992 extern char *sys_errlist[];
995 if (errnum >= 0 && errnum < sys_nerr)
996 return sys_errlist[errnum];
997 return (char *)"Unknown error";
1000 #endif /* ! HAVE_STRERROR */