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