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, *poppass = 0;
202 #ifndef DISABLE_DIRECT_ACCESS
208 int lock_method = DEFAULT_LOCKING;
210 char *maybe_lock_env;
212 maybe_lock_env = getenv("EMACSLOCKMETHOD");
213 if (maybe_lock_env) {
214 printf("maybe-lock_env: %s\n", maybe_lock_env);
215 lock_method = parse_lock_method(maybe_lock_env);
220 char *optstring = "i:o:m:p:l:r:xvhk";
222 char *optstring = "i:o:m:vh";
226 int opt = getopt_long(argc, argv, optstring, longopts, 0);
228 int opt = getopt(argc, argv, optstring);
230 # error "movemail cannot be built without getopt, preferably getopt_long"
238 case 1: /* one of the standard arguments seen */
247 case 'i': /* infile */
251 case 'o': /* outfile */
255 case 'p': /* pop password */
264 case 'l': /* lines to match */
265 match_lines = atoi(optarg);
268 case 'r': /* regular expression */
269 regexp_pattern = compile_regex(optarg);
274 lock_method = parse_lock_method(optarg);
287 while (optind < argc) {
289 inname = argv[optind];
291 outname = argv[optind];
293 poppass = argv[optind];
297 if (!inname || !outname) {
302 if (lock_method == MMDF)
307 fatal("Destination file name is empty", 0);
309 VERBOSE(("checking access to output file\n"));
310 /* Check access to output file. */
311 if (access(outname, F_OK) == 0 && access(outname, W_OK) != 0)
312 pfatal_with_name(outname);
314 /* Also check that outname's directory is writable to the real uid. */
316 char *buf = (char *)xmalloc(strlen(outname) + 1);
318 strcpy(buf, outname);
319 cp = buf + strlen(buf);
320 while (cp > buf && !IS_DIRECTORY_SEP(cp[-1]))
324 if (access(buf, W_OK) != 0)
325 pfatal_with_name(buf);
330 if (!strncmp(inname, "po:", 3)) {
331 int retcode = popmail(inname + 3, outname, poppass);
335 #endif /* MAIL_USE_POP */
337 #ifndef DISABLE_DIRECT_ACCESS
339 /* Check access to input file. */
340 if (access(inname, R_OK | W_OK) != 0)
341 pfatal_with_name(inname);
346 VERBOSE(("opening input file\n"));
348 switch (lock_method) {
350 indesc = open(inname, O_RDONLY);
354 indesc = open(inname, O_RDWR);
359 indesc = open(inname, O_RDWR);
364 indesc = open(inname, O_RDWR);
369 indesc = lk_open(inname, O_RDONLY, 0, 0, 10);
377 pfatal_with_name(inname);
380 /* In case movemail is setuid to root, make sure the user can
381 read the output file. */
382 umask(umask(0) & 0333);
385 outdesc = open(outname, O_WRONLY | O_CREAT | O_EXCL, 0666);
387 pfatal_with_name(outname);
389 VERBOSE(("locking input file\n"));
391 switch (lock_method) {
394 if (lockf(indesc, F_LOCK, 0) < 0)
395 pfatal_with_name(inname);
400 if (flock(indesc, LOCK_EX) < 0)
401 pfatal_with_name(inname);
406 if (locking(indesc, LK_RLCK, -1L) < 0)
407 pfatal_with_name(inname);
417 VERBOSE(("copying input file to output file\n"));
422 nread = read(indesc, buf, sizeof buf);
424 nread != write(outdesc, buf, nread)) {
425 int saved_errno = errno;
428 pfatal_with_name(outname);
430 if (nread < (int)sizeof(buf))
436 if (fsync(outdesc) < 0)
437 pfatal_and_delete(outname);
440 /* Check to make sure no errors before we zap the inbox. */
441 if (close(outdesc) != 0)
442 pfatal_and_delete(outname);
444 VERBOSE(("deleting or truncating input file\n"));
446 switch (lock_method) {
450 #ifdef HAVE_FTRUNCATE
451 if(ftruncate(indesc, 0L)!=0)
452 pfatal_and_delete(inname);
454 close(open(inname, O_CREAT | O_TRUNC | O_RDWR, 0666));
460 lk_close(indesc, 0, 0, 0);
474 if (!WIFEXITED(status))
476 else if (WEXITSTATUS(status) != 0)
477 exit(WEXITSTATUS(status));
479 if (lock_method == DOTLOCKING)
482 #endif /* not DISABLE_DIRECT_ACCESS */
487 static void usage(int lock_method)
489 printf ("Usage: movemail [-rvxkh] [-l lines ] [-m method ] [-i] "
490 "inbox [-o] destfile [[-p] POP-password]\n");
491 printf("where method is one of: dot");
504 printf("\nDefault is: %s\n", unparse_lock_method(lock_method));
508 static char *unparse_lock_method(int lock_method)
510 switch (lock_method) {
527 static int parse_lock_method(char *method_name)
529 if (!strcmp("dot", method_name) || !strcmp("file", method_name))
532 else if (!strcmp("lockf", method_name))
536 else if (!strcmp("flock", method_name))
540 else if (!strcmp("mmdf", method_name))
544 else if (!strcmp("locking", method_name))
548 fatal("invalid lock method: %s", method_name);
549 return 0; /* unreached */
552 static char *dot_filename(char *filename)
554 return concat(filename, ".lock", "");
557 static char *dotlock_filename = NULL;
559 #ifndef DISABLE_DIRECT_ACCESS
560 static void lock_dot(char *filename)
569 dotlock_filename = (char *)xmalloc(strlen(filename) + 1);
571 /* Use a lock file named after our first argument with .lock appended:
572 If it exists, the mail file is locked. */
574 lockname = dot_filename(filename);
575 tempname = (char *)xmalloc(strlen(filename) + strlen("EXXXXXX") + 1);
576 strcpy(tempname, filename);
577 p = tempname + strlen(tempname);
578 while (p != tempname && !IS_DIRECTORY_SEP(p[-1]))
581 strcpy(p, "EXXXXXX");
589 /* Create the lock file, but not under the lock file name. */
590 /* Give up if cannot do that. */
593 desc = mkstemp(tempname);
595 desc = open(tempname, O_WRONLY | O_CREAT | O_EXCL, 0666);
599 int msz = strlen(tempname) + 50;
600 char *message = (char *)xmalloc(msz);
601 int sz = snprintf(message, msz,
602 "%s--see source file lib-src/movemail.c",
604 assert(sz>=0 && sz < msz);
605 pfatal_with_name(message);
609 tem = link(tempname, lockname);
615 /* If lock file is five minutes old, unlock it.
616 Five minutes should be good enough to cope with crashes
617 and wedgitude, and long enough to avoid being fooled
618 by time differences between machines. */
619 if (stat(lockname, &st) >= 0) {
621 if (st.st_ctime < now - 300)
625 strcpy(dotlock_filename, filename);
627 #endif /* not DISABLE_DIRECT_ACCESS */
629 static void unlock_dot(char *filename)
631 unlink(dot_filename(filename));
634 static void maybe_unlock_dot(void)
636 if (dotlock_filename)
637 unlock_dot(dotlock_filename);
640 /* Print error message and exit. */
642 static void fatal(char *s1, char *s2)
649 /* Print error message. `s1' is printf control string, `s2' is arg for it. */
651 static void error(const char *s1, const char *s2, const char *s3)
653 fprintf(stderr, "movemail: ");
654 fprintf(stderr, s1, s2, s3);
655 fprintf(stderr, "\n");
658 static void pfatal_with_name(char *name)
660 char *s = concat("", strerror(errno), " for %s");
664 static void pfatal_and_delete(char *name)
666 char *s = concat("", strerror(errno), " for %s");
671 /* Return a newly-allocated string whose contents concatenate those of
674 static char *concat(char *s1, char *s2, char *s3)
676 int len1 = strlen(s1), len2 = strlen(s2), len3 = strlen(s3);
677 char *result = (char *)xmalloc(len1 + len2 + len3 + 1);
679 strncpy(result, s1, len1+1);
680 strncpy(result + len1, s2, len2+1);
681 strncpy(result + len1 + len2, s3, len3+1);
682 *(result + len1 + len2 + len3) = '\0';
687 /* Like malloc but get fatal error if memory is exhausted. */
689 static long *xmalloc(unsigned int size)
691 long *result = (long *)malloc(size);
693 fatal("virtual memory exhausted", 0);
697 /* This is the guts of the interface to the Post Office Protocol. */
701 #include <sys/socket.h>
702 #include <netinet/in.h>
705 #include "../src/syspwd.h"
707 #define POP_ERROR (-1)
708 #define POP_RETRIEVED (0)
714 char ibuffer[BUFSIZ];
715 char obuffer[BUFSIZ];
718 static int popmail(char *user, char *outfile, char *password)
723 short *retrieved_list;
727 VERBOSE(("opening server\n"));
728 server = pop_open(0, user, password, POP_NO_GETPASS);
730 error("%s", pop_error, NULL);
734 VERBOSE(("stat'ing messages\n"));
735 if (pop_stat(server, &nmsgs, &nbytes)) {
736 error("%s", pop_error, NULL);
741 VERBOSE(("closing server\n"));
746 /* build a retrieved table */
747 retrieved_list = (short *)xmalloc(sizeof(short) * (nmsgs + 1));
748 memset(retrieved_list, 0, sizeof(short) * (nmsgs + 1));
750 mbfi = open(outfile, O_WRONLY | O_CREAT | O_EXCL, 0666);
753 error("Error in open: %s, %s", strerror(errno), outfile);
756 fchown(mbfi, getuid(), (gid_t) - 1);
758 if ((mbf = fdopen(mbfi, "wb")) == NULL) {
760 error("Error in fdopen: %s", strerror(errno), NULL);
766 for (idx = 0; idx < nmsgs; idx++) {
767 i = reverse ? nmsgs - idx : idx + 1;
768 VERBOSE(("checking message %d \n", i));
772 pop_search_top(server, i, match_lines,
773 regexp_pattern) == POP_RETRIEVED) {
774 VERBOSE(("retrieving message %d \n", i));
775 mbx_delimit_begin(mbf);
776 if (pop_retr(server, i, mbx_write, mbf) !=
778 error("%s", Errmsg, NULL);
783 retrieved_list[i] = 1;
785 mbx_delimit_end(mbf);
788 error("Error in fflush: %s", strerror(errno),
797 /* On AFS, a call to write only modifies the file in the local
798 * workstation's AFS cache. The changes are not written to the server
799 * until a call to fsync or close is made. Users with AFS home
800 * directories have lost mail when over quota because these checks were
801 * not made in previous versions of movemail. */
804 if (fsync(mbfi) < 0) {
805 error("Error in fsync: %s", strerror(errno), NULL);
810 if (close(mbfi) == -1) {
811 error("Error in close: %s", strerror(errno), NULL);
815 if (!keep_messages) {
816 for (i = 1; i <= nmsgs; i++) {
817 if (retrieved_list[i] == 1) {
818 VERBOSE(("deleting message %d \n", i));
819 if (pop_delete(server, i)) {
820 error("%s", pop_error, NULL);
828 VERBOSE(("closing server \n"));
829 if (pop_quit(server)) {
830 error("%s", pop_error, NULL);
838 pop_retr(popserver server, int msgno, int (*action) (char *, FILE *),
844 if (pop_retrieve_first(server, msgno, &line)) {
845 strncpy(Errmsg, pop_error, sizeof(Errmsg));
846 Errmsg[sizeof(Errmsg) - 1] = '\0';
850 while (!(ret = pop_retrieve_next(server, &line))) {
854 if ((*action) (line, arg) != POP_RETRIEVED) {
855 strcpy(Errmsg, strerror(errno));
862 strncpy(Errmsg, pop_error, sizeof(Errmsg));
863 Errmsg[sizeof(Errmsg) - 1] = '\0';
867 return (POP_RETRIEVED);
870 /* search the top lines of each message looking for a match */
872 pop_search_top(popserver server, int msgno, int lines,
873 struct re_pattern_buffer *regexp)
877 int match = POP_DONE;
879 if (pop_top_first(server, msgno, lines, &line)) {
880 strncpy(Errmsg, pop_error, sizeof(Errmsg));
881 Errmsg[sizeof(Errmsg) - 1] = '\0';
885 while (!(ret = pop_top_next(server, &line))) {
889 /* VERBOSE (("checking %s\n", line)); */
890 if (match != POP_RETRIEVED) {
892 re_match(regexp, line, strlen(line), 0,
894 strcpy(Errmsg, "error in regular expression");
897 } else if (ret >= 0) {
898 match = POP_RETRIEVED;
904 strncpy(Errmsg, pop_error, sizeof(Errmsg));
905 Errmsg[sizeof(Errmsg) - 1] = '\0';
912 /* Do this as a macro instead of using strcmp to save on execution time. */
913 #define IS_FROM_LINE(a) ((a[0] == 'F') \
919 static int mbx_write(char *line, FILE * mbf)
921 if (IS_FROM_LINE(line)) {
922 if (fputc('>', mbf) == EOF)
925 if (fputs(line, mbf) == EOF)
927 if (fputc(0x0a, mbf) == EOF)
929 return (POP_RETRIEVED);
932 static int mbx_delimit_begin(FILE * mbf)
934 if (fputs("\f\n0, unseen,,\n", mbf) == EOF)
936 return (POP_RETRIEVED);
939 static int mbx_delimit_end(FILE * mbf)
941 if (putc('\037', mbf) == EOF)
943 return (POP_RETRIEVED);
946 /* Turn a name, which is an ed-style (but Emacs syntax) regular
947 expression, into a real regular expression by compiling it. */
948 static struct re_pattern_buffer *compile_regex(char *pattern)
951 struct re_pattern_buffer *patbuf = 0;
954 (struct re_pattern_buffer *)
955 xmalloc(sizeof(struct re_pattern_buffer));
956 patbuf->translate = NULL;
957 patbuf->fastmap = NULL;
958 patbuf->buffer = NULL;
959 patbuf->allocated = 0;
961 err = re_compile_pattern(pattern, strlen(pattern), patbuf);
963 error("%s while compiling pattern", err, NULL);
970 #endif /* MAIL_USE_POP */
972 #ifndef HAVE_STRERROR
973 char *strerror(int errnum)
975 extern char *sys_errlist[];
978 if (errnum >= 0 && errnum < sys_nerr)
979 return sys_errlist[errnum];
980 return (char *)"Unknown error";
983 #endif /* ! HAVE_STRERROR */