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