Merge branch 'njsf-cov' into for-steve
[sxemacs] / lib-src / movemail.c
1 /* movemail foo bar -- move file foo to file bar,
2    locking file foo.
3    Copyright (C) 1986, 1992, 1993, 1994, 1996 Free Software Foundation, Inc.
4    
5    Copyright (C) 2005 Johann "Myrkraverk" Oskarsson <johann@myrkraverk.com>
6
7 This file is part of SXEmacs.
8
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.
13
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.
18
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/>.
21
22  Please mail bugs and suggestions to the SXEmacs maintainer.
23 */
24
25 /* Important notice:
26  *
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.
33  */
34
35 /*
36  * Mike Sperber <sperber@informatik.uni-tuebingen.de> reorganized
37  * everything that has to with locking in December 1999.
38  */
39
40 /*
41  * Modified January, 1986 by Michael R. Gretzinger (Project Athena)
42  *
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.
48  * 
49  * New module: popmail.c
50  * Modified routines:
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
55  *
56  * Modified August, 1993 by Jonathan Kamens (OpenVision Technologies)
57  *
58  * Move all of the POP code into a separate file, "pop.c".
59  * Use strerror instead of get_errmsg.
60  *
61  */
62
63 #define NO_SHORTNAMES           /* Tell config not to load remap.h */
64 #define DONT_ENCAPSULATE
65 #include <config.h>
66 #include <sys/types.h>
67 #include <sys/stat.h>
68 #include <stdio.h>
69 #include <errno.h>
70 #include "../src/sysfile.h"
71 #include "../src/syswait.h"
72 #include "../src/systime.h"
73 #include <stdlib.h>
74 #include <string.h>
75 #ifdef HAVE_GETOPT_H
76 #include <getopt.h>
77 #endif
78 #ifdef MAIL_USE_POP
79 #include "pop.h"
80 #include "../src/regex.h"
81 #endif
82
83 #include <assert.h>
84
85 extern char *optarg;
86 extern int optind, opterr;
87
88 #ifndef HAVE_STRERROR
89 char *strerror(int errnum);
90 #endif                          /* HAVE_STRERROR */
91
92 #ifndef DIRECTORY_SEP
93 #define DIRECTORY_SEP '/'
94 #endif
95 #ifndef IS_DIRECTORY_SEP
96 #define IS_DIRECTORY_SEP(_c_) ((_c_) == DIRECTORY_SEP)
97 #endif
98
99 #if defined (HAVE_UNISTD_H)
100 #include <unistd.h>
101 #endif                          /* unistd.h */
102 #ifndef F_OK
103 #define F_OK 0
104 #define X_OK 1
105 #define W_OK 2
106 #define R_OK 4
107 #endif                          /* No F_OK */
108
109 #if defined (HAVE_FCNTL_H)
110 #include <fcntl.h>
111 #endif                          /* fcntl.h */
112
113 #ifdef HAVE_LOCKING
114 #include <sys/locking.h>
115 #endif
116
117 #ifdef HAVE_MMDF
118 extern int lk_open(), lk_close();
119 #endif
120
121 /* Cancel substitutions made by config.h for Emacs.  */
122 #undef open
123 #undef read
124 #undef write
125 #undef close
126
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);
134 #ifdef MAIL_USE_POP
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);
144 #endif
145
146 int verbose = 0;
147 #ifdef MAIL_USE_POP
148 int reverse = 0;
149 int keep_messages = 0;
150 struct re_pattern_buffer *regexp_pattern = 0;
151 int match_lines = 10;
152 #endif
153
154 #define VERBOSE(x) if (verbose) { printf x; fflush(stdout); }
155
156 #ifdef HAVE_GETOPT_LONG
157 struct option longopts[] = {
158         {"inbox", required_argument, NULL, 'i'},
159         {"outfile", required_argument, NULL, 'o'},
160 #ifdef MAIL_USE_POP
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'},
166 #endif
167         {"lock-method", required_argument, NULL, 'm'},
168         {"help", no_argument, NULL, 'h'},
169         {"verbose", no_argument, NULL, 'v'},
170         {0}
171 };
172 #endif 
173
174 #define DOTLOCKING      0
175 #define FLOCKING        1
176 #define LOCKFING        2
177 #define MMDF            3
178 #define LOCKING         4
179
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
188 #else
189 #define DEFAULT_LOCKING DOTLOCKING
190 #endif
191
192 #ifndef DISABLE_DIRECT_ACCESS
193 static void lock_dot(char *);
194 #endif
195 static void unlock_dot(char *);
196 static int parse_lock_method(char *);
197 static char *unparse_lock_method(int);
198
199 int main(int argc, char *argv[])
200 {
201         char *inname = 0, *outname = 0;
202 #if defined MAIL_USE_POP
203         char *poppass = 0;
204 #endif  /* MAIL_USE_POP */
205 #ifndef DISABLE_DIRECT_ACCESS
206         int indesc, outdesc;
207         int nread;
208         int status;
209 #endif
210
211         int lock_method = DEFAULT_LOCKING;
212
213         char *maybe_lock_env;
214
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);
219         }
220
221         for (;;) {
222 #ifdef MAIL_USE_POP
223                 char *optstring = "i:o:m:p:l:r:xvhk";
224 #else
225                 char *optstring = "i:o:m:vh";
226 #endif
227
228 #if HAVE_GETOPT_LONG
229                 int opt = getopt_long(argc, argv, optstring, longopts, 0);
230 #elif HAVE_GETOPT
231                 int opt = getopt(argc, argv, optstring);
232 #else
233 # error         "movemail cannot be built without getopt, preferably getopt_long"
234 #endif
235                 if (opt == EOF)
236                         break;
237
238                 switch (opt) {
239                 case 0:
240                         break;
241                 case 1: /* one of the standard arguments seen */
242                         if (!inname) {
243                                 inname = optarg;
244                         } else if (!outname) {
245                                 outname = optarg;
246 #if defined MAIL_USE_POP
247                         } else {
248                                 poppass = optarg;
249 #endif  /* MAIL_USE_POP */
250                         }
251                         break;
252
253                 case 'i':       /* infile */
254                         inname = optarg;
255                         break;
256
257                 case 'o':       /* outfile */
258                         outname = optarg;
259                         break;
260 #ifdef MAIL_USE_POP
261                 case 'p':       /* pop password */
262                         poppass = optarg;
263                         break;
264                 case 'k':
265                         keep_messages = 1;
266                         break;
267                 case 'x':
268                         reverse = 1;
269                         break;
270                 case 'l':       /* lines to match */
271                         match_lines = atoi(optarg);
272                         break;
273
274                 case 'r':       /* regular expression */
275                         regexp_pattern = compile_regex(optarg);
276                         break;
277 #endif
278
279                 case 'm':
280                         lock_method = parse_lock_method(optarg);
281                         break;
282                 case 'h':
283                         usage(lock_method);
284                         exit(0);
285                 case 'v':
286                         verbose = 1;
287                         break;
288                 default:
289                         break;
290                 }
291         }
292
293         while (optind < argc) {
294                 if (!inname) {
295                         inname = argv[optind];
296                 } else if (!outname) {
297                         outname = argv[optind];
298 #if defined MAIL_USE_POP
299                 } else {
300                         poppass = argv[optind];
301 #endif  /* MAIL_USE_POP */
302                 }
303                 optind++;
304         }
305
306         if (!inname || !outname) {
307                 usage(lock_method);
308                 exit(1);
309         }
310 #ifdef HAVE_MMDF
311         if (lock_method == MMDF)
312                 mmdf_init(argv[0]);
313 #endif
314
315         if (*outname == 0)
316                 fatal("Destination file name is empty", 0);
317
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);
322
323         /* Also check that outname's directory is writable to the real uid.  */
324         {
325                 char *buf = (char *)xmalloc(strlen(outname) + 1);
326                 char *cp;
327                 strcpy(buf, outname);
328                 cp = buf + strlen(buf);
329                 while (cp > buf && !IS_DIRECTORY_SEP(cp[-1]))
330                         *--cp = 0;
331                 if (cp == buf)
332                         *cp++ = '.';
333                 if (access(buf, W_OK) != 0)
334                         pfatal_with_name(buf);
335                 free(buf);
336         }
337
338 #ifdef MAIL_USE_POP
339         if (!strncmp(inname, "po:", 3)) {
340                 int retcode = popmail(inname + 3, outname, poppass);
341                 exit(retcode);
342         }
343         setuid(getuid());
344 #endif                          /* MAIL_USE_POP */
345
346 #ifndef DISABLE_DIRECT_ACCESS
347
348         /* Check access to input file.  */
349         if (access(inname, R_OK | W_OK) != 0)
350                 pfatal_with_name(inname);
351
352         if (fork() == 0) {
353                 setuid(getuid());
354
355                 VERBOSE(("opening input file\n"));
356
357                 switch (lock_method) {
358                 case DOTLOCKING:
359                         indesc = open(inname, O_RDONLY);
360                         break;
361 #ifdef HAVE_LOCKF
362                 case LOCKFING:
363                         indesc = open(inname, O_RDWR);
364                         break;
365 #endif
366 #ifdef HAVE_FLOCK
367                 case FLOCKING:
368                         indesc = open(inname, O_RDWR);
369                         break;
370 #endif
371 #ifdef HAVE_LOCKING
372                 case LOCKING:
373                         indesc = open(inname, O_RDWR);
374                         break;
375 #endif
376 #ifdef HAVE_MMDF
377                 case MMDF:
378                         indesc = lk_open(inname, O_RDONLY, 0, 0, 10);
379                         break;
380 #endif
381                 default:
382                         abort();
383                 }
384
385                 if (indesc < 0)
386                         pfatal_with_name(inname);
387
388 #ifdef HAVE_UMASK
389                 /* In case movemail is setuid to root, make sure the user can
390                    read the output file.  */
391                 umask(umask(0) & 0333);
392 #endif
393
394                 outdesc = open(outname, O_WRONLY | O_CREAT | O_EXCL, 0666);
395                 if (outdesc < 0)
396                         pfatal_with_name(outname);
397
398                 VERBOSE(("locking input file\n"));
399
400                 switch (lock_method) {
401 #ifdef HAVE_LOCKF
402                 case LOCKFING:
403                         if (lockf(indesc, F_LOCK, 0) < 0)
404                                 pfatal_with_name(inname);
405                         break;
406 #endif
407 #ifdef HAVE_FLOCK
408                 case FLOCKING:
409                         if (flock(indesc, LOCK_EX) < 0)
410                                 pfatal_with_name(inname);
411                         break;
412 #endif
413 #ifdef HAVE_LOCKING
414                 case LOCKING:
415                         if (locking(indesc, LK_RLCK, -1L) < 0)
416                                 pfatal_with_name(inname);
417                         break;
418 #endif
419                 case DOTLOCKING:
420                         lock_dot(inname);
421                         break;
422                 default:
423                         break;
424                 }
425
426                 VERBOSE(("copying input file to output file\n"));
427
428                 {
429                         char buf[1024];
430                         while (1) {
431                                 nread = read(indesc, buf, sizeof buf);
432                                 if (nread < 0 ||  
433                                     nread != write(outdesc, buf, nread)) {
434                                         int saved_errno = errno;
435                                         unlink(outname);
436                                         errno = saved_errno;
437                                         pfatal_with_name(outname);
438                                 }
439                                 if (nread < (int)sizeof(buf))
440                                         break;
441                         }
442                 }
443
444 #ifdef HAVE_FSYNC
445                 if (fsync(outdesc) < 0)
446                         pfatal_and_delete(outname);
447 #endif
448
449                 /* Check to make sure no errors before we zap the inbox.  */
450                 if (close(outdesc) != 0)
451                         pfatal_and_delete(outname);
452
453                 VERBOSE(("deleting or truncating input file\n"));
454
455                 switch (lock_method) {
456                 case LOCKFING:
457                 case FLOCKING:
458                 case LOCKING:
459 #ifdef HAVE_FTRUNCATE
460                         if(ftruncate(indesc, 0L)!=0)
461                                 pfatal_and_delete(inname);
462 #else
463                         close(open(inname, O_CREAT | O_TRUNC | O_RDWR, 0666));
464 #endif
465                         close(indesc);
466                         break;
467 #ifdef HAVE_MMDF
468                 case MMDF:
469                         lk_close(indesc, 0, 0, 0);
470                         break;
471 #endif
472                 case DOTLOCKING:
473                         creat(inname, 0600);
474                         break;
475                 default:
476                         break;
477                 }
478
479                 exit(0);
480         }
481
482         wait(&status);
483         if (!WIFEXITED(status))
484                 exit(1);
485         else if (WEXITSTATUS(status) != 0)
486                 exit(WEXITSTATUS(status));
487
488         if (lock_method == DOTLOCKING)
489                 unlock_dot(inname);
490
491 #endif                          /* not DISABLE_DIRECT_ACCESS */
492
493         return 0;
494 }
495
496 static void usage(int lock_method)
497 {
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");
501 #ifdef HAVE_LOCKF
502         printf(", lockf");
503 #endif
504 #ifdef HAVE_FLOCK
505         printf(", flock");
506 #endif
507 #ifdef HAVE_MMDF
508         printf(", mmdf");
509 #endif
510 #ifdef HAVE_LOCKING
511         printf(", locking");
512 #endif
513         printf("\nDefault is: %s\n", unparse_lock_method(lock_method));
514
515 }
516
517 static char *unparse_lock_method(int lock_method)
518 {
519         switch (lock_method) {
520         case DOTLOCKING:
521                 return "dot";
522         case FLOCKING:
523                 return "flock";
524         case LOCKFING:
525                 return "lockf";
526         case LOCKING:
527                 return "locking";
528         case MMDF:
529                 return "mmdf";
530         default:
531                 abort();
532                 return 0;
533         }
534 }
535
536 static int parse_lock_method(char *method_name)
537 {
538         if (!strcmp("dot", method_name) || !strcmp("file", method_name))
539                 return DOTLOCKING;
540 #ifdef HAVE_LOCKF
541         else if (!strcmp("lockf", method_name))
542                 return LOCKFING;
543 #endif
544 #ifdef HAVE_FLOCK
545         else if (!strcmp("flock", method_name))
546                 return FLOCKING;
547 #endif
548 #ifdef HAVE_MMDF
549         else if (!strcmp("mmdf", method_name))
550                 return MMDF;
551 #endif
552 #ifdef HAVE_LOCKING
553         else if (!strcmp("locking", method_name))
554                 return LOCKING;
555 #endif
556         else
557                 fatal("invalid lock method: %s", method_name);
558         return 0;               /* unreached */
559 }
560
561 static char *dot_filename(char *filename)
562 {
563         return concat(filename, ".lock", "");
564 }
565
566 static char *dotlock_filename = NULL;
567
568 #ifndef DISABLE_DIRECT_ACCESS
569 static void lock_dot(char *filename)
570 {
571         struct stat st;
572         long now;
573         int tem;
574         char *lockname, *p;
575         char *tempname;
576         int desc;
577
578         dotlock_filename = (char *)xmalloc(strlen(filename) + 1);
579
580         /* Use a lock file named after our first argument with .lock appended:
581            If it exists, the mail file is locked. */
582
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]))
588                 p--;
589         *p = 0;
590         strcpy(p, "EXXXXXX");
591
592 #ifndef HAVE_MKSTEMP
593         mktemp(tempname);
594         unlink(tempname);
595 #endif
596
597         for (;;) {
598                 /* Create the lock file, but not under the lock file name.  */
599                 /* Give up if cannot do that.  */
600
601 #ifdef HAVE_MKSTEMP
602                 /* Remove all group and other permissions.. */
603                 umask(S_IRWXG|S_IRWXO);
604                 desc = mkstemp(tempname);
605 #else
606                 desc = open(tempname, O_WRONLY | O_CREAT | O_EXCL, 0666);
607 #endif
608
609                 if (desc < 0) {
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",
614                                           tempname);
615                         assert(sz>=0 && sz < msz);
616                         pfatal_with_name(message);
617                 }
618                 close(desc);
619
620                 tem = link(tempname, lockname);
621                 unlink(tempname);
622                 if (tem >= 0)
623                         break;
624                 sleep(1);
625
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) {
631                         now = time(0);
632                         if (st.st_ctime < now - 300)
633                                 unlink(lockname);
634                 }
635         }
636         strcpy(dotlock_filename, filename);
637 }
638 #endif                          /* not DISABLE_DIRECT_ACCESS */
639
640 static void unlock_dot(char *filename)
641 {
642         unlink(dot_filename(filename));
643 }
644
645 static void maybe_unlock_dot(void)
646 {
647         if (dotlock_filename)
648                 unlock_dot(dotlock_filename);
649 }
650
651 /* Print error message and exit.  */
652
653 static void fatal(char *s1, char *s2)
654 {
655         maybe_unlock_dot();
656         error(s1, s2, NULL);
657         exit(1);
658 }
659
660 /* Print error message.  `s1' is printf control string, `s2' is arg for it. */
661
662 static void error(const char *s1, const char *s2, const char *s3)
663 {
664         fprintf(stderr, "movemail: ");
665         fprintf(stderr, s1, s2, s3);
666         fprintf(stderr, "\n");
667 }
668
669 static void pfatal_with_name(char *name)
670 {
671         char *s = concat("", strerror(errno), " for %s");
672         fatal(s, name);
673 }
674
675 static void pfatal_and_delete(char *name)
676 {
677         char *s = concat("", strerror(errno), " for %s");
678         unlink(name);
679         fatal(s, name);
680 }
681
682 /* Return a newly-allocated string whose contents concatenate those of
683    s1, s2, s3.  */
684
685 static char *concat(char *s1, char *s2, char *s3)
686 {
687         int len1 = strlen(s1), len2 = strlen(s2), len3 = strlen(s3);
688         char *result = (char *)xmalloc(len1 + len2 + len3 + 1);
689
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';
694
695         return result;
696 }
697
698 /* Like malloc but get fatal error if memory is exhausted.  */
699
700 static long *xmalloc(unsigned int size)
701 {
702         long *result = (long *)malloc(size);
703         if (!result)
704                 fatal("virtual memory exhausted", 0);
705         return result;
706 }
707
708 /* This is the guts of the interface to the Post Office Protocol.  */
709
710 #ifdef MAIL_USE_POP
711
712 #include <sys/socket.h>
713 #include <netinet/in.h>
714 #include <netdb.h>
715 #include <stdio.h>
716 #include "../src/syspwd.h"
717
718 #define POP_ERROR       (-1)
719 #define POP_RETRIEVED (0)
720 #define POP_DONE (1)
721
722 char *progname;
723 FILE *sfi;
724 FILE *sfo;
725 char ibuffer[BUFSIZ];
726 char obuffer[BUFSIZ];
727 char Errmsg[80];
728
729 static int popmail(char *user, char *outfile, char *password)
730 {
731         int nmsgs, nbytes;
732         register int i, idx;
733         int mbfi;
734         short *retrieved_list;
735         FILE *mbf;
736         popserver server;
737
738         VERBOSE(("opening server\n"));
739         server = pop_open(0, user, password, POP_NO_GETPASS);
740         if (!server) {
741                 error("%s", pop_error, NULL);
742                 return (1);
743         }
744
745         VERBOSE(("stat'ing messages\n"));
746         if (pop_stat(server, &nmsgs, &nbytes)) {
747                 error("%s", pop_error, NULL);
748                 return (1);
749         }
750
751         if (!nmsgs) {
752                 VERBOSE(("closing server\n"));
753                 pop_close(server);
754                 return (0);
755         }
756
757         /* build a retrieved table */
758         retrieved_list = (short *)xmalloc(sizeof(short) * (nmsgs + 1));
759         memset(retrieved_list, 0, sizeof(short) * (nmsgs + 1));
760
761         mbfi = open(outfile, O_WRONLY | O_CREAT | O_EXCL, 0666);
762         if (mbfi < 0) {
763                 pop_close(server);
764                 error("Error in open: %s, %s", strerror(errno), outfile);
765                 return (1);
766         }
767         fchown(mbfi, getuid(), (gid_t) - 1);
768
769         if ((mbf = fdopen(mbfi, "wb")) == NULL) {
770                 pop_close(server);
771                 error("Error in fdopen: %s", strerror(errno), NULL);
772                 close(mbfi);
773                 unlink(outfile);
774                 return (1);
775         }
776
777         for (idx = 0; idx < nmsgs; idx++) {
778                 i = reverse ? nmsgs - idx : idx + 1;
779                 VERBOSE(("checking message %d     \n", i));
780
781                 if (!regexp_pattern
782                     ||
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) !=
788                             POP_RETRIEVED) {
789                                 error("%s", Errmsg, NULL);
790                                 close(mbfi);
791                                 return (1);
792                         }
793
794                         retrieved_list[i] = 1;
795
796                         mbx_delimit_end(mbf);
797                         fflush(mbf);
798                         if (ferror(mbf)) {
799                                 error("Error in fflush: %s", strerror(errno),
800                                       NULL);
801                                 pop_close(server);
802                                 close(mbfi);
803                                 return (1);
804                         }
805                 }
806         }
807
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. */
813
814 #ifdef HAVE_FSYNC
815         if (fsync(mbfi) < 0) {
816                 error("Error in fsync: %s", strerror(errno), NULL);
817                 return (1);
818         }
819 #endif
820
821         if (close(mbfi) == -1) {
822                 error("Error in close: %s", strerror(errno), NULL);
823                 return (1);
824         }
825
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);
832                                         pop_close(server);
833                                         return (1);
834                                 }
835                         }
836                 }
837         }
838
839         VERBOSE(("closing server             \n"));
840         if (pop_quit(server)) {
841                 error("%s", pop_error, NULL);
842                 return (1);
843         }
844
845         return (0);
846 }
847
848 static int
849 pop_retr(popserver server, int msgno, int (*action) (char *, FILE *),
850          FILE * arg)
851 {
852         char *line;
853         int ret;
854
855         if (pop_retrieve_first(server, msgno, &line)) {
856                 strncpy(Errmsg, pop_error, sizeof(Errmsg));
857                 Errmsg[sizeof(Errmsg) - 1] = '\0';
858                 return (POP_ERROR);
859         }
860
861         while (!(ret = pop_retrieve_next(server, &line))) {
862                 if (!line)
863                         break;
864
865                 if ((*action) (line, arg) != POP_RETRIEVED) {
866                         strcpy(Errmsg, strerror(errno));
867                         pop_close(server);
868                         return (POP_ERROR);
869                 }
870         }
871
872         if (ret) {
873                 strncpy(Errmsg, pop_error, sizeof(Errmsg));
874                 Errmsg[sizeof(Errmsg) - 1] = '\0';
875                 return (POP_ERROR);
876         }
877
878         return (POP_RETRIEVED);
879 }
880
881 /* search the top lines of each message looking for a match */
882 static int
883 pop_search_top(popserver server, int msgno, int lines,
884                struct re_pattern_buffer *regexp)
885 {
886         char *line;
887         int ret;
888         int match = POP_DONE;
889
890         if (pop_top_first(server, msgno, lines, &line)) {
891                 strncpy(Errmsg, pop_error, sizeof(Errmsg));
892                 Errmsg[sizeof(Errmsg) - 1] = '\0';
893                 return (POP_ERROR);
894         }
895
896         while (!(ret = pop_top_next(server, &line))) {
897                 if (!line)
898                         break;
899
900                 /*      VERBOSE (("checking %s\n", line)); */
901                 if (match != POP_RETRIEVED) {
902                         if ((ret =
903                              re_match(regexp, line, strlen(line), 0,
904                                       0)) == -2) {
905                                 strcpy(Errmsg, "error in regular expression");
906                                 pop_close(server);
907                                 return (POP_ERROR);
908                         } else if (ret >= 0) {
909                                 match = POP_RETRIEVED;
910                         }
911                 }
912         }
913
914         if (ret) {
915                 strncpy(Errmsg, pop_error, sizeof(Errmsg));
916                 Errmsg[sizeof(Errmsg) - 1] = '\0';
917                 return (POP_ERROR);
918         }
919
920         return match;
921 }
922
923 /* Do this as a macro instead of using strcmp to save on execution time. */
924 #define IS_FROM_LINE(a) ((a[0] == 'F') \
925                          && (a[1] == 'r') \
926                          && (a[2] == 'o') \
927                          && (a[3] == 'm') \
928                          && (a[4] == ' '))
929
930 static int mbx_write(char *line, FILE * mbf)
931 {
932         if (IS_FROM_LINE(line)) {
933                 if (fputc('>', mbf) == EOF)
934                         return (POP_ERROR);
935         }
936         if (fputs(line, mbf) == EOF)
937                 return (POP_ERROR);
938         if (fputc(0x0a, mbf) == EOF)
939                 return (POP_ERROR);
940         return (POP_RETRIEVED);
941 }
942
943 static int mbx_delimit_begin(FILE * mbf)
944 {
945         if (fputs("\f\n0, unseen,,\n", mbf) == EOF)
946                 return (POP_ERROR);
947         return (POP_RETRIEVED);
948 }
949
950 static int mbx_delimit_end(FILE * mbf)
951 {
952         if (putc('\037', mbf) == EOF)
953                 return (POP_ERROR);
954         return (POP_RETRIEVED);
955 }
956
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)
960 {
961         const char *err;
962         struct re_pattern_buffer *patbuf = 0;
963
964         patbuf =
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;
971
972         err = re_compile_pattern(pattern, strlen(pattern), patbuf);
973         if (err != NULL) {
974                 error("%s while compiling pattern", err, NULL);
975                 return 0;
976         }
977
978         return patbuf;
979 }
980
981 #endif                          /* MAIL_USE_POP */
982
983 #ifndef HAVE_STRERROR
984 char *strerror(int errnum)
985 {
986         extern char *sys_errlist[];
987         extern int sys_nerr;
988
989         if (errnum >= 0 && errnum < sys_nerr)
990                 return sys_errlist[errnum];
991         return (char *)"Unknown error";
992 }
993
994 #endif                          /* ! HAVE_STRERROR */