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);
348 #endif /* MAIL_USE_POP */
350 #ifndef DISABLE_DIRECT_ACCESS
356 VERBOSE(("opening input file\n"));
358 switch (lock_method) {
360 indesc = open(inname, O_RDONLY);
364 indesc = open(inname, O_RDWR);
369 indesc = open(inname, O_RDWR);
374 indesc = open(inname, O_RDWR);
379 indesc = lk_open(inname, O_RDONLY, 0, 0, 10);
387 pfatal_with_name(inname);
390 /* In case movemail is setuid to root, make sure the user can
391 read the output file. */
392 umask(umask(0) & 0333);
395 outdesc = open(outname, O_WRONLY | O_CREAT | O_EXCL, 0666);
397 pfatal_with_name(outname);
399 VERBOSE(("locking input file\n"));
401 switch (lock_method) {
404 if (lockf(indesc, F_LOCK, 0) < 0)
405 pfatal_with_name(inname);
410 if (flock(indesc, LOCK_EX) < 0)
411 pfatal_with_name(inname);
416 if (locking(indesc, LK_RLCK, -1L) < 0)
417 pfatal_with_name(inname);
427 VERBOSE(("copying input file to output file\n"));
432 nread = read(indesc, buf, sizeof buf);
434 nread != write(outdesc, buf, nread)) {
435 int saved_errno = errno;
438 pfatal_with_name(outname);
440 if (nread < (int)sizeof(buf))
446 if (fsync(outdesc) < 0)
447 pfatal_and_delete(outname);
450 /* Check to make sure no errors before we zap the inbox. */
451 if (close(outdesc) != 0)
452 pfatal_and_delete(outname);
454 VERBOSE(("deleting or truncating input file\n"));
456 switch (lock_method) {
460 #ifdef HAVE_FTRUNCATE
461 if(ftruncate(indesc, 0L)!=0)
462 pfatal_and_delete(inname);
464 close(open(inname, O_CREAT | O_TRUNC | O_RDWR, 0666));
470 lk_close(indesc, 0, 0, 0);
484 if (!WIFEXITED(status))
486 else if (WEXITSTATUS(status) != 0)
487 exit(WEXITSTATUS(status));
489 if (lock_method == DOTLOCKING)
492 #endif /* not DISABLE_DIRECT_ACCESS */
497 static void usage(int lock_method)
499 printf ("Usage: movemail [-rvxkh] [-l lines ] [-m method ] [-i] "
500 "inbox [-o] destfile [[-p] POP-password]\n");
501 printf("where method is one of: dot");
514 printf("\nDefault is: %s\n", unparse_lock_method(lock_method));
518 static char *unparse_lock_method(int lock_method)
520 switch (lock_method) {
537 static int parse_lock_method(char *method_name)
539 if (!strcmp("dot", method_name) || !strcmp("file", method_name))
542 else if (!strcmp("lockf", method_name))
546 else if (!strcmp("flock", method_name))
550 else if (!strcmp("mmdf", method_name))
554 else if (!strcmp("locking", method_name))
558 fatal("invalid lock method: %s", method_name);
559 return 0; /* unreached */
562 static char *dot_filename(char *filename)
564 return concat(filename, ".lock", "");
567 static char *dotlock_filename = NULL;
569 #ifndef DISABLE_DIRECT_ACCESS
570 static void lock_dot(char *filename)
579 dotlock_filename = (char *)xmalloc(strlen(filename) + 1);
581 /* Use a lock file named after our first argument with .lock appended:
582 If it exists, the mail file is locked. */
584 lockname = dot_filename(filename);
585 tempname = (char *)xmalloc(strlen(filename) + strlen("EXXXXXX") + 1);
586 strcpy(tempname, filename);
587 p = tempname + strlen(tempname);
588 while (p != tempname && !IS_DIRECTORY_SEP(p[-1]))
591 strcpy(p, "EXXXXXX");
599 /* Create the lock file, but not under the lock file name. */
600 /* Give up if cannot do that. */
603 /* Remove all group and other permissions.. */
604 umask(S_IRWXG|S_IRWXO);
605 desc = mkstemp(tempname);
607 desc = open(tempname, O_WRONLY | O_CREAT | O_EXCL, 0666);
611 int msz = strlen(tempname) + 50;
612 char *message = (char *)xmalloc(msz);
613 int sz = snprintf(message, msz,
614 "%s--see source file lib-src/movemail.c",
616 assert(sz>=0 && sz < msz);
617 pfatal_with_name(message);
621 tem = link(tempname, lockname);
627 /* If lock file is five minutes old, unlock it.
628 Five minutes should be good enough to cope with crashes
629 and wedgitude, and long enough to avoid being fooled
630 by time differences between machines. */
631 if (stat(lockname, &st) >= 0) {
633 if (st.st_ctime < now - 300)
637 strcpy(dotlock_filename, filename);
640 #endif /* not DISABLE_DIRECT_ACCESS */
642 static void unlock_dot(char *filename)
644 unlink(dot_filename(filename));
647 static void maybe_unlock_dot(void)
649 if (dotlock_filename)
650 unlock_dot(dotlock_filename);
653 /* Print error message and exit. */
655 static void fatal(char *s1, char *s2)
662 /* Print error message. `s1' is printf control string, `s2' is arg for it. */
664 static void error(const char *s1, const char *s2, const char *s3)
666 fprintf(stderr, "movemail: ");
667 fprintf(stderr, s1, s2, s3);
668 fprintf(stderr, "\n");
671 static void pfatal_with_name(char *name)
673 char *s = concat("", strerror(errno), " for %s");
677 static void pfatal_and_delete(char *name)
679 char *s = concat("", strerror(errno), " for %s");
684 /* Return a newly-allocated string whose contents concatenate those of
687 static char *concat(char *s1, char *s2, char *s3)
689 int len1 = strlen(s1), len2 = strlen(s2), len3 = strlen(s3);
690 char *result = (char *)xmalloc(len1 + len2 + len3 + 1);
692 xstrncpy(result, s1, len1+1);
693 xstrncpy(result + len1, s2, len2+1);
694 xstrncpy(result + len1 + len2, s3, len3+1);
695 *(result + len1 + len2 + len3) = '\0';
700 /* Like malloc but get fatal error if memory is exhausted. */
702 static long *xmalloc(unsigned int size)
704 long *result = (long *)malloc(size);
706 fatal("virtual memory exhausted", 0);
710 /* This is the guts of the interface to the Post Office Protocol. */
714 #include <sys/socket.h>
715 #include <netinet/in.h>
718 #include "../src/syspwd.h"
720 #define POP_ERROR (-1)
721 #define POP_RETRIEVED (0)
727 char ibuffer[BUFSIZ];
728 char obuffer[BUFSIZ];
731 static int popmail(char *user, char *outfile, char *password)
736 short *retrieved_list;
740 VERBOSE(("opening server\n"));
741 server = pop_open(0, user, password, POP_NO_GETPASS);
743 error("%s", pop_error, NULL);
747 VERBOSE(("stat'ing messages\n"));
748 if (pop_stat(server, &nmsgs, &nbytes)) {
749 error("%s", pop_error, NULL);
754 VERBOSE(("closing server\n"));
759 /* build a retrieved table */
760 retrieved_list = (short *)xmalloc(sizeof(short) * (nmsgs + 1));
761 memset(retrieved_list, 0, sizeof(short) * (nmsgs + 1));
763 mbfi = open(outfile, O_WRONLY | O_CREAT | O_EXCL, 0666);
766 error("Error in open: %s, %s", strerror(errno), outfile);
769 fchown(mbfi, getuid(), (gid_t) - 1);
771 if ((mbf = fdopen(mbfi, "wb")) == NULL) {
773 error("Error in fdopen: %s", strerror(errno), NULL);
779 for (idx = 0; idx < nmsgs; idx++) {
780 i = reverse ? nmsgs - idx : idx + 1;
781 VERBOSE(("checking message %d \n", i));
785 pop_search_top(server, i, match_lines,
786 regexp_pattern) == POP_RETRIEVED) {
787 VERBOSE(("retrieving message %d \n", i));
788 mbx_delimit_begin(mbf);
789 if (pop_retr(server, i, mbx_write, mbf) !=
791 error("%s", Errmsg, NULL);
796 retrieved_list[i] = 1;
798 mbx_delimit_end(mbf);
801 error("Error in fflush: %s", strerror(errno),
810 /* On AFS, a call to write only modifies the file in the local
811 * workstation's AFS cache. The changes are not written to the server
812 * until a call to fsync or close is made. Users with AFS home
813 * directories have lost mail when over quota because these checks were
814 * not made in previous versions of movemail. */
817 if (fsync(mbfi) < 0) {
818 error("Error in fsync: %s", strerror(errno), NULL);
823 if (close(mbfi) == -1) {
824 error("Error in close: %s", strerror(errno), NULL);
828 if (!keep_messages) {
829 for (i = 1; i <= nmsgs; i++) {
830 if (retrieved_list[i] == 1) {
831 VERBOSE(("deleting message %d \n", i));
832 if (pop_delete(server, i)) {
833 error("%s", pop_error, NULL);
841 VERBOSE(("closing server \n"));
842 if (pop_quit(server)) {
843 error("%s", pop_error, NULL);
851 pop_retr(popserver server, int msgno, int (*action) (char *, FILE *),
857 if (pop_retrieve_first(server, msgno, &line)) {
858 xstrncpy(Errmsg, pop_error, sizeof(Errmsg));
859 Errmsg[sizeof(Errmsg) - 1] = '\0';
863 while (!(ret = pop_retrieve_next(server, &line))) {
867 if ((*action) (line, arg) != POP_RETRIEVED) {
868 strcpy(Errmsg, strerror(errno));
875 xstrncpy(Errmsg, pop_error, sizeof(Errmsg));
876 Errmsg[sizeof(Errmsg) - 1] = '\0';
880 return (POP_RETRIEVED);
883 /* search the top lines of each message looking for a match */
885 pop_search_top(popserver server, int msgno, int lines,
886 struct re_pattern_buffer *regexp)
890 int match = POP_DONE;
892 if (pop_top_first(server, msgno, lines, &line)) {
893 xstrncpy(Errmsg, pop_error, sizeof(Errmsg));
894 Errmsg[sizeof(Errmsg) - 1] = '\0';
898 while (!(ret = pop_top_next(server, &line))) {
902 /* VERBOSE (("checking %s\n", line)); */
903 if (match != POP_RETRIEVED) {
905 re_match(regexp, line, strlen(line), 0,
907 strcpy(Errmsg, "error in regular expression");
910 } else if (ret >= 0) {
911 match = POP_RETRIEVED;
917 xstrncpy(Errmsg, pop_error, sizeof(Errmsg));
918 Errmsg[sizeof(Errmsg) - 1] = '\0';
925 /* Do this as a macro instead of using strcmp to save on execution time. */
926 #define IS_FROM_LINE(a) ((a[0] == 'F') \
932 static int mbx_write(char *line, FILE * mbf)
934 if (IS_FROM_LINE(line)) {
935 if (fputc('>', mbf) == EOF)
938 if (fputs(line, mbf) == EOF)
940 if (fputc(0x0a, mbf) == EOF)
942 return (POP_RETRIEVED);
945 static int mbx_delimit_begin(FILE * mbf)
947 if (fputs("\f\n0, unseen,,\n", mbf) == EOF)
949 return (POP_RETRIEVED);
952 static int mbx_delimit_end(FILE * mbf)
954 if (putc('\037', mbf) == EOF)
956 return (POP_RETRIEVED);
959 /* Turn a name, which is an ed-style (but Emacs syntax) regular
960 expression, into a real regular expression by compiling it. */
961 static struct re_pattern_buffer *compile_regex(char *pattern)
964 struct re_pattern_buffer *patbuf = 0;
967 (struct re_pattern_buffer *)
968 xmalloc(sizeof(struct re_pattern_buffer));
969 patbuf->translate = NULL;
970 patbuf->fastmap = NULL;
971 patbuf->buffer = NULL;
972 patbuf->allocated = 0;
974 err = re_compile_pattern(pattern, strlen(pattern), patbuf);
976 error("%s while compiling pattern", err, NULL);
983 #endif /* MAIL_USE_POP */
985 #ifndef HAVE_STRERROR
986 char *strerror(int errnum)
988 extern char *sys_errlist[];
991 if (errnum >= 0 && errnum < sys_nerr)
992 return sys_errlist[errnum];
993 return (char *)"Unknown error";
996 #endif /* ! HAVE_STRERROR */