1 /* movemail foo bar -- move file foo to file bar,
2    locking file foo the way /bin/mail respects.
3 
4 Copyright (C) 1986, 1992-1994, 1996, 1999, 2001-2021 Free Software
5 Foundation, Inc.
6 
7 This file is part of GNU Emacs.
8 
9 GNU Emacs 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 (at
12 your option) any later version.
13 
14 GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
21 
22 
23 /* Important notice: defining MAIL_USE_FLOCK or MAIL_USE_LOCKF *will
24    cause loss of mail* if you do it on a system that does not normally
25    use flock/lockf as its way of interlocking access to inbox files.  The
26    setting of MAIL_USE_FLOCK and MAIL_USE_LOCKF *must agree* with the
27    system's own conventions.  It is not a choice that is up to you.
28 
29    So, if your system uses lock files rather than flock, then the only way
30    you can get proper operation is to enable movemail to write lockfiles there.
31    This means you must either give that directory access modes
32    that permit everyone to write lockfiles in it, or you must make movemail
33    a setuid or setgid program.  */
34 
35 /*
36  * Modified January, 1986 by Michael R. Gretzinger (Project Athena)
37  *
38  * Added POP (Post Office Protocol) service.  When compiled -DMAIL_USE_POP
39  * movemail will accept input filename arguments of the form
40  * "po:username".  This will cause movemail to open a connection to
41  * a pop server running on $MAILHOST (environment variable).  Movemail
42  * must be setuid to root in order to work with POP.
43  *
44  * New module: popmail.c
45  * Modified routines:
46  *	main - added code within #ifdef MAIL_USE_POP; added setuid (getuid ())
47  *		after POP code.
48  * New routines in movemail.c:
49  *	get_errmsg - return pointer to system error message
50  *
51  * Modified August, 1993 by Jonathan Kamens (OpenVision Technologies)
52  *
53  * Move all of the POP code into a separate file, "pop.c".
54  * Use strerror instead of get_errmsg.
55  *
56  */
57 
58 #include <config.h>
59 #include <sys/types.h>
60 #include <sys/stat.h>
61 #include <sys/file.h>
62 #include <stdlib.h>
63 #include <errno.h>
64 #include <time.h>
65 
66 #include <getopt.h>
67 #include <unistd.h>
68 #include <fcntl.h>
69 #include <signal.h>
70 #include <string.h>
71 
72 #include <unlocked-io.h>
73 
74 #include "syswait.h"
75 #ifdef MAIL_USE_POP
76 #include "pop.h"
77 #endif
78 
79 #ifdef MSDOS
80 #undef access
81 #endif /* MSDOS */
82 
83 #ifdef WINDOWSNT
84 #include "ntlib.h"
85 #undef access
86 #undef unlink
87 #define fork() 0
88 #define waitpid(child, var, flags) (*(var) = 0)
89 /* Unfortunately, Samba doesn't seem to properly lock Unix files even
90    though the locking call succeeds (and indeed blocks local access from
91    other NT programs).  If you have direct file access using an NFS
92    client or something other than Samba, the locking call might work
93    properly - make sure it does before you enable this!
94 
95    [18-Feb-97 andrewi] I now believe my comment above to be incorrect,
96    since it was based on a misunderstanding of how locking calls are
97    implemented and used on Unix.  */
98 //#define DISABLE_DIRECT_ACCESS
99 
100 #include <fcntl.h>
101 #endif /* WINDOWSNT */
102 
103 #ifdef WINDOWSNT
104 #include <sys/locking.h>
105 #endif
106 
107 /* If your system uses the `flock' or `lockf' system call for mail locking,
108    define MAIL_USE_SYSTEM_LOCK.  If your system type should always define
109    MAIL_USE_LOCKF or MAIL_USE_FLOCK but configure does not do this,
110    please make a bug report.  */
111 
112 #ifdef MAIL_USE_LOCKF
113 #define MAIL_USE_SYSTEM_LOCK
114 #endif
115 
116 #ifdef MAIL_USE_FLOCK
117 #define MAIL_USE_SYSTEM_LOCK
118 #endif
119 
120 #if (!defined MAIL_USE_SYSTEM_LOCK				\
121      && (defined HAVE_LIBMAIL || defined HAVE_LIBLOCKFILE)	\
122      && defined HAVE_MAILLOCK_H)
123 #include <maillock.h>
124 /* We can't use maillock unless we know what directory system mail
125    files appear in. */
126 #ifdef MAILDIR
127 #define MAIL_USE_MAILLOCK
128 static char *mail_spool_name (char *);
129 #endif
130 #endif
131 
132 static _Noreturn void fatal (const char *s1, const char *s2, const char *s3);
133 static void error (const char *s1, const char *s2, const char *s3);
134 static _Noreturn void pfatal_with_name (char *name);
135 static _Noreturn void pfatal_and_delete (char *name);
136 #ifdef MAIL_USE_POP
137 static int popmail (char *, char *, bool, char *, bool);
138 static bool pop_retr (popserver, int, FILE *);
139 static bool mbx_write (char *, int, FILE *);
140 static bool mbx_delimit_begin (FILE *);
141 static bool mbx_delimit_end (FILE *);
142 #endif
143 
144 #if (defined MAIL_USE_MAILLOCK						\
145      || (!defined DISABLE_DIRECT_ACCESS && !defined MAIL_USE_SYSTEM_LOCK))
146 /* Like malloc but get fatal error if memory is exhausted.  */
147 
148 static void * ATTRIBUTE_MALLOC
xmalloc(size_t size)149 xmalloc (size_t size)
150 {
151   void *result = malloc (size);
152   if (!result)
153     fatal ("virtual memory exhausted", 0, 0);
154   return result;
155 }
156 #endif
157 
158 /* Nonzero means this is name of a lock file to delete on fatal error.  */
159 static char *delete_lockname;
160 
161 int
main(int argc,char ** argv)162 main (int argc, char **argv)
163 {
164   char *inname, *outname;
165   int indesc, outdesc;
166   ssize_t nread;
167   int wait_status;
168   int c;
169   bool preserve_mail = false;
170 
171 #ifndef MAIL_USE_SYSTEM_LOCK
172   struct stat st;
173   int tem;
174   char *tempname;
175   size_t inname_len, inname_dirlen;
176   int desc;
177 #endif /* not MAIL_USE_SYSTEM_LOCK */
178 
179 #ifdef MAIL_USE_POP
180   bool pop_reverse_order = false;
181 # define ARGSTR "pr"
182 #else /* ! MAIL_USE_POP */
183 # define ARGSTR "p"
184 #endif /* MAIL_USE_POP */
185 
186   uid_t real_gid = getgid ();
187   uid_t priv_gid = getegid ();
188 
189   delete_lockname = 0;
190 
191   while (0 <= (c = getopt (argc, argv, ARGSTR)))
192     {
193       switch (c) {
194 #ifdef MAIL_USE_POP
195       case 'r':
196 	pop_reverse_order = true;
197 	break;
198 #endif
199       case 'p':
200 	preserve_mail = true;
201 	break;
202       default:
203 	return EXIT_FAILURE;
204       }
205     }
206 
207   if (
208 #ifdef MAIL_USE_POP
209       (argc - optind < 2) || (argc - optind > 3)
210 #else
211       (argc - optind != 2)
212 #endif
213       )
214     {
215 #ifdef MAIL_USE_POP
216       fprintf (stderr, "Usage: movemail [-p] [-r] inbox destfile%s\n",
217 	       " [POP-password]");
218 #else
219       fprintf (stderr, "Usage: movemail [-p] inbox destfile%s\n", "");
220 #endif
221       return EXIT_FAILURE;
222     }
223 
224   inname = argv[optind];
225   outname = argv[optind+1];
226 
227   if (*outname == 0)
228     fatal ("Destination file name is empty", 0, 0);
229 
230 #ifdef MAIL_USE_POP
231   if (!strncmp (inname, "po:", 3))
232     {
233       int status;
234 
235       status = popmail (inname + 3, outname, preserve_mail,
236 			(argc - optind == 3) ? argv[optind+2] : NULL,
237 			pop_reverse_order);
238       return status;
239     }
240 
241   if (setuid (getuid ()) < 0)
242     fatal ("Failed to drop privileges", 0, 0);
243 
244 #endif /* MAIL_USE_POP */
245 
246 #ifndef DISABLE_DIRECT_ACCESS
247 
248   char *lockname = 0;
249   char *spool_name = 0;
250 
251 #ifdef MAIL_USE_MAILLOCK
252   spool_name = mail_spool_name (inname);
253 #endif
254   if (! spool_name)
255     {
256 #ifndef MAIL_USE_SYSTEM_LOCK
257       /* Use a lock file named after our first argument with .lock appended:
258 	 If it exists, the mail file is locked.  */
259       /* Note: this locking mechanism is *required* by the mailer
260 	 (on systems which use it) to prevent loss of mail.
261 
262 	 On systems that use a lock file, extracting the mail without locking
263 	 WILL occasionally cause loss of mail due to timing errors!
264 
265 	 So, if creation of the lock file fails due to access
266 	 permission on the mail spool directory, you simply MUST
267 	 change the permission and/or make movemail a setgid program
268 	 so it can create lock files properly.
269 
270 	 You might also wish to verify that your system is one which
271 	 uses lock files for this purpose.  Some systems use other methods.  */
272 
273       inname_len = strlen (inname);
274       lockname = xmalloc (inname_len + sizeof ".lock");
275       strcpy (lockname, inname);
276       strcpy (lockname + inname_len, ".lock");
277       for (inname_dirlen = inname_len;
278 	   inname_dirlen && !IS_DIRECTORY_SEP (inname[inname_dirlen - 1]);
279 	   inname_dirlen--)
280 	continue;
281       tempname = xmalloc (inname_dirlen + sizeof "EXXXXXX");
282 
283       while (true)
284 	{
285 	  /* Create the lock file, but not under the lock file name.  */
286 	  /* Give up if cannot do that.  */
287 
288 	  memcpy (tempname, inname, inname_dirlen);
289 	  strcpy (tempname + inname_dirlen, "EXXXXXX");
290 	  desc = mkostemp (tempname, O_BINARY);
291 	  if (desc < 0)
292 	    {
293 	      int mkostemp_errno = errno;
294 	      error ("error while creating what would become the lock file",
295 		     0, 0);
296 	      errno = mkostemp_errno;
297 	      pfatal_with_name (tempname);
298 	    }
299 	  close (desc);
300 
301 	  tem = link (tempname, lockname);
302 
303 	  if (tem < 0 && errno != EEXIST)
304 	    pfatal_with_name (lockname);
305 
306 	  unlink (tempname);
307 	  if (tem >= 0)
308 	    break;
309 	  sleep (1);
310 
311 	  /* If lock file is five minutes old, unlock it.
312 	     Five minutes should be good enough to cope with crashes
313 	     and wedgitude, and long enough to avoid being fooled
314 	     by time differences between machines.  */
315 	  if (stat (lockname, &st) >= 0)
316 	    {
317 	      time_t now = time (0);
318 	      if (st.st_ctime < now - 300)
319 		{
320 		  unlink (lockname);
321 		  lockname = 0;
322 		}
323 	    }
324 	}
325 
326       delete_lockname = lockname;
327 #endif /* not MAIL_USE_SYSTEM_LOCK */
328     }
329 
330 #ifdef SIGCHLD
331   signal (SIGCHLD, SIG_DFL);
332 #endif
333 
334   pid_t child = fork ();
335   if (child < 0)
336     fatal ("Error in fork; %s", strerror (errno), 0);
337 
338   if (child == 0)
339     {
340       int lockcount = 0;
341       int status = 0;
342 #if defined (MAIL_USE_MAILLOCK) && defined (HAVE_TOUCHLOCK)
343       time_t touched_lock;
344 #endif
345 
346       if (setuid (getuid ()) < 0 || setregid (-1, real_gid) < 0)
347 	fatal ("Failed to drop privileges", 0, 0);
348 
349 #ifdef MAIL_USE_SYSTEM_LOCK
350       indesc = open (inname, O_RDWR | O_BINARY);
351 #else  /* if not MAIL_USE_SYSTEM_LOCK */
352       indesc = open (inname, O_RDONLY | O_BINARY);
353 #endif /* not MAIL_USE_SYSTEM_LOCK */
354 
355       if (indesc < 0)
356 	pfatal_with_name (inname);
357 
358       /* Make sure the user can read the output file.  */
359       umask (umask (0) & 0377);
360 
361       outdesc = open (outname, O_WRONLY | O_BINARY | O_CREAT | O_EXCL, 0666);
362       if (outdesc < 0)
363 	pfatal_with_name (outname);
364 
365       if (setregid (-1, priv_gid) < 0)
366 	fatal ("Failed to regain privileges", 0, 0);
367 
368       /* This label exists so we can retry locking
369 	 after a delay, if it got EAGAIN or EBUSY.  */
370     retry_lock:
371 
372       /* Try to lock it.  */
373 #ifdef MAIL_USE_MAILLOCK
374       if (spool_name)
375 	{
376 	  /* The "-" is to make it a negative number if maillock returns
377 	     non-zero. */
378 	  status = - maillock (spool_name, 1);
379 #ifdef HAVE_TOUCHLOCK
380 	  touched_lock = time (0);
381 #endif
382 	  lockcount = 5;
383 	}
384       else
385 #endif /* MAIL_USE_MAILLOCK */
386 	{
387 #ifdef MAIL_USE_SYSTEM_LOCK
388 #ifdef MAIL_USE_LOCKF
389 	  status = lockf (indesc, F_LOCK, 0);
390 #else /* not MAIL_USE_LOCKF */
391 #ifdef WINDOWSNT
392 	  status = locking (indesc, LK_RLCK, -1L);
393 #else
394 	  status = flock (indesc, LOCK_EX);
395 #endif
396 #endif /* not MAIL_USE_LOCKF */
397 #endif /* MAIL_USE_SYSTEM_LOCK */
398 	}
399 
400       /* If it fails, retry up to 5 times
401 	 for certain failure codes.  */
402       if (status < 0)
403 	{
404 	  if (++lockcount <= 5 && (errno == EAGAIN || errno == EBUSY))
405 	    {
406 	      sleep (1);
407 	      goto retry_lock;
408 	    }
409 
410 	  pfatal_with_name (inname);
411 	}
412 
413       {
414 	char buf[1024];
415 
416 	while (true)
417 	  {
418 	    nread = read (indesc, buf, sizeof buf);
419 	    if (nread < 0)
420 	      pfatal_with_name (inname);
421 	    if (nread != write (outdesc, buf, nread))
422 	      {
423 		int saved_errno = errno;
424 		unlink (outname);
425 		errno = saved_errno;
426 		pfatal_with_name (outname);
427 	      }
428 	    if (nread < sizeof buf)
429 	      break;
430 #if defined (MAIL_USE_MAILLOCK) && defined (HAVE_TOUCHLOCK)
431 	    if (spool_name)
432 	      {
433 		time_t now = time (0);
434 		if (now - touched_lock > 60)
435 		  {
436 		    touchlock ();
437 		    touched_lock = now;
438 		  }
439 	      }
440 #endif /* MAIL_USE_MAILLOCK */
441 	  }
442       }
443 
444       if (fsync (outdesc) != 0 && errno != EINVAL)
445 	pfatal_and_delete (outname);
446 
447       /* Prevent symlink attacks truncating other users' mailboxes */
448       if (setregid (-1, real_gid) < 0)
449 	fatal ("Failed to drop privileges", 0, 0);
450 
451       /* Check to make sure no errors before we zap the inbox.  */
452       if (close (outdesc) != 0)
453 	pfatal_and_delete (outname);
454 
455 #ifdef MAIL_USE_SYSTEM_LOCK
456       if (! preserve_mail)
457 	{
458 	  if (ftruncate (indesc, 0) != 0)
459 	    pfatal_with_name (inname);
460 	}
461 #endif /* MAIL_USE_SYSTEM_LOCK */
462 
463       close (indesc);
464 
465 #ifndef MAIL_USE_SYSTEM_LOCK
466       if (! preserve_mail)
467 	{
468 	  /* Delete the input file; if we can't, at least get rid of its
469 	     contents.  */
470 #ifdef MAIL_UNLINK_SPOOL
471 	  /* This is generally bad to do, because it destroys the permissions
472 	     that were set on the file.  Better to just empty the file.  */
473 	  if (unlink (inname) < 0 && errno != ENOENT)
474 #endif /* MAIL_UNLINK_SPOOL */
475 	    creat (inname, 0600);
476 	}
477 #endif /* not MAIL_USE_SYSTEM_LOCK */
478 
479       /* End of mailbox truncation */
480       if (setregid (-1, priv_gid) < 0)
481 	fatal ("Failed to regain privileges", 0, 0);
482 
483 #ifdef MAIL_USE_MAILLOCK
484       /* This has to occur in the child, i.e., in the process that
485          acquired the lock! */
486       if (spool_name)
487 	mailunlock ();
488 #endif
489       return EXIT_SUCCESS;
490     }
491 
492   if (waitpid (child, &wait_status, 0) < 0)
493     fatal ("Error in waitpid; %s", strerror (errno), 0);
494   if (!WIFEXITED (wait_status))
495     return EXIT_FAILURE;
496   else if (WEXITSTATUS (wait_status) != 0)
497     return WEXITSTATUS (wait_status);
498 
499   if (lockname)
500     unlink (lockname);
501 
502 #endif /* ! DISABLE_DIRECT_ACCESS */
503 
504   return EXIT_SUCCESS;
505 }
506 
507 #ifdef MAIL_USE_MAILLOCK
508 /* This function uses stat to confirm that the mail directory is
509    identical to the directory of the input file, rather than just
510    string-comparing the two paths, because one or both of them might
511    be symbolic links pointing to some other directory. */
512 static char *
mail_spool_name(char * inname)513 mail_spool_name (char *inname)
514 {
515   struct stat stat1, stat2;
516   char *indir, *fname;
517   int status;
518 
519   if (! (fname = strrchr (inname, '/')))
520     return NULL;
521 
522   fname++;
523 
524   if (stat (MAILDIR, &stat1) < 0)
525     return NULL;
526 
527   indir = xmalloc (fname - inname + 1);
528   memcpy (indir, inname, fname - inname);
529   indir[fname-inname] = '\0';
530 
531 
532   status = stat (indir, &stat2);
533 
534   free (indir);
535 
536   if (status < 0)
537     return NULL;
538 
539   if (stat1.st_dev == stat2.st_dev
540       && stat1.st_ino == stat2.st_ino)
541     return fname;
542 
543   return NULL;
544 }
545 #endif /* MAIL_USE_MAILLOCK */
546 
547 /* Print error message and exit.  */
548 
549 static void
fatal(const char * s1,const char * s2,const char * s3)550 fatal (const char *s1, const char *s2, const char *s3)
551 {
552   if (delete_lockname)
553     unlink (delete_lockname);
554   error (s1, s2, s3);
555   exit (EXIT_FAILURE);
556 }
557 
558 /* Print error message.  `s1' is printf control string, `s2' and `s3'
559    are args for it or null. */
560 
561 static void
error(const char * s1,const char * s2,const char * s3)562 error (const char *s1, const char *s2, const char *s3)
563 {
564   fprintf (stderr, "movemail: ");
565   if (s3)
566     fprintf (stderr, s1, s2, s3);
567   else if (s2)
568     fprintf (stderr, s1, s2);
569   else
570     fprintf (stderr, "%s", s1);
571   fprintf (stderr, "\n");
572 }
573 
574 static void
pfatal_with_name(char * name)575 pfatal_with_name (char *name)
576 {
577   fatal ("%s for %s", strerror (errno), name);
578 }
579 
580 static void
pfatal_and_delete(char * name)581 pfatal_and_delete (char *name)
582 {
583   char *s = strerror (errno);
584   unlink (name);
585   fatal ("%s for %s", s, name);
586 }
587 
588 /* This is the guts of the interface to the Post Office Protocol.  */
589 
590 #ifdef MAIL_USE_POP
591 
592 #ifndef WINDOWSNT
593 #include <sys/socket.h>
594 #include <netinet/in.h>
595 #include <netdb.h>
596 #else
597 #undef _WINSOCKAPI_
598 #include <winsock.h>
599 #endif
600 #include <pwd.h>
601 #include <string.h>
602 
603 /*
604  * The full valid syntax for a POP mailbox specification for movemail
605  * is "po:username:hostname".  The ":hostname" is optional; if it is
606  * omitted, the MAILHOST environment variable will be consulted.  Note
607  * that by the time popmail() is called the "po:" has been stripped
608  * off of the front of the mailbox name.
609  *
610  * If the mailbox is in the form "po:username:hostname", then it is
611  * modified by this function -- the second colon is replaced by a
612  * null.
613  *
614  * Return a value suitable for passing to `exit'.
615  */
616 
617 static int
popmail(char * mailbox,char * outfile,bool preserve,char * password,bool reverse_order)618 popmail (char *mailbox, char *outfile, bool preserve, char *password,
619 	 bool reverse_order)
620 {
621   int nmsgs, nbytes;
622   int i;
623   int mbfi;
624   FILE *mbf;
625   popserver server;
626   int start, end, increment;
627   char *user, *hostname;
628 
629   user = mailbox;
630   if ((hostname = strchr (mailbox, ':')))
631     *hostname++ = '\0';
632 
633   server = pop_open (hostname, user, password, POP_NO_GETPASS);
634   if (! server)
635     {
636       error ("Error connecting to POP server: %s", pop_error, 0);
637       return EXIT_FAILURE;
638     }
639 
640   if (pop_stat (server, &nmsgs, &nbytes))
641     {
642       error ("Error getting message count from POP server: %s", pop_error, 0);
643       return EXIT_FAILURE;
644     }
645 
646   if (!nmsgs)
647     {
648       pop_close (server);
649       return EXIT_SUCCESS;
650     }
651 
652   mbfi = open (outfile, O_WRONLY | O_BINARY | O_CREAT | O_EXCL, 0666);
653   if (mbfi < 0)
654     {
655       pop_close (server);
656       error ("Error in open: %s, %s", strerror (errno), outfile);
657       return EXIT_FAILURE;
658     }
659 
660   if (fchown (mbfi, getuid (), -1) != 0)
661     {
662       int fchown_errno = errno;
663       struct stat st;
664       if (fstat (mbfi, &st) != 0 || st.st_uid != getuid ())
665 	{
666 	  pop_close (server);
667 	  error ("Error in fchown: %s, %s", strerror (fchown_errno), outfile);
668 	  return EXIT_FAILURE;
669 	}
670     }
671 
672   mbf = fdopen (mbfi, "wb");
673   if (!mbf)
674     {
675       pop_close (server);
676       error ("Error in fdopen: %s", strerror (errno), 0);
677       close (mbfi);
678       unlink (outfile);
679       return EXIT_FAILURE;
680     }
681 
682   if (reverse_order)
683     {
684       start = nmsgs;
685       end = 1;
686       increment = -1;
687     }
688   else
689     {
690       start = 1;
691       end = nmsgs;
692       increment = 1;
693     }
694 
695   for (i = start; i * increment <= end * increment; i += increment)
696     if (! (mbx_delimit_begin (mbf)
697 	   && pop_retr (server, i, mbf)
698 	   && mbx_delimit_end (mbf)
699 	   && fflush (mbf) == 0))
700       {
701 	if (errno)
702 	  error ("Error in POP retrieving: %s", strerror (errno), 0);
703 	pop_close (server);
704 	fclose (mbf);
705 	return EXIT_FAILURE;
706       }
707 
708   if (fsync (mbfi) != 0 && errno != EINVAL)
709     {
710       error ("Error in fsync: %s", strerror (errno), 0);
711       fclose (mbf);
712       return EXIT_FAILURE;
713     }
714 
715   if (fclose (mbf) != 0)
716     {
717       error ("Error in fclose: %s", strerror (errno), 0);
718       return EXIT_FAILURE;
719     }
720 
721   if (! preserve)
722     for (i = 1; i <= nmsgs; i++)
723       {
724 	if (pop_delete (server, i))
725 	  {
726 	    error ("Error from POP server: %s", pop_error, 0);
727 	    pop_close (server);
728 	    return EXIT_FAILURE;
729 	  }
730       }
731 
732   if (pop_quit (server))
733     {
734       error ("Error from POP server: %s", pop_error, 0);
735       return EXIT_FAILURE;
736     }
737 
738   return EXIT_SUCCESS;
739 }
740 
741 static bool
pop_retr(popserver server,int msgno,FILE * arg)742 pop_retr (popserver server, int msgno, FILE *arg)
743 {
744   char *line;
745   int ret;
746 
747   if (pop_retrieve_first (server, msgno, &line))
748     {
749       error ("Error from POP server: %s", pop_error, 0);
750       errno = 0;
751       return false;
752     }
753 
754   while ((ret = pop_retrieve_next (server, &line)) >= 0)
755     {
756       if (! line)
757 	break;
758 
759       if (! mbx_write (line, ret, arg))
760 	{
761 	  int write_errno = errno;
762 	  pop_close (server);
763 	  errno = write_errno;
764 	  return false;
765 	}
766     }
767 
768   if (ret)
769     {
770       error ("Error from POP server: %s", pop_error, 0);
771       errno = 0;
772       return false;
773     }
774 
775   return true;
776 }
777 
778 static bool
mbx_write(char * line,int len,FILE * mbf)779 mbx_write (char *line, int len, FILE *mbf)
780 {
781 #ifdef MOVEMAIL_QUOTE_POP_FROM_LINES
782   /* Do this as a macro instead of using strcmp to save on execution time. */
783   # define IS_FROM_LINE(a) ((a[0] == 'F')	\
784 			    && (a[1] == 'r')	\
785 			    && (a[2] == 'o')	\
786 			    && (a[3] == 'm')	\
787 			    && (a[4] == ' '))
788   if (IS_FROM_LINE (line))
789     {
790       if (fputc ('>', mbf) < 0)
791 	return false;
792     }
793 #endif
794   if (line[0] == '\037')
795     {
796       if (fputs ("^_", mbf) < 0)
797 	return false;
798       line++;
799       len--;
800     }
801   return fwrite (line, 1, len, mbf) == len && 0 <= fputc ('\n', mbf);
802 }
803 
804 #ifdef WINDOWSNT
805 /* Work around MS-Windows lack of support for %e or %T with a
806    special-purpose strftime that assumes the exact format that
807    movemail uses.  */
808 static size_t
movemail_strftime(char * s,size_t size,char const * format,struct tm const * tm)809 movemail_strftime (char *s, size_t size, char const *format,
810 		   struct tm const *tm)
811 {
812   char fmt[size + 6], *q;
813   const char *p;
814 
815   for (p = format, q = &fmt[0]; *p; )
816     {
817       if (*p == '%' && p[1] == 'e')
818 	{
819 	  memcpy (q, "%d", 2);
820 	  q += 2;
821 	  p += 2;
822 	}
823       else if (*p == '%' && p[1] == 'T')
824 	{
825 	  memcpy (q, "%H:%M:%S", 8);
826 	  q += 8;
827 	  p += 2;
828 	}
829       else if (*p == '%' && p[1] == '%')
830 	{
831 	  memcpy (q, p, 2);
832 	  q += 2;
833 	  p += 2;
834 	}
835       else
836 	*q++ = *p++;
837     }
838 
839   size_t n = strftime (s, size, fmt, tm);
840   char *mday = s + sizeof "From movemail Sun Jan " - 1;
841   if (*mday == '0')
842     *mday = ' ';
843   return n;
844 }
845 # undef strftime
846 # define strftime movemail_strftime
847 #endif
848 
849 static bool
mbx_delimit_begin(FILE * mbf)850 mbx_delimit_begin (FILE *mbf)
851 {
852   time_t now = time (NULL);
853   struct tm *ltime = localtime (&now);
854   if (!ltime)
855     return false;
856 
857   char fromline[100];
858   if (! strftime (fromline, sizeof fromline,
859 		  "From movemail %a %b %e %T %Y\n", ltime))
860     {
861       errno = EOVERFLOW;
862       return false;
863     }
864   return 0 <= fputs (fromline, mbf);
865 }
866 
867 static bool
mbx_delimit_end(FILE * mbf)868 mbx_delimit_end (FILE *mbf)
869 {
870   return 0 <= putc ('\n', mbf);
871 }
872 
873 #endif /* MAIL_USE_POP */
874