xref: /minix/minix/commands/mail/mail.c (revision 5ef5b27f)
1 /*  mail - send/receive mail 		 Author: Peter S. Housel */
2 /* Version 0.2 of September 1990: added -e, -t, * options - cwr */
3 
4 /* 2003-07-18: added -s option - ASW */
5 
6 #include <sys/types.h>
7 #include <sys/stat.h>
8 #include <errno.h>
9 #undef EOF			/* temporary hack */
10 #include <signal.h>
11 #include <pwd.h>
12 #include <time.h>
13 #include <setjmp.h>
14 #include <string.h>
15 #include <stdlib.h>
16 #include <fcntl.h>
17 #include <unistd.h>
18 #include <sys/wait.h>
19 #include <stdio.h>
20 
21 #ifdef DEBUG
22 #define D(Q) (Q)
23 #else
24 #define D(Q)
25 #endif
26 
27 #define SHELL		"/bin/sh"
28 
29 #define DROPNAME 	"/var/mail/%s"
30 #define LOCKNAME	"/var/mail/%s.lock"
31 #define LOCKWAIT	5	/* seconds to wait after collision */
32 #define LOCKTRIES	4	/* maximum number of collisions */
33 
34 #define MBOX		"mbox"
35 
36 #define HELPFILE	"/usr/lib/mail.help"
37 #define PROMPT		"? "
38 #define PATHLEN		80
39 #define MAXRCPT		100	/* maximum number of recipients */
40 #define LINELEN		512
41 
42 /* #define MAILER		"/usr/bin/smail"	*/ /* smart mailer */
43 #define MAILERARGS		/* (unused) */
44 
45 #define UNREAD		1	/* 'not read yet' status */
46 #define DELETED		2	/* 'deleted' status */
47 #define READ		3	/* 'has been read' status */
48 
49 struct letter {
50   struct letter *prev, *next;	/* linked letter list */
51   int status;			/* letter status */
52   off_t location;		/* location within mailbox file */
53 };
54 
55 struct letter *firstlet, *lastlet;
56 
57 int usemailer = 1;		/* use MAILER to deliver (if any) */
58 int printmode = 0;		/* print-and-exit mode */
59 int quitmode = 0;		/* take interrupts */
60 int reversemode = 0;		/* print mailbox in reverse order */
61 int usedrop = 1;		/* read the maildrop (no -f given) */
62 int verbose = 0;		/* pass "-v" flag on to mailer */
63 int needupdate = 0;		/* need to update mailbox */
64 int msgstatus = 0;		/* return the mail status */
65 int distlist = 0;		/* include distribution list */
66 char mailbox[PATHLEN];		/* user's mailbox/maildrop */
67 char tempname[PATHLEN] = "/tmp/mailXXXXXX";	/* temporary file */
68 char *subject = NULL;
69 FILE *boxfp = NULL;		/* mailbox file */
70 jmp_buf printjump;		/* for quitting out of letters */
71 unsigned oldmask;		/* saved umask() */
72 
73 extern int optind;
74 extern char *optarg;
75 
76 int main(int argc, char **argv);
77 int deliver(int count, char *vec []);
78 FILE *makerewindable(void);
79 int copy(FILE *fromfp, FILE *tofp);
80 void readbox(void);
81 void printall(void);
82 void interact(void);
83 void onint(int dummy);
84 void savelet(struct letter *let, char *savefile);
85 void updatebox(void);
86 void printlet(struct letter *let, FILE *tofp);
87 void doshell(char *command);
88 void usage(void);
89 char *basename(char *name);
90 char *whoami(void);
91 void dohelp(void);
92 int filesize(char *name);
93 
94 int main(argc, argv)
95 int argc;
96 char *argv[];
97 {
98   int c;
99 
100   if ('l' == (basename(argv[0]))[0])	/* 'lmail' link? */
101 	usemailer = 0;		/* yes, let's deliver it */
102 
103   (void) mktemp(tempname);	/* name the temp file */
104 
105   oldmask = umask(022);		/* change umask for security */
106 
107   while (EOF != (c = getopt(argc, argv, "epqrf:tdvs:"))) switch (c) {
108 	    case 'e':	++msgstatus;	break;
109 
110 	    case 't':	++distlist;	break;
111 
112 	    case 'p':	++printmode;	break;
113 
114 	    case 'q':	++quitmode;	break;
115 
116 	    case 'r':	++reversemode;	break;
117 
118 	    case 'f':
119 		setuid(getuid());	/* won't need to lock */
120 		usedrop = 0;
121 		strncpy(mailbox, optarg, (size_t)(PATHLEN - 1));
122 		break;
123 
124 	    case 'd':	usemailer = 0;	break;
125 
126 	    case 'v':	++verbose;	break;
127 
128 	    case 's':	subject = optarg; break;
129 
130 	    default:
131 		usage();
132 		exit(1);
133 	}
134 
135   if (optind < argc) {
136 	if (deliver(argc - optind, argv + optind) < 0)
137 		exit(1);
138 	else
139 		exit(0);
140   }
141   if (usedrop) sprintf(mailbox, DROPNAME, whoami());
142 
143   D(printf("mailbox=%s\n", mailbox));
144 
145   if (msgstatus) {
146 	if (filesize(mailbox))
147 		exit(0);
148 	else
149 		exit(1);
150   }
151 
152   readbox();
153 
154   if (printmode)
155 	printall();
156   else
157 	interact();
158 
159   if (needupdate) updatebox();
160 
161   return(0);
162 }
163 
164 int deliver(count, vec)
165 int count;
166 char *vec[];
167 {
168   int i, j;
169   int errs = 0;			/* count of errors */
170   int dropfd;			/* file descriptor for user's drop */
171   int created = 0;		/* true if we created the maildrop */
172   FILE *mailfp;			/* fp for mail */
173   struct stat stb;		/* for checking drop modes, owners */
174 #ifdef __STDC__
175   void (*sigint)(int), (*sighup)(int), (*sigquit)(int);/* saving signal state */
176 #else
177   void (*sigint) (), (*sighup) (), (*sigquit) ();      /* saving signal state */
178 #endif
179   time_t now;			/* for datestamping the postmark */
180   char sender[32];		/* sender's login name */
181   char lockname[PATHLEN];	/* maildrop lock */
182   int locktries;		/* tries when box is locked */
183   struct passwd *pw;		/* sender and recipent */
184   int to_console;		/* deliver to console if everything fails */
185 
186   if (count > MAXRCPT) {
187 	fprintf(stderr, "mail: too many recipients\n");
188 	return -1;
189   }
190 #ifdef MAILER
191   if (usemailer) {
192 	char *argvec[MAXRCPT + 3];
193 	char **argp;
194 
195 	setuid(getuid());
196 
197 	argp = argvec;
198 	*argp++ = "send-mail";
199 	if (verbose) *argp++ = "-v";
200 
201 	for (i = 0; i < count; ++i) *argp++ = vec[i];
202 
203 	*argp = NULL;
204 	execv(MAILER, argvec);
205 	fprintf(stderr, "mail: couldn't exec %s\n", MAILER);
206 	return -1;
207   }
208 #endif /* MAILER */
209 
210   if (NULL == (pw = getpwuid(getuid()))) {
211 	fprintf(stderr, "mail: unknown sender\n");
212 	return -1;
213   }
214   strcpy(sender, pw->pw_name);
215 
216   /* If we need to rewind stdin and it isn't rewindable, make a copy */
217   if (isatty(0) || (count > 1 && lseek(0, 0L, 0) == (off_t) -1)) {
218 	mailfp = makerewindable();
219   } else
220 	mailfp = stdin;
221 
222   /* Shut off signals during the delivery */
223   sigint = signal(SIGINT, SIG_IGN);
224   sighup = signal(SIGHUP, SIG_IGN);
225   sigquit = signal(SIGQUIT, SIG_IGN);
226 
227   for (i = 0; i < count; ++i) {
228 	if (count > 1) rewind(mailfp);
229 
230 	D(printf("deliver to %s\n", vec[i]));
231 
232 	if (NULL == (pw = getpwnam(vec[i]))) {
233 		fprintf(stderr, "mail: user %s not known\n", vec[i]);
234 		++errs;
235 		continue;
236 	}
237 	sprintf(mailbox, DROPNAME, pw->pw_name);
238 	sprintf(lockname, LOCKNAME, pw->pw_name);
239 
240 	D(printf("maildrop='%s', lock='%s'\n", mailbox, lockname));
241 
242 	/* Lock the maildrop while we're messing with it. Races are
243 	 * possible (though not very likely) when we have to create
244 	 * the maildrop, but not otherwise. If the box is already
245 	 * locked, wait awhile and try again. */
246 	locktries = created = to_console = 0;
247 trylock:
248 	if (link(mailbox, lockname) != 0) {
249 		if (ENOENT == errno) {	/* user doesn't have a drop yet */
250 			dropfd = creat(mailbox, 0600);
251 			if (dropfd < 0 && errno == ENOENT) {
252 				/* Probably missing spool dir; to console. */
253 				boxfp = fopen("/dev/console", "w");
254 				if (boxfp != NULL) {
255 					to_console = 1;
256 					goto nobox;
257 				}
258 			}
259 			if (dropfd < 0) {
260 				fprintf(stderr, "mail: couln't create a maildrop for user %s\n",
261 					vec[i]);
262 				++errs;
263 				continue;
264 			}
265 			++created;
266 			goto trylock;
267 		} else {	/* somebody else has it locked, it seems -
268 			 * wait */
269 			if (++locktries >= LOCKTRIES) {
270 				fprintf(stderr, "mail: couldn't lock maildrop for user %s\n",
271 					vec[i]);
272 				++errs;
273 				continue;
274 			}
275 			sleep(LOCKWAIT);
276 			goto trylock;
277 		}
278 	}
279 	if (created) {
280 		(void) chown(mailbox, pw->pw_uid, pw->pw_gid);
281 		boxfp = fdopen(dropfd, "a");
282 	} else
283 		boxfp = fopen(mailbox, "a");
284 
285 	if (NULL == boxfp || stat(mailbox, &stb) < 0) {
286 		fprintf(stderr, "mail: serious maildrop problems for %s\n", vec[i]);
287 		unlink(lockname);
288 		++errs;
289 		continue;
290 	}
291 	if (stb.st_uid != pw->pw_uid || (stb.st_mode & S_IFMT) != S_IFREG) {
292 		fprintf(stderr, "mail: mailbox for user %s is illegal\n", vec[i]);
293 		unlink(lockname);
294 		++errs;
295 		continue;
296 	}
297 nobox:
298 	if (to_console) {
299 		fprintf(boxfp,
300 			"-------------\n| Mail from %s to %s\n-------------\n",
301 			sender, vec[i]);
302 	} else {
303 		(void) time(&now);
304 		fprintf(boxfp, "From %s %24.24s\n", sender, ctime(&now));
305 	}
306 
307 	/* Add the To: header line */
308 	fprintf(boxfp, "To: %s\n", vec[i]);
309 
310 	if (distlist) {
311 		fprintf(boxfp, "Dist: ");
312 		for (j = 0; j < count; ++j)
313 			if (getpwnam(vec[j]) != NULL && j != i)
314 				fprintf(boxfp, "%s ", vec[j]) ;
315 		fprintf(boxfp, "\n");
316 	}
317 
318 	/* Add the Subject: header line */
319 	if (subject != NULL) fprintf(boxfp, "Subject: %s\n", subject);
320 
321 	fprintf(boxfp, "\n");
322 
323 	if ((copy(mailfp, boxfp) < 0) || (fclose(boxfp) != 0)) {
324 		fprintf(stderr, "mail: error delivering to user %s", vec[i]);
325 		perror(" ");
326 		++errs;
327 	}
328 	unlink(lockname);
329   }
330 
331   fclose(mailfp);
332 
333   /* Put signals back the way they were */
334   signal(SIGINT, sigint);
335   signal(SIGHUP, sighup);
336   signal(SIGQUIT, sigquit);
337 
338   return(0 == errs) ? 0 : -1;
339 }
340 
341 /* 'stdin' isn't rewindable. Make a temp file that is.
342  * Note that if one wanted to catch SIGINT and write a '~/dead.letter'
343  * for interactive mails, this might be the place to do it (though the
344  * case where a MAILER is being used would also need to be handled).
345  */
346 FILE *makerewindable()
347 {
348   FILE *tempfp;			/* temp file used for copy */
349   int c;			/* character being copied */
350   int state;			/* ".\n" detection state */
351 
352   if (NULL == (tempfp = fopen(tempname, "w"))) {
353 	fprintf(stderr, "mail: can't create temporary file\n");
354 	return NULL;
355   }
356 
357   /* Here we copy until we reach the end of the letter (end of file or
358    * a line containing only a '.'), painstakingly avoiding setting a
359    * line length limit. */
360   state = '\n';
361   while (EOF != (c = getc(stdin))) switch (state) {
362 	    case '\n':
363 		if ('.' == c)
364 			state = '.';
365 		else {
366 			if ('\n' != c) state = '\0';
367 			putc(c, tempfp);
368 		}
369 		break;
370 	    case '.':
371 		if ('\n' == c) goto done;
372 		state = '\0';
373 		putc('.', tempfp);
374 		putc(c, tempfp);
375 		break;
376 	    default:
377 		state = ('\n' == c) ? '\n' : '\0';
378 		putc(c, tempfp);
379 	}
380 done:
381   if (ferror(tempfp) || fclose(tempfp)) {
382 	fprintf(stderr, "mail: couldn't copy letter to temporary file\n");
383 	return NULL;
384   }
385   tempfp = freopen(tempname, "r", stdin);
386   unlink(tempname);		/* unlink name; file lingers on in limbo */
387   return tempfp;
388 }
389 
390 int copy(fromfp, tofp)
391 FILE *fromfp, *tofp;
392 {
393   int c;			/* character being copied */
394   int state;			/* ".\n" and postmark detection state */
395   int blankline = 0;		/* was most recent line completely blank? */
396   static char postmark[] = "From ";
397   char *p, *q;
398 
399   /* Here we copy until we reach the end of the letter (end of file or
400    * a line containing only a '.'). Postmarks (lines beginning with
401    * "From ") are copied with a ">" prepended. Here we also complicate
402    * things by not setting a line limit. */
403   state = '\n';
404   p = postmark;
405   while (EOF != (c = getc(fromfp))) {
406 	switch (state) {
407 	    case '\n':
408 		if ('.' == c)	/* '.' at BOL */
409 			state = '.';
410 		else if (*p == c) {	/* start of postmark */
411 			++p;
412 			state = 'P';
413 		} else {	/* anything else */
414 			if ('\n' == c)
415 				blankline = 1;
416 			else {
417 				state = '\0';
418 				blankline = 0;
419 			}
420 			putc(c, tofp);
421 		}
422 		break;
423 	    case '.':
424 		if ('\n' == c) goto done;
425 		state = '\0';
426 		putc('.', tofp);
427 		putc(c, tofp);
428 		break;
429 	    case 'P':
430 		if (*p == c) {
431 			if (*++p == '\0') {	/* successfully reached end */
432 				p = postmark;
433 				putc('>', tofp);
434 				fputs(postmark, tofp);
435 				state = '\0';
436 				break;
437 			}
438 			break;	/* not there yet */
439 		}
440 		state = ('\n' == c) ? '\n' : '\0';
441 		for (q = postmark; q < p; ++q) putc(*q, tofp);
442 		putc(c, tofp);
443 		blankline = 0;
444 		p = postmark;
445 		break;
446 	    default:
447 		state = ('\n' == c) ? '\n' : '\0';
448 		putc(c, tofp);
449 	}
450   }
451   if ('\n' != state) putc('\n', tofp);
452 done:
453   if (!blankline) putc('\n', tofp);
454   if (ferror(tofp)) return -1;
455   return 0;
456 }
457 
458 void readbox()
459 {
460   char linebuf[512];
461   struct letter *let;
462   off_t current;
463 
464   firstlet = lastlet = NULL;
465 
466   if (access(mailbox, 4) < 0 || NULL == (boxfp = fopen(mailbox, "r"))) {
467 	if (usedrop && ENOENT == errno) return;
468 	fprintf(stderr, "can't access mailbox ");
469 	perror(mailbox);
470 	exit(1);
471   }
472   current = 0L;
473   while (1) {
474 	if (NULL == fgets(linebuf, sizeof linebuf, boxfp)) break;
475 
476 	if (!strncmp(linebuf, "From ", (size_t)5)) {
477 		if (NULL == (let = (struct letter *) malloc(sizeof(struct letter)))) {
478 			fprintf(stderr, "Out of memory.\n");
479 			exit(1);
480 		}
481 		if (NULL == lastlet) {
482 			firstlet = let;
483 			let->prev = NULL;
484 		} else {
485 			let->prev = lastlet;
486 			lastlet->next = let;
487 		}
488 		lastlet = let;
489 		let->next = NULL;
490 
491 		let->status = UNREAD;
492 		let->location = current;
493 		D(printf("letter at %ld\n", current));
494 	}
495 	current += strlen(linebuf);
496   }
497 }
498 
499 void printall()
500 {
501   struct letter *let;
502 
503   let = reversemode ? firstlet : lastlet;
504 
505   if (NULL == let) {
506 	printf("No mail.\n");
507 	return;
508   }
509   while (NULL != let) {
510 	printlet(let, stdout);
511 	let = reversemode ? let->next : let->prev;
512   }
513 }
514 
515 void interact()
516 {
517   char linebuf[512];		/* user input line */
518   struct letter *let, *next;	/* current and next letter */
519   int interrupted = 0;		/* SIGINT hit during letter print */
520   int needprint = 1;		/* need to print this letter */
521   char *savefile;		/* filename to save into */
522 
523   if (NULL == firstlet) {
524 	printf("No mail.\n");
525 	return;
526   }
527   let = reversemode ? firstlet : lastlet;
528 
529   while (1) {
530 	next = reversemode ? let->next : let->prev;
531 	if (NULL == next) next = let;
532 
533 	if (!quitmode) {
534 		interrupted = setjmp(printjump);
535 		signal(SIGINT, onint);
536 	}
537 	if (!interrupted && needprint) {
538 		if (DELETED != let->status) let->status = READ;
539 		printlet(let, stdout);
540 	}
541 	if (interrupted) putchar('\n');
542 	needprint = 0;
543 	fputs(PROMPT, stdout);
544 	fflush(stdout);
545 
546 	if (fgets(linebuf, sizeof linebuf, stdin) == NULL) break;
547 
548 	if (!quitmode) signal(SIGINT, SIG_IGN);
549 
550 	switch (linebuf[0]) {
551 	    case '\n':
552 		let = next;
553 		needprint = 1;
554 		continue;
555 	    case 'd':
556 		let->status = DELETED;
557 		if (next != let)/* look into this */
558 			needprint = 1;
559 		needupdate = 1;
560 		let = next;
561 		continue;
562 	    case 'p':
563 		needprint = 1;
564 		continue;
565 	    case '-':
566 		next = reversemode ? let->prev : let->next;
567 		if (NULL == next) next = let;
568 		let = next;
569 		needprint = 1;
570 		continue;
571 	    case 's':
572 		for (savefile = strtok(linebuf + 1, " \t\n");
573 		     savefile != NULL;
574 		     savefile = strtok((char *) NULL, " \t\n")) {
575 			savelet(let, savefile);
576 		}
577 		continue;
578 	    case '!':
579 		doshell(linebuf + 1);
580 		continue;
581 	    case '*':
582 		dohelp();
583 		continue;
584 	    case 'q':
585 		return;
586 	    case 'x':
587 		exit(0);
588 	    default:
589 		fprintf(stderr, "Illegal command\n");
590 		continue;
591 	}
592   }
593 }
594 
595 void onint(dummy)
596 int dummy;	/* to satisfy ANSI compilers */
597 {
598   longjmp(printjump, 1);
599 }
600 
601 void savelet(let, savefile)
602 struct letter *let;
603 char *savefile;
604 {
605   int waitstat, pid;
606   FILE *savefp;
607 
608   if ((pid = fork()) < 0) {
609 	perror("mail: couldn't fork");
610 	return;
611   } else if (pid != 0) {	/* parent */
612 	wait(&waitstat);
613 	return;
614   }
615 
616   /* Child */
617   setgid(getgid());
618   setuid(getuid());
619   if ((savefp = fopen(savefile, "a")) == NULL) {
620 	perror(savefile);
621 	exit(0);
622   }
623   printlet(let, savefp);
624   if ((ferror(savefp) != 0) | (fclose(savefp) != 0)) {
625 	fprintf(stderr, "savefile write error:");
626 	perror(savefile);
627   }
628   exit(0);
629 }
630 
631 void updatebox()
632 {
633   FILE *tempfp;			/* fp for tempfile */
634   char lockname[PATHLEN];	/* maildrop lock */
635   int locktries = 0;		/* tries when box is locked */
636   struct letter *let;		/* current letter */
637   int c;
638 
639   sprintf(lockname, LOCKNAME, whoami());
640 
641   if (NULL == (tempfp = fopen(tempname, "w"))) {
642 	perror("mail: can't create temporary file");
643 	return;
644   }
645   for (let = firstlet; let != NULL; let = let->next) {
646 	if (let->status != DELETED) {
647 		printlet(let, tempfp);
648 		D(printf("printed letter at %ld\n", let->location));
649 	}
650   }
651 
652   if (ferror(tempfp) || NULL == (tempfp = freopen(tempname, "r", tempfp))) {
653 	perror("mail: temporary file write error");
654 	unlink(tempname);
655 	return;
656   }
657 
658   /* Shut off signals during the update */
659   signal(SIGINT, SIG_IGN);
660   signal(SIGHUP, SIG_IGN);
661   signal(SIGQUIT, SIG_IGN);
662 
663   if (usedrop) while (link(mailbox, lockname) != 0) {
664 		if (++locktries >= LOCKTRIES) {
665 			fprintf(stderr, "mail: couldn't lock maildrop for update\n");
666 			return;
667 		}
668 		sleep(LOCKWAIT);
669 	}
670 
671   if (NULL == (boxfp = freopen(mailbox, "w", boxfp))) {
672 	perror("mail: couldn't reopen maildrop");
673 	fprintf(stderr, "mail may have been lost; look in %s\n", tempname);
674 	if (usedrop) unlink(lockname);
675 	return;
676   }
677   unlink(tempname);
678 
679   while ((c = getc(tempfp)) != EOF) putc(c, boxfp);
680 
681   fclose(boxfp);
682 
683   if (usedrop) unlink(lockname);
684 }
685 
686 void printlet(let, tofp)
687 struct letter *let;
688 FILE *tofp;
689 {
690   off_t current, limit;
691   int c;
692 
693   fseek(boxfp, (current = let->location), 0);
694   limit = (NULL != let->next) ? let->next->location : -1;
695 
696   while (current != limit && (c = getc(boxfp)) != EOF) {
697 	putc(c, tofp);
698 	++current;
699   }
700 }
701 
702 void doshell(command)
703 char *command;
704 {
705   int waitstat, pid;
706   char *shell;
707 
708   if (NULL == (shell = getenv("SHELL"))) shell = SHELL;
709 
710   if ((pid = fork()) < 0) {
711 	perror("mail: couldn't fork");
712 	return;
713   } else if (pid != 0) {	/* parent */
714 	wait(&waitstat);
715 	return;
716   }
717 
718   /* Child */
719   setgid(getgid());
720   setuid(getuid());
721   umask(oldmask);
722 
723   execl(shell, shell, "-c", command, (char *) NULL);
724   fprintf(stderr, "can't exec shell\n");
725   exit(127);
726 }
727 
728 void usage()
729 {
730   fprintf(stderr, "usage: mail [-epqr] [-f file]\n");
731   fprintf(stderr, "       mail [-dtv] [-s subject] user [...]\n");
732 }
733 
734 char *basename(name)
735 char *name;
736 {
737   char *p;
738 
739   if (NULL == (p = rindex(name, '/')))
740 	return name;
741   else
742 	return p + 1;
743 }
744 
745 char *whoami()
746 {
747   struct passwd *pw;
748 
749   if (NULL != (pw = getpwuid(getuid())))
750 	return pw->pw_name;
751   else
752 	return "nobody";
753 }
754 
755 void dohelp()
756 {
757   FILE *fp;
758   char buffer[80];
759 
760   if ( (fp = fopen(HELPFILE, "r")) == NULL) {
761 	fprintf(stdout, "can't open helpfile %s\n", HELPFILE);
762 	return;
763   }
764 
765   while (fgets(buffer, 80, fp))
766 	fputs(buffer, stdout);
767 
768   fclose(fp);
769 }
770 
771 int filesize(name)
772 char *name ;
773 {
774   struct stat buf;
775 
776   if (stat(name, &buf) == -1)
777 	buf.st_size = 0L;
778 
779   return (buf.st_size ? 1 : 0);
780 }
781