XEmacs 21.2-b1
[chise/xemacs-chise.git.1] / lib-src / movemail.c
1 /* movemail foo bar -- move file foo to file bar,
2    locking file foo the way /bin/mail respects.
3    Copyright (C) 1986, 1992, 1993, 1994, 1996 Free Software Foundation, Inc.
4
5 This file is part of GNU Emacs.
6
7 GNU Emacs is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2, or (at your option)
10 any later version.
11
12 GNU Emacs is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with GNU Emacs; see the file COPYING.  If not, write to
19 the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 Boston, MA 02111-1307, USA.  */
21
22 /* Important notice: defining MAIL_USE_FLOCK or MAIL_USE_LOCKF *will
23    cause loss of mail* if you do it on a system that does not normally
24    use flock as its way of interlocking access to inbox files.  The
25    setting of MAIL_USE_FLOCK and MAIL_USE_LOCKF *must agree* with the
26    system's own conventions.  It is not a choice that is up to you.
27
28    So, if your system uses lock files rather than flock, then the only way
29    you can get proper operation is to enable movemail to write lockfiles there.
30    This means you must either give that directory access modes
31    that permit everyone to write lockfiles in it, or you must make movemail
32    a setuid or setgid program.  */
33
34 /*
35  * Modified January, 1986 by Michael R. Gretzinger (Project Athena)
36  *
37  * Added POP (Post Office Protocol) service.  When compiled -DMAIL_USE_POP
38  * movemail will accept input filename arguments of the form
39  * "po:username".  This will cause movemail to open a connection to
40  * a pop server running on $MAILHOST (environment variable).  Movemail
41  * must be setuid to root in order to work with POP.
42  * 
43  * New module: popmail.c
44  * Modified routines:
45  *      main - added code within #ifdef MAIL_USE_POP; added setuid (getuid ())
46  *              after POP code. 
47  * New routines in movemail.c:
48  *      get_errmsg - return pointer to system error message
49  *
50  * Modified August, 1993 by Jonathan Kamens (OpenVision Technologies)
51  *
52  * Move all of the POP code into a separate file, "pop.c".
53  * Use strerror instead of get_errmsg.
54  *
55  */
56
57 #define NO_SHORTNAMES   /* Tell config not to load remap.h */
58 #include <../src/config.h>
59 #include <sys/types.h>
60 #include <sys/stat.h>
61 #include <sys/file.h>
62 #include <stdio.h>
63 #include <errno.h>
64 #include "../src/syswait.h"
65 #include "../src/systime.h"
66 #include <stdlib.h>
67 #include <string.h>
68 #ifdef MAIL_USE_POP
69 #include "pop.h"
70 #endif
71
72 #ifndef HAVE_STRERROR
73 static char * strerror (int errnum);
74 #endif /* HAVE_STRERROR */
75
76 #ifdef MSDOS
77 #undef access
78 #endif /* MSDOS */
79
80 #ifndef DIRECTORY_SEP
81 #define DIRECTORY_SEP '/'
82 #endif
83 #ifndef IS_DIRECTORY_SEP
84 #define IS_DIRECTORY_SEP(_c_) ((_c_) == DIRECTORY_SEP)
85 #endif
86
87 #ifdef WINDOWSNT
88 #undef access
89 #undef unlink
90 #define fork() 0
91 #define sys_wait(var) (*(var) = 0)
92 /* Unfortunately, Samba doesn't seem to properly lock Unix files even
93    though the locking call succeeds (and indeed blocks local access from
94    other NT programs).  If you have direct file access using an NFS
95    client or something other than Samba, the locking call might work
96    properly - make sure it does before you enable this! */
97 #define DISABLE_DIRECT_ACCESS
98 #include <io.h>
99 #endif /* WINDOWSNT */
100
101 #if defined (HAVE_UNISTD_H) || defined (USG)
102 #include <unistd.h>
103 #endif /* unistd.h */
104 #ifndef F_OK
105 #define F_OK 0
106 #define X_OK 1
107 #define W_OK 2
108 #define R_OK 4
109 #endif /* No F_OK */
110
111 #if defined (HAVE_FCNTL_H) || defined (USG)
112 #include <fcntl.h>
113 #endif /* fcntl.h */
114
115 #if defined (XENIX) || defined (WINDOWSNT)
116 #include <sys/locking.h>
117 #endif
118
119 #ifdef MAIL_USE_LOCKF
120 #define MAIL_USE_SYSTEM_LOCK
121 #endif
122
123 #ifdef MAIL_USE_FLOCK
124 #define MAIL_USE_SYSTEM_LOCK
125 #endif
126
127 #ifdef MAIL_USE_MMDF
128 extern int lk_open (), lk_close ();
129 #endif
130
131 /* Cancel substitutions made by config.h for Emacs.  */
132 #undef open
133 #undef read
134 #undef write
135 #undef close
136
137 static void fatal (char *, char*);
138 static void error (char *, char *, char *);
139 static void pfatal_with_name (char *);
140 static void pfatal_and_delete (char *);
141 static char *concat (char *, char *, char *);
142 static long *xmalloc (unsigned int);
143 #ifdef MAIL_USE_POP
144 static int popmail (char *, char *, char *);
145 static int pop_retr (popserver server, int msgno, int (*action)(), void *arg);
146 static int mbx_write (char *, FILE *);
147 static int mbx_delimit_begin (FILE *);
148 static int mbx_delimit_end (FILE *);
149 #endif
150
151 /* Nonzero means this is name of a lock file to delete on fatal error.  */
152 char *delete_lockname;
153
154 int
155 main (int argc, char *argv[])
156 {
157   char *inname, *outname;
158 #ifndef DISABLE_DIRECT_ACCESS
159   int indesc, outdesc;
160   int nread;
161   int status;
162 #endif
163
164 #ifndef MAIL_USE_SYSTEM_LOCK
165   struct stat st;
166   long now;
167   int tem;
168   char *lockname, *p;
169   char *tempname;
170   int desc;
171 #endif /* not MAIL_USE_SYSTEM_LOCK */
172
173   delete_lockname = 0;
174
175   if (argc < 3)
176     {
177       fprintf (stderr, "Usage: movemail inbox destfile [POP-password]\n");
178       exit(1);
179     }
180
181   inname = argv[1];
182   outname = argv[2];
183
184 #ifdef MAIL_USE_MMDF
185   mmdf_init (argv[0]);
186 #endif
187
188   if (*outname == 0)
189     fatal ("Destination file name is empty", 0);
190
191   /* Check access to output file.  */
192   if (access (outname, F_OK) == 0 && access (outname, W_OK) != 0)
193     pfatal_with_name (outname);
194
195   /* Also check that outname's directory is writable to the real uid.  */
196   {
197     char *buf = (char *) xmalloc (strlen (outname) + 1);
198     char *cp;
199     strcpy (buf, outname);
200     cp = buf + strlen (buf);
201     while (cp > buf && !IS_DIRECTORY_SEP (cp[-1]))
202       *--cp = 0;
203     if (cp == buf)
204       *cp++ = '.';
205     if (access (buf, W_OK) != 0)
206       pfatal_with_name (buf);
207     free (buf);
208   }
209
210 #ifdef MAIL_USE_POP
211   if (!strncmp (inname, "po:", 3))
212     {
213       int retcode = popmail (inname + 3, outname, argc > 3 ? argv[3] : NULL);
214       exit (retcode);
215     }
216
217   setuid (getuid ());
218 #endif /* MAIL_USE_POP */
219
220 #ifndef DISABLE_DIRECT_ACCESS
221
222   /* Check access to input file.  */
223   if (access (inname, R_OK | W_OK) != 0)
224     pfatal_with_name (inname);
225
226 #ifndef MAIL_USE_MMDF
227 #ifndef MAIL_USE_SYSTEM_LOCK
228   /* Use a lock file named after our first argument with .lock appended:
229      If it exists, the mail file is locked.  */
230   /* Note: this locking mechanism is *required* by the mailer
231      (on systems which use it) to prevent loss of mail.
232
233      On systems that use a lock file, extracting the mail without locking
234      WILL occasionally cause loss of mail due to timing errors!
235
236      So, if creation of the lock file fails
237      due to access permission on the mail spool directory,
238      you simply MUST change the permission
239      and/or make movemail a setgid program
240      so it can create lock files properly.
241
242      You might also wish to verify that your system is one
243      which uses lock files for this purpose.  Some systems use other methods.
244
245      If your system uses the `flock' system call for mail locking,
246      define MAIL_USE_SYSTEM_LOCK in config.h or the s-*.h file
247      and recompile movemail.  If the s- file for your system
248      should define MAIL_USE_SYSTEM_LOCK but does not, send a bug report
249      to bug-gnu-emacs@prep.ai.mit.edu so we can fix it.  */
250
251   lockname = concat (inname, ".lock", "");
252   tempname = (char *) xmalloc (strlen (inname) + strlen ("EXXXXXX") + 1);
253   strcpy (tempname, inname);
254   p = tempname + strlen (tempname);
255   while (p != tempname && !IS_DIRECTORY_SEP (p[-1]))
256     p--;
257   *p = 0;
258   strcpy (p, "EXXXXXX");
259   mktemp (tempname);
260   unlink (tempname);
261
262   while (1)
263     {
264       /* Create the lock file, but not under the lock file name.  */
265       /* Give up if cannot do that.  */
266       desc = open (tempname, O_WRONLY | O_CREAT | O_EXCL, 0666);
267       if (desc < 0)
268         {
269           char *message = (char *) xmalloc (strlen (tempname) + 50);
270           sprintf (message, "%s--see source file lib-src/movemail.c",
271                    tempname);
272           pfatal_with_name (message);
273         }
274       close (desc);
275
276       tem = link (tempname, lockname);
277       unlink (tempname);
278       if (tem >= 0)
279         break;
280       sleep (1);
281
282       /* If lock file is five minutes old, unlock it.
283          Five minutes should be good enough to cope with crashes
284          and wedgitude, and long enough to avoid being fooled
285          by time differences between machines.  */
286       if (stat (lockname, &st) >= 0)
287         {
288           now = time (0);
289           if (st.st_ctime < now - 300)
290             unlink (lockname);
291         }
292     }
293
294   delete_lockname = lockname;
295 #endif /* not MAIL_USE_SYSTEM_LOCK */
296 #endif /* not MAIL_USE_MMDF */
297
298   if (fork () == 0)
299     {
300       setuid (getuid ());
301
302 #ifndef MAIL_USE_MMDF
303 #ifdef MAIL_USE_SYSTEM_LOCK
304       indesc = open (inname, O_RDWR);
305 #else  /* if not MAIL_USE_SYSTEM_LOCK */
306       indesc = open (inname, O_RDONLY);
307 #endif /* not MAIL_USE_SYSTEM_LOCK */
308 #else  /* MAIL_USE_MMDF */
309       indesc = lk_open (inname, O_RDONLY, 0, 0, 10);
310 #endif /* MAIL_USE_MMDF */
311
312       if (indesc < 0)
313         pfatal_with_name (inname);
314
315 #if defined (BSD) || defined (XENIX)
316       /* In case movemail is setuid to root, make sure the user can
317          read the output file.  */
318       /* This is desirable for all systems
319          but I don't want to assume all have the umask system call */
320       umask (umask (0) & 0333);
321 #endif /* BSD or Xenix */
322       outdesc = open (outname, O_WRONLY | O_CREAT | O_EXCL, 0666);
323       if (outdesc < 0)
324         pfatal_with_name (outname);
325 #ifdef MAIL_USE_SYSTEM_LOCK
326 #ifdef MAIL_USE_LOCKF
327       if (lockf (indesc, F_LOCK, 0) < 0) pfatal_with_name (inname);
328 #else /* not MAIL_USE_LOCKF */
329 #ifdef XENIX
330       if (locking (indesc, LK_RLCK, 0L) < 0) pfatal_with_name (inname);
331 #else
332 #ifdef WINDOWSNT
333       if (locking (indesc, LK_RLCK, -1L) < 0) pfatal_with_name (inname);
334 #else
335       if (flock (indesc, LOCK_EX) < 0) pfatal_with_name (inname);
336 #endif
337 #endif
338 #endif /* not MAIL_USE_LOCKF */
339 #endif /* MAIL_USE_SYSTEM_LOCK */
340
341       {
342         char buf[1024];
343
344         while (1)
345           {
346             nread = read (indesc, buf, sizeof buf);
347             if (nread != write (outdesc, buf, nread))
348               {
349                 int saved_errno = errno;
350                 unlink (outname);
351                 errno = saved_errno;
352                 pfatal_with_name (outname);
353               }
354             if (nread < sizeof buf)
355               break;
356           }
357       }
358
359 #ifdef BSD
360       if (fsync (outdesc) < 0)
361         pfatal_and_delete (outname);
362 #endif
363
364       /* Check to make sure no errors before we zap the inbox.  */
365       if (close (outdesc) != 0)
366         pfatal_and_delete (outname);
367
368 #ifdef MAIL_USE_SYSTEM_LOCK
369 #if defined (STRIDE) || defined (XENIX) || defined (WINDOWSNT)
370       /* Stride, xenix have file locking, but no ftruncate.  This mess will do. */
371       close (open (inname, O_CREAT | O_TRUNC | O_RDWR, 0666));
372 #else
373       ftruncate (indesc, 0L);
374 #endif /* STRIDE or XENIX */
375 #endif /* MAIL_USE_SYSTEM_LOCK */
376
377 #ifdef MAIL_USE_MMDF
378       lk_close (indesc, 0, 0, 0);
379 #else
380       close (indesc);
381 #endif
382
383 #ifndef MAIL_USE_SYSTEM_LOCK
384       /* Delete the input file; if we can't, at least get rid of its
385          contents.  */
386 #ifdef MAIL_UNLINK_SPOOL
387       /* This is generally bad to do, because it destroys the permissions
388          that were set on the file.  Better to just empty the file.  */
389       if (unlink (inname) < 0 && errno != ENOENT)
390 #endif /* MAIL_UNLINK_SPOOL */
391         creat (inname, 0600);
392 #endif /* not MAIL_USE_SYSTEM_LOCK */
393
394       exit (0);
395     }
396
397   wait (&status);
398   if (!WIFEXITED (status))
399     exit (1);
400   else if (WEXITSTATUS (status) != 0)
401     exit (WEXITSTATUS (status));
402
403 #if !defined (MAIL_USE_MMDF) && !defined (MAIL_USE_SYSTEM_LOCK)
404   unlink (lockname);
405 #endif /* not MAIL_USE_MMDF and not MAIL_USE_SYSTEM_LOCK */
406
407 #endif /* ! DISABLE_DIRECT_ACCESS */
408
409   return 0;
410 }
411 \f
412 /* Print error message and exit.  */
413
414 static void
415 fatal (char *s1, char *s2)
416 {
417   if (delete_lockname)
418     unlink (delete_lockname);
419   error (s1, s2, NULL);
420   exit (1);
421 }
422
423 /* Print error message.  `s1' is printf control string, `s2' is arg for it. */
424
425 static void
426 error (char *s1, char *s2, char *s3)
427 {
428   fprintf (stderr, "movemail: ");
429   fprintf (stderr, s1, s2, s3);
430   fprintf (stderr, "\n");
431 }
432
433 static void
434 pfatal_with_name (char *name)
435 {
436   char *s = concat ("", strerror (errno), " for %s");
437   fatal (s, name);
438 }
439
440 static void
441 pfatal_and_delete (char *name)
442 {
443   char *s = concat ("", strerror (errno), " for %s");
444   unlink (name);
445   fatal (s, name);
446 }
447
448 /* Return a newly-allocated string whose contents concatenate those of s1, s2, s3.  */
449
450 static char *
451 concat (char *s1, char *s2, char *s3)
452 {
453   int len1 = strlen (s1), len2 = strlen (s2), len3 = strlen (s3);
454   char *result = (char *) xmalloc (len1 + len2 + len3 + 1);
455
456   strcpy (result, s1);
457   strcpy (result + len1, s2);
458   strcpy (result + len1 + len2, s3);
459   *(result + len1 + len2 + len3) = 0;
460
461   return result;
462 }
463
464 /* Like malloc but get fatal error if memory is exhausted.  */
465
466 static long *
467 xmalloc (unsigned int size)
468 {
469   long *result = (long *) malloc (size);
470   if (!result)
471     fatal ("virtual memory exhausted", 0);
472   return result;
473 }
474 \f
475 /* This is the guts of the interface to the Post Office Protocol.  */
476
477 #ifdef MAIL_USE_POP
478
479 #ifndef WINDOWSNT
480 #include <sys/socket.h>
481 #include <netinet/in.h>
482 #include <netdb.h>
483 #else
484 #undef _WINSOCKAPI_
485 #include <winsock.h>
486 #endif
487 #include <stdio.h>
488 #include <pwd.h>
489
490 #define NOTOK (-1)
491 #define OK 0
492 #define DONE 1
493
494 char *progname;
495 FILE *sfi;
496 FILE *sfo;
497 char ibuffer[BUFSIZ];
498 char obuffer[BUFSIZ];
499 char Errmsg[80];
500
501 static int
502 popmail (char *user, char *outfile, char *password)
503 {
504   int nmsgs, nbytes;
505   register int i;
506   int mbfi;
507   FILE *mbf;
508   popserver server;
509
510   server = pop_open (0, user, password, POP_NO_GETPASS);
511   if (! server)
512     {
513       error (pop_error, NULL, NULL);
514       return (1);
515     }
516
517   if (pop_stat (server, &nmsgs, &nbytes))
518     {
519       error (pop_error, NULL, NULL);
520       return (1);
521     }
522
523   if (!nmsgs)
524     {
525       pop_close (server);
526       return (0);
527     }
528
529   mbfi = open (outfile, O_WRONLY | O_CREAT | O_EXCL, 0666);
530   if (mbfi < 0)
531     {
532       pop_close (server);
533       error ("Error in open: %s, %s", strerror (errno), outfile);
534       return (1);
535     }
536 #ifndef __CYGWIN32__
537   fchown (mbfi, getuid (), -1);
538 #endif
539
540   if ((mbf = fdopen (mbfi, "wb")) == NULL)
541     {
542       pop_close (server);
543       error ("Error in fdopen: %s", strerror (errno), NULL);
544       close (mbfi);
545       unlink (outfile);
546       return (1);
547     }
548
549   for (i = 1; i <= nmsgs; i++)
550     {
551       mbx_delimit_begin (mbf);
552       if (pop_retr (server, i, mbx_write, mbf) != OK)
553         {
554           error (Errmsg, NULL, NULL);
555           close (mbfi);
556           return (1);
557         }
558       mbx_delimit_end (mbf);
559       fflush (mbf);
560       if (ferror (mbf))
561         {
562           error ("Error in fflush: %s", strerror (errno), NULL);
563           pop_close (server);
564           close (mbfi);
565           return (1);
566         }
567     }
568
569   /* On AFS, a call to write only modifies the file in the local
570    *     workstation's AFS cache.  The changes are not written to the server
571    *      until a call to fsync or close is made.  Users with AFS home
572    *      directories have lost mail when over quota because these checks were
573    *      not made in previous versions of movemail. */
574
575 #ifdef BSD
576   if (fsync (mbfi) < 0)
577     {
578       error ("Error in fsync: %s", strerror (errno), NULL);
579       return (1);
580     }
581 #endif
582
583   if (close (mbfi) == -1)
584     {
585       error ("Error in close: %s", strerror (errno), NULL);
586       return (1);
587     }
588
589   for (i = 1; i <= nmsgs; i++)
590     {
591       if (pop_delete (server, i))
592         {
593           error (pop_error, NULL, NULL);
594           pop_close (server);
595           return (1);
596         }
597     }
598
599   if (pop_quit (server))
600     {
601       error (pop_error, NULL, NULL);
602       return (1);
603     }
604     
605   return (0);
606 }
607
608 static int
609 pop_retr (popserver server, int msgno, int (*action)(), void *arg)
610 {
611   char *line;
612   int ret;
613
614   if (pop_retrieve_first (server, msgno, &line))
615     {
616       strncpy (Errmsg, pop_error, sizeof (Errmsg));
617       Errmsg[sizeof (Errmsg)-1] = '\0';
618       return (NOTOK);
619     }
620
621   while (! (ret = pop_retrieve_next (server, &line)))
622     {
623       if (! line)
624         break;
625
626       if ((*action)(line, arg) != OK)
627         {
628           strcpy (Errmsg, strerror (errno));
629           pop_close (server);
630           return (NOTOK);
631         }
632     }
633
634   if (ret)
635     {
636       strncpy (Errmsg, pop_error, sizeof (Errmsg));
637       Errmsg[sizeof (Errmsg)-1] = '\0';
638       return (NOTOK);
639     }
640
641   return (OK);
642 }
643
644 /* Do this as a macro instead of using strcmp to save on execution time. */
645 #define IS_FROM_LINE(a) ((a[0] == 'F') \
646                          && (a[1] == 'r') \
647                          && (a[2] == 'o') \
648                          && (a[3] == 'm') \
649                          && (a[4] == ' '))
650
651 static int
652 mbx_write (char *line, FILE *mbf)
653 {
654   if (IS_FROM_LINE (line))
655     {
656       if (fputc ('>', mbf) == EOF)
657         return (NOTOK);
658     }
659   if (fputs (line, mbf) == EOF) 
660     return (NOTOK);
661   if (fputc (0x0a, mbf) == EOF)
662     return (NOTOK);
663   return (OK);
664 }
665
666 static int
667 mbx_delimit_begin (FILE *mbf)
668 {
669   if (fputs ("\f\n0, unseen,,\n", mbf) == EOF)
670     return (NOTOK);
671   return (OK);
672 }
673
674 static int
675 mbx_delimit_end (FILE *mbf)
676 {
677   if (putc ('\037', mbf) == EOF)
678     return (NOTOK);
679   return (OK);
680 }
681
682 #endif /* MAIL_USE_POP */
683 \f
684 #ifndef HAVE_STRERROR
685 static char *
686 strerror (int errnum)
687 {
688   extern char *sys_errlist[];
689   extern int sys_nerr;
690
691   if (errnum >= 0 && errnum < sys_nerr)
692     return sys_errlist[errnum];
693   return (char *) "Unknown error";
694 }
695
696 #endif /* ! HAVE_STRERROR */