*** empty log message ***
[bpt/emacs.git] / 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 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 1, 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, 675 Mass Ave, Cambridge, MA 02139, USA. */
20
21 /*
22 * Modified January, 1986 by Michael R. Gretzinger (Project Athena)
23 *
24 * Added POP (Post Office Protocol) service. When compiled -DPOP
25 * movemail will accept input filename arguments of the form
26 * "po:username". This will cause movemail to open a connection to
27 * a pop server running on $MAILHOST (environment variable). Movemail
28 * must be setuid to root in order to work with POP.
29 *
30 * New module: popmail.c
31 * Modified routines:
32 * main - added code within #ifdef MAIL_USE_POP; added setuid(getuid())
33 * after POP code.
34 * New routines in movemail.c:
35 * get_errmsg - return pointer to system error message
36 *
37 */
38
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #include <sys/file.h>
42 #include <errno.h>
43 #define NO_SHORTNAMES /* Tell config not to load remap.h */
44 #include "../src/config.h"
45
46 #ifdef USG
47 #include <fcntl.h>
48 #include <unistd.h>
49 #ifndef F_OK
50 #define F_OK 0
51 #define X_OK 1
52 #define W_OK 2
53 #define R_OK 4
54 #endif
55 #endif /* USG */
56
57 #ifdef XENIX
58 #include <sys/locking.h>
59 #endif
60
61 #ifdef MAIL_USE_MMDF
62 extern int lk_open (), lk_close ();
63 #endif
64
65 /* Cancel substitutions made by config.h for Emacs. */
66 #undef open
67 #undef read
68 #undef write
69 #undef close
70
71 char *concat ();
72 extern int errno;
73
74 /* Nonzero means this is name of a lock file to delete on fatal error. */
75 char *delete_lockname;
76
77 main (argc, argv)
78 int argc;
79 char **argv;
80 {
81 char *inname, *outname;
82 int indesc, outdesc;
83 char buf[1024];
84 int nread;
85
86 #ifndef MAIL_USE_FLOCK
87 struct stat st;
88 long now;
89 int tem;
90 char *lockname, *p;
91 char tempname[40];
92 int desc;
93 #endif /* not MAIL_USE_FLOCK */
94
95 delete_lockname = 0;
96
97 if (argc < 3)
98 fatal ("two arguments required");
99
100 inname = argv[1];
101 outname = argv[2];
102
103 #ifdef MAIL_USE_MMDF
104 mmdf_init (argv[0]);
105 #endif
106
107 /* Check access to output file. */
108 if (access (outname, F_OK) == 0 && access (outname, W_OK) != 0)
109 pfatal_with_name (outname);
110
111 /* Also check that outname's directory is writeable to the real uid. */
112 {
113 char *buf = (char *) malloc (strlen (outname) + 1);
114 char *p, q;
115 strcpy (buf, outname);
116 p = buf + strlen (buf);
117 while (p > buf && p[-1] != '/')
118 *--p = 0;
119 if (p == buf)
120 *p++ = '.';
121 if (access (buf, W_OK) != 0)
122 pfatal_with_name (buf);
123 free (buf);
124 }
125
126 #ifdef MAIL_USE_POP
127 if (!bcmp (inname, "po:", 3))
128 {
129 int status; char *user;
130
131 user = (char *) rindex (inname, ':') + 1;
132 status = popmail (user, outname);
133 exit (status);
134 }
135
136 setuid (getuid());
137 #endif /* MAIL_USE_POP */
138
139 /* Check access to input file. */
140 if (access (inname, R_OK | W_OK) != 0)
141 pfatal_with_name (inname);
142
143 #ifndef MAIL_USE_MMDF
144 #ifndef MAIL_USE_FLOCK
145 /* Use a lock file named /usr/spool/mail/$USER.lock:
146 If it exists, the mail file is locked. */
147 lockname = concat (inname, ".lock", "");
148 strcpy (tempname, inname);
149 p = tempname + strlen (tempname);
150 while (p != tempname && p[-1] != '/')
151 p--;
152 *p = 0;
153 strcpy (p, "EXXXXXX");
154 mktemp (tempname);
155 (void) unlink (tempname);
156
157 while (1)
158 {
159 /* Create the lock file, but not under the lock file name. */
160 /* Give up if cannot do that. */
161 desc = open (tempname, O_WRONLY | O_CREAT, 0666);
162 if (desc < 0)
163 pfatal_with_name (concat ("temporary file \"", tempname, "\""));
164 close (desc);
165
166 tem = link (tempname, lockname);
167 (void) unlink (tempname);
168 if (tem >= 0)
169 break;
170 sleep (1);
171
172 /* If lock file is a minute old, unlock it. */
173 if (stat (lockname, &st) >= 0)
174 {
175 now = time (0);
176 if (st.st_ctime < now - 60)
177 (void) unlink (lockname);
178 }
179 }
180
181 delete_lockname = lockname;
182 #endif /* not MAIL_USE_FLOCK */
183
184 #ifdef MAIL_USE_FLOCK
185 indesc = open (inname, O_RDWR);
186 #else /* if not MAIL_USE_FLOCK */
187 indesc = open (inname, O_RDONLY);
188 #endif /* not MAIL_USE_FLOCK */
189 #else /* MAIL_USE_MMDF */
190 indesc = lk_open (inname, O_RDONLY, 0, 0, 10);
191 #endif /* MAIL_USE_MMDF */
192
193 if (indesc < 0)
194 pfatal_with_name (inname);
195
196 #if defined(BSD) || defined(XENIX)
197 /* In case movemail is setuid to root, make sure the user can
198 read the output file. */
199 /* This is desirable for all systems
200 but I don't want to assume all have the umask system call */
201 umask (umask (0) & 0333);
202 #endif /* BSD or Xenix */
203 outdesc = open (outname, O_WRONLY | O_CREAT | O_EXCL, 0666);
204 if (outdesc < 0)
205 pfatal_with_name (outname);
206 #ifdef MAIL_USE_FLOCK
207 #ifdef XENIX
208 if (locking (indesc, LK_RLCK, 0L) < 0) pfatal_with_name (inname);
209 #else
210 if (flock (indesc, LOCK_EX) < 0) pfatal_with_name (inname);
211 #endif
212 #endif /* MAIL_USE_FLOCK */
213
214 while (1)
215 {
216 nread = read (indesc, buf, sizeof buf);
217 if (nread != write (outdesc, buf, nread))
218 {
219 int saved_errno = errno;
220 (void) unlink (outname);
221 errno = saved_errno;
222 pfatal_with_name (outname);
223 }
224 if (nread < sizeof buf)
225 break;
226 }
227
228 #ifdef BSD
229 fsync (outdesc);
230 #endif
231
232 /* Check to make sure no errors before we zap the inbox. */
233 if (close (outdesc) != 0)
234 {
235 int saved_errno = errno;
236 (void) unlink (outname);
237 errno = saved_errno;
238 pfatal_with_name (outname);
239 }
240
241 #ifdef MAIL_USE_FLOCK
242 #if defined(STRIDE) || defined(XENIX)
243 /* Stride, xenix have file locking, but no ftruncate. This mess will do. */
244 (void) close (open (inname, O_CREAT | O_TRUNC | O_RDWR, 0666));
245 #else
246 (void) ftruncate (indesc, 0L);
247 #endif /* STRIDE or XENIX */
248 #endif /* MAIL_USE_FLOCK */
249
250 #ifdef MAIL_USE_MMDF
251 lk_close (indesc, 0, 0, 0);
252 #else
253 close (indesc);
254 #endif
255
256 #ifndef MAIL_USE_FLOCK
257 /* Delete the input file; if we can't, at least get rid of its contents. */
258 if (unlink (inname) < 0)
259 if (errno != ENOENT)
260 creat (inname, 0666);
261 #ifndef MAIL_USE_MMDF
262 unlink (lockname);
263 #endif /* not MAIL_USE_MMDF */
264 #endif /* not MAIL_USE_FLOCK */
265 exit (0);
266 }
267 \f
268 /* Print error message and exit. */
269
270 fatal (s1, s2)
271 char *s1, *s2;
272 {
273 if (delete_lockname)
274 unlink (delete_lockname);
275 error (s1, s2);
276 exit (1);
277 }
278
279 /* Print error message. `s1' is printf control string, `s2' is arg for it. */
280
281 error (s1, s2, s3)
282 char *s1, *s2, *s3;
283 {
284 printf ("movemail: ");
285 printf (s1, s2, s3);
286 printf ("\n");
287 }
288
289 pfatal_with_name (name)
290 char *name;
291 {
292 extern int errno, sys_nerr;
293 extern char *sys_errlist[];
294 char *s;
295
296 if (errno < sys_nerr)
297 s = concat ("", sys_errlist[errno], " for %s");
298 else
299 s = "cannot open %s";
300 fatal (s, name);
301 }
302
303 /* Return a newly-allocated string whose contents concatenate those of s1, s2, s3. */
304
305 char *
306 concat (s1, s2, s3)
307 char *s1, *s2, *s3;
308 {
309 int len1 = strlen (s1), len2 = strlen (s2), len3 = strlen (s3);
310 char *result = (char *) xmalloc (len1 + len2 + len3 + 1);
311
312 strcpy (result, s1);
313 strcpy (result + len1, s2);
314 strcpy (result + len1 + len2, s3);
315 *(result + len1 + len2 + len3) = 0;
316
317 return result;
318 }
319
320 /* Like malloc but get fatal error if memory is exhausted. */
321
322 int
323 xmalloc (size)
324 int size;
325 {
326 int result = malloc (size);
327 if (!result)
328 fatal ("virtual memory exhausted", 0);
329 return result;
330 }
331 \f
332 /* This is the guts of the interface to the Post Office Protocol. */
333
334 #ifdef MAIL_USE_POP
335
336 #include <sys/socket.h>
337 #include <netinet/in.h>
338 #include <netdb.h>
339 #include <stdio.h>
340
341 #ifdef USG
342 #include <fcntl.h>
343 /* Cancel substitutions made by config.h for Emacs. */
344 #undef open
345 #undef read
346 #undef write
347 #undef close
348 #endif /* USG */
349
350 #define NOTOK (-1)
351 #define OK 0
352 #define DONE 1
353
354 char *progname;
355 FILE *sfi;
356 FILE *sfo;
357 char Errmsg[80];
358
359 static int debug = 0;
360
361 char *get_errmsg ();
362 char *getenv ();
363 int mbx_write ();
364
365 popmail (user, outfile)
366 char *user;
367 char *outfile;
368 {
369 char *host;
370 int nmsgs, nbytes;
371 char response[128];
372 register int i;
373 int mbfi;
374 FILE *mbf;
375
376 host = getenv ("MAILHOST");
377 if (host == NULL)
378 {
379 fatal ("no MAILHOST defined");
380 }
381
382 if (pop_init (host) == NOTOK)
383 {
384 error (Errmsg);
385 return 1;
386 }
387
388 if (getline (response, sizeof response, sfi) != OK)
389 {
390 error (response);
391 return 1;
392 }
393
394 if (pop_command ("USER %s", user) == NOTOK
395 || pop_command ("RPOP %s", user) == NOTOK)
396 {
397 error (Errmsg);
398 pop_command ("QUIT");
399 return 1;
400 }
401
402 if (pop_stat (&nmsgs, &nbytes) == NOTOK)
403 {
404 error (Errmsg);
405 pop_command ("QUIT");
406 return 1;
407 }
408
409 if (!nmsgs)
410 {
411 pop_command ("QUIT");
412 return 0;
413 }
414
415 mbfi = open (outfile, O_WRONLY | O_CREAT | O_EXCL, 0666);
416 if (mbfi < 0)
417 {
418 pop_command ("QUIT");
419 error ("Error in open: %s, %s", get_errmsg (), outfile);
420 return 1;
421 }
422 fchown (mbfi, getuid (), -1);
423
424 if ((mbf = fdopen (mbfi, "w")) == NULL)
425 {
426 pop_command ("QUIT");
427 error ("Error in fdopen: %s", get_errmsg ());
428 close (mbfi);
429 unlink (outfile);
430 return 1;
431 }
432
433 for (i = 1; i <= nmsgs; i++)
434 {
435 mbx_delimit_begin (mbf);
436 if (pop_retr (i, mbx_write, mbf) != OK)
437 {
438 error (Errmsg);
439 pop_command ("QUIT");
440 close (mbfi);
441 return 1;
442 }
443 mbx_delimit_end (mbf);
444 fflush (mbf);
445 }
446
447 for (i = 1; i <= nmsgs; i++)
448 {
449 if (pop_command ("DELE %d", i) == NOTOK)
450 {
451 error (Errmsg);
452 pop_command ("QUIT");
453 close (mbfi);
454 return 1;
455 }
456 }
457
458 pop_command ("QUIT");
459 close (mbfi);
460 return 0;
461 }
462
463 pop_init (host)
464 char *host;
465 {
466 register struct hostent *hp;
467 register struct servent *sp;
468 int lport = IPPORT_RESERVED - 1;
469 struct sockaddr_in sin;
470 register int s;
471
472 hp = gethostbyname (host);
473 if (hp == NULL)
474 {
475 sprintf (Errmsg, "MAILHOST unknown: %s", host);
476 return NOTOK;
477 }
478
479 sp = getservbyname ("pop", "tcp");
480 if (sp == 0)
481 {
482 strcpy (Errmsg, "tcp/pop: unknown service");
483 return NOTOK;
484 }
485
486 sin.sin_family = hp->h_addrtype;
487 bcopy (hp->h_addr, (char *)&sin.sin_addr, hp->h_length);
488 sin.sin_port = sp->s_port;
489 s = rresvport (&lport);
490 if (s < 0)
491 {
492 sprintf (Errmsg, "error creating socket: %s", get_errmsg ());
493 return NOTOK;
494 }
495
496 if (connect (s, (char *)&sin, sizeof sin) < 0)
497 {
498 sprintf (Errmsg, "error during connect: %s", get_errmsg ());
499 close (s);
500 return NOTOK;
501 }
502
503 sfi = fdopen (s, "r");
504 sfo = fdopen (s, "w");
505 if (sfi == NULL || sfo == NULL)
506 {
507 sprintf (Errmsg, "error in fdopen: %s", get_errmsg ());
508 close (s);
509 return NOTOK;
510 }
511
512 return OK;
513 }
514
515 pop_command (fmt, a, b, c, d)
516 char *fmt;
517 {
518 char buf[128];
519 char errmsg[64];
520
521 sprintf (buf, fmt, a, b, c, d);
522
523 if (debug) fprintf (stderr, "---> %s\n", buf);
524 if (putline (buf, Errmsg, sfo) == NOTOK) return NOTOK;
525
526 if (getline (buf, sizeof buf, sfi) != OK)
527 {
528 strcpy (Errmsg, buf);
529 return NOTOK;
530 }
531
532 if (debug) fprintf (stderr, "<--- %s\n", buf);
533 if (*buf != '+')
534 {
535 strcpy (Errmsg, buf);
536 return NOTOK;
537 }
538 else
539 {
540 return OK;
541 }
542 }
543
544
545 pop_stat (nmsgs, nbytes)
546 int *nmsgs, *nbytes;
547 {
548 char buf[128];
549
550 if (debug) fprintf (stderr, "---> STAT\n");
551 if (putline ("STAT", Errmsg, sfo) == NOTOK) return NOTOK;
552
553 if (getline (buf, sizeof buf, sfi) != OK)
554 {
555 strcpy (Errmsg, buf);
556 return NOTOK;
557 }
558
559 if (debug) fprintf (stderr, "<--- %s\n", buf);
560 if (*buf != '+')
561 {
562 strcpy (Errmsg, buf);
563 return NOTOK;
564 }
565 else
566 {
567 sscanf (buf, "+OK %d %d", nmsgs, nbytes);
568 return OK;
569 }
570 }
571
572 pop_retr (msgno, action, arg)
573 int (*action)();
574 {
575 char buf[128];
576
577 sprintf (buf, "RETR %d", msgno);
578 if (debug) fprintf (stderr, "%s\n", buf);
579 if (putline (buf, Errmsg, sfo) == NOTOK) return NOTOK;
580
581 if (getline (buf, sizeof buf, sfi) != OK)
582 {
583 strcpy (Errmsg, buf);
584 return NOTOK;
585 }
586
587 while (1)
588 {
589 switch (multiline (buf, sizeof buf, sfi))
590 {
591 case OK:
592 (*action)(buf, arg);
593 break;
594 case DONE:
595 return OK;
596 case NOTOK:
597 strcpy (Errmsg, buf);
598 return NOTOK;
599 }
600 }
601 }
602
603 getline (buf, n, f)
604 char *buf;
605 register int n;
606 FILE *f;
607 {
608 register char *p;
609 int c;
610
611 p = buf;
612 while (--n > 0 && (c = fgetc (f)) != EOF)
613 if ((*p++ = c) == '\n') break;
614
615 if (ferror (f))
616 {
617 strcpy (buf, "error on connection");
618 return NOTOK;
619 }
620
621 if (c == EOF && p == buf)
622 {
623 strcpy (buf, "connection closed by foreign host");
624 return DONE;
625 }
626
627 *p = NULL;
628 if (*--p == '\n') *p = NULL;
629 if (*--p == '\r') *p = NULL;
630 return OK;
631 }
632
633 multiline (buf, n, f)
634 char *buf;
635 register int n;
636 FILE *f;
637 {
638 if (getline (buf, n, f) != OK) return NOTOK;
639 if (*buf == '.')
640 {
641 if (*(buf+1) == NULL)
642 {
643 return DONE;
644 }
645 else
646 {
647 strcpy (buf, buf+1);
648 }
649 }
650 return OK;
651 }
652
653 char *
654 get_errmsg ()
655 {
656 extern int errno, sys_nerr;
657 extern char *sys_errlist[];
658 char *s;
659
660 if (errno < sys_nerr)
661 s = sys_errlist[errno];
662 else
663 s = "unknown error";
664 return (s);
665 }
666
667 putline (buf, err, f)
668 char *buf;
669 char *err;
670 FILE *f;
671 {
672 fprintf (f, "%s\r\n", buf);
673 fflush (f);
674 if (ferror (f))
675 {
676 strcpy (err, "lost connection");
677 return NOTOK;
678 }
679 return OK;
680 }
681
682 mbx_write (line, mbf)
683 char *line;
684 FILE *mbf;
685 {
686 fputs (line, mbf);
687 fputc (0x0a, mbf);
688 }
689
690 mbx_delimit_begin (mbf)
691 FILE *mbf;
692 {
693 fputs ("\f\n0, unseen,,\n", mbf);
694 }
695
696 mbx_delimit_end (mbf)
697 FILE *mbf;
698 {
699 putc ('\037', mbf);
700 }
701
702 #endif /* MAIL_USE_POP */