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