source: trunk/minix/commands/simple/mail.c@ 15

Last change on this file since 15 was 9, checked in by Mattia Monga, 14 years ago

Minix 3.1.2a

File size: 17.3 KB
Line 
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 "/usr/spool/mail/%s"
30#define LOCKNAME "/usr/spool/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
49struct letter {
50 struct letter *prev, *next; /* linked letter list */
51 int status; /* letter status */
52 off_t location; /* location within mailbox file */
53};
54
55struct letter *firstlet, *lastlet;
56
57int usemailer = 1; /* use MAILER to deliver (if any) */
58int printmode = 0; /* print-and-exit mode */
59int quitmode = 0; /* take interrupts */
60int reversemode = 0; /* print mailbox in reverse order */
61int usedrop = 1; /* read the maildrop (no -f given) */
62int verbose = 0; /* pass "-v" flag on to mailer */
63int needupdate = 0; /* need to update mailbox */
64int msgstatus = 0; /* return the mail status */
65int distlist = 0; /* include distribution list */
66char mailbox[PATHLEN]; /* user's mailbox/maildrop */
67char tempname[PATHLEN] = "/tmp/mailXXXXXX"; /* temporary file */
68char *subject = NULL;
69FILE *boxfp = NULL; /* mailbox file */
70jmp_buf printjump; /* for quitting out of letters */
71unsigned oldmask; /* saved umask() */
72
73extern int optind;
74extern char *optarg;
75
76_PROTOTYPE(int main, (int argc, char **argv));
77_PROTOTYPE(int deliver, (int count, char *vec []));
78_PROTOTYPE(FILE *makerewindable, (void));
79_PROTOTYPE(int copy, (FILE *fromfp, FILE *tofp));
80_PROTOTYPE(void readbox, (void));
81_PROTOTYPE(void printall, (void));
82_PROTOTYPE(void interact, (void));
83_PROTOTYPE(void onint, (int dummy));
84_PROTOTYPE(void savelet, (struct letter *let, char *savefile));
85_PROTOTYPE(void updatebox, (void));
86_PROTOTYPE(void printlet, (struct letter *let, FILE *tofp));
87_PROTOTYPE(void doshell, (char *command));
88_PROTOTYPE(void usage, (void));
89_PROTOTYPE(char *basename, (char *name));
90_PROTOTYPE(char *whoami, (void));
91_PROTOTYPE(void dohelp, (void));
92_PROTOTYPE(int filesize, (char *name));
93
94int main(argc, argv)
95int argc;
96char *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
164int deliver(count, vec)
165int count;
166char *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;
247trylock:
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 }
297nobox:
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 */
346FILE *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 }
380done:
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
390int copy(fromfp, tofp)
391FILE *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);
452done:
453 if (!blankline) putc('\n', tofp);
454 if (ferror(tofp)) return -1;
455 return 0;
456}
457
458void 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
499void 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
515void 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
595void onint(dummy)
596int dummy; /* to satisfy ANSI compilers */
597{
598 longjmp(printjump, 1);
599}
600
601void savelet(let, savefile)
602struct letter *let;
603char *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
631void 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
686void printlet(let, tofp)
687struct letter *let;
688FILE *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
702void doshell(command)
703char *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
728void usage()
729{
730 fprintf(stderr, "usage: mail [-epqr] [-f file]\n");
731 fprintf(stderr, " mail [-dtv] [-s subject] user [...]\n");
732}
733
734char *basename(name)
735char *name;
736{
737 char *p;
738
739 if (NULL == (p = rindex(name, '/')))
740 return name;
741 else
742 return p + 1;
743}
744
745char *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
755void 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 else
763 while (fgets(buffer, 80, fp))
764 fputs(buffer, stdout);
765}
766
767int filesize(name)
768char *name ;
769{
770 struct stat buf;
771
772 if (stat(name, &buf) == -1)
773 buf.st_size = 0L;
774
775 return (buf.st_size ? 1 : 0);
776}
Note: See TracBrowser for help on using the repository browser.