1 /* mail - send/receive mail Author: Peter S. Housel */
2 /* Version 0.2 of September 1990: added -e, -t, * options - cwr */
4 /* 2003-07-18: added -s option - ASW */
9 #undef EOF /* temporary hack */
27 #define SHELL "/bin/sh"
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 */
36 #define HELPFILE "/usr/lib/mail.help"
39 #define MAXRCPT 100 /* maximum number of recipients */
42 /* #define MAILER "/usr/bin/smail" */ /* smart mailer */
43 #define MAILERARGS /* (unused) */
45 #define UNREAD 1 /* 'not read yet' status */
46 #define DELETED 2 /* 'deleted' status */
47 #define READ 3 /* 'has been read' status */
50 struct letter
*prev
, *next
; /* linked letter list */
51 int status
; /* letter status */
52 off_t location
; /* location within mailbox file */
55 struct letter
*firstlet
, *lastlet
;
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 */
69 FILE *boxfp
= NULL
; /* mailbox file */
70 jmp_buf printjump
; /* for quitting out of letters */
71 unsigned oldmask
; /* saved umask() */
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
);
83 void onint(int dummy
);
84 void savelet(struct letter
*let
, char *savefile
);
86 void printlet(struct letter
*let
, FILE *tofp
);
87 void doshell(char *command
);
89 char *basename(char *name
);
92 int filesize(char *name
);
100 if ('l' == (basename(argv
[0]))[0]) /* 'lmail' link? */
101 usemailer
= 0; /* yes, let's deliver it */
103 (void) mktemp(tempname
); /* name the temp file */
105 oldmask
= umask(022); /* change umask for security */
107 while (EOF
!= (c
= getopt(argc
, argv
, "epqrf:tdvs:"))) switch (c
) {
108 case 'e': ++msgstatus
; break;
110 case 't': ++distlist
; break;
112 case 'p': ++printmode
; break;
114 case 'q': ++quitmode
; break;
116 case 'r': ++reversemode
; break;
119 setuid(getuid()); /* won't need to lock */
121 strncpy(mailbox
, optarg
, (size_t)(PATHLEN
- 1));
124 case 'd': usemailer
= 0; break;
126 case 'v': ++verbose
; break;
128 case 's': subject
= optarg
; break;
136 if (deliver(argc
- optind
, argv
+ optind
) < 0)
141 if (usedrop
) sprintf(mailbox
, DROPNAME
, whoami());
143 D(printf("mailbox=%s\n", mailbox
));
146 if (filesize(mailbox
))
159 if (needupdate
) updatebox();
164 int deliver(count
, vec
)
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 */
175 void (*sigint
)(int), (*sighup
)(int), (*sigquit
)(int);/* saving signal state */
177 void (*sigint
) (), (*sighup
) (), (*sigquit
) (); /* saving signal state */
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 */
186 if (count
> MAXRCPT
) {
187 fprintf(stderr
, "mail: too many recipients\n");
192 char *argvec
[MAXRCPT
+ 3];
198 *argp
++ = "send-mail";
199 if (verbose
) *argp
++ = "-v";
201 for (i
= 0; i
< count
; ++i
) *argp
++ = vec
[i
];
204 execv(MAILER
, argvec
);
205 fprintf(stderr
, "mail: couldn't exec %s\n", MAILER
);
210 if (NULL
== (pw
= getpwuid(getuid()))) {
211 fprintf(stderr
, "mail: unknown sender\n");
214 strcpy(sender
, pw
->pw_name
);
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();
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
);
227 for (i
= 0; i
< count
; ++i
) {
228 if (count
> 1) rewind(mailfp
);
230 D(printf("deliver to %s\n", vec
[i
]));
232 if (NULL
== (pw
= getpwnam(vec
[i
]))) {
233 fprintf(stderr
, "mail: user %s not known\n", vec
[i
]);
237 sprintf(mailbox
, DROPNAME
, pw
->pw_name
);
238 sprintf(lockname
, LOCKNAME
, pw
->pw_name
);
240 D(printf("maildrop='%s', lock='%s'\n", mailbox
, lockname
));
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;
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");
260 fprintf(stderr
, "mail: couln't create a maildrop for user %s\n",
267 } else { /* somebody else has it locked, it seems -
269 if (++locktries
>= LOCKTRIES
) {
270 fprintf(stderr
, "mail: couldn't lock maildrop for user %s\n",
280 (void) chown(mailbox
, pw
->pw_uid
, pw
->pw_gid
);
281 boxfp
= fdopen(dropfd
, "a");
283 boxfp
= fopen(mailbox
, "a");
285 if (NULL
== boxfp
|| stat(mailbox
, &stb
) < 0) {
286 fprintf(stderr
, "mail: serious maildrop problems for %s\n", vec
[i
]);
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
]);
300 "-------------\n| Mail from %s to %s\n-------------\n",
304 fprintf(boxfp
, "From %s %24.24s\n", sender
, ctime(&now
));
307 /* Add the To: header line */
308 fprintf(boxfp
, "To: %s\n", vec
[i
]);
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");
318 /* Add the Subject: header line */
319 if (subject
!= NULL
) fprintf(boxfp
, "Subject: %s\n", subject
);
321 fprintf(boxfp
, "\n");
323 if ((copy(mailfp
, boxfp
) < 0) || (fclose(boxfp
) != 0)) {
324 fprintf(stderr
, "mail: error delivering to user %s", vec
[i
]);
333 /* Put signals back the way they were */
334 signal(SIGINT
, sigint
);
335 signal(SIGHUP
, sighup
);
336 signal(SIGQUIT
, sigquit
);
338 return(0 == errs
) ? 0 : -1;
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).
346 FILE *makerewindable()
348 FILE *tempfp
; /* temp file used for copy */
349 int c
; /* character being copied */
350 int state
; /* ".\n" detection state */
352 if (NULL
== (tempfp
= fopen(tempname
, "w"))) {
353 fprintf(stderr
, "mail: can't create temporary file\n");
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. */
361 while (EOF
!= (c
= getc(stdin
))) switch (state
) {
366 if ('\n' != c
) state
= '\0';
371 if ('\n' == c
) goto done
;
377 state
= ('\n' == c
) ? '\n' : '\0';
381 if (ferror(tempfp
) || fclose(tempfp
)) {
382 fprintf(stderr
, "mail: couldn't copy letter to temporary file\n");
385 tempfp
= freopen(tempname
, "r", stdin
);
386 unlink(tempname
); /* unlink name; file lingers on in limbo */
390 int copy(fromfp
, tofp
)
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 ";
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. */
405 while (EOF
!= (c
= getc(fromfp
))) {
408 if ('.' == c
) /* '.' at BOL */
410 else if (*p
== c
) { /* start of postmark */
413 } else { /* anything else */
424 if ('\n' == c
) goto done
;
431 if (*++p
== '\0') { /* successfully reached end */
434 fputs(postmark
, tofp
);
438 break; /* not there yet */
440 state
= ('\n' == c
) ? '\n' : '\0';
441 for (q
= postmark
; q
< p
; ++q
) putc(*q
, tofp
);
447 state
= ('\n' == c
) ? '\n' : '\0';
451 if ('\n' != state
) putc('\n', tofp
);
453 if (!blankline
) putc('\n', tofp
);
454 if (ferror(tofp
)) return -1;
464 firstlet
= lastlet
= NULL
;
466 if (access(mailbox
, 4) < 0 || NULL
== (boxfp
= fopen(mailbox
, "r"))) {
467 if (usedrop
&& ENOENT
== errno
) return;
468 fprintf(stderr
, "can't access mailbox ");
474 if (NULL
== fgets(linebuf
, sizeof linebuf
, boxfp
)) break;
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");
481 if (NULL
== lastlet
) {
491 let
->status
= UNREAD
;
492 let
->location
= current
;
493 D(printf("letter at %ld\n", current
));
495 current
+= strlen(linebuf
);
503 let
= reversemode
? firstlet
: lastlet
;
506 printf("No mail.\n");
509 while (NULL
!= let
) {
510 printlet(let
, stdout
);
511 let
= reversemode
? let
->next
: let
->prev
;
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 */
523 if (NULL
== firstlet
) {
524 printf("No mail.\n");
527 let
= reversemode
? firstlet
: lastlet
;
530 next
= reversemode
? let
->next
: let
->prev
;
531 if (NULL
== next
) next
= let
;
534 interrupted
= setjmp(printjump
);
535 signal(SIGINT
, onint
);
537 if (!interrupted
&& needprint
) {
538 if (DELETED
!= let
->status
) let
->status
= READ
;
539 printlet(let
, stdout
);
541 if (interrupted
) putchar('\n');
543 fputs(PROMPT
, stdout
);
546 if (fgets(linebuf
, sizeof linebuf
, stdin
) == NULL
) break;
548 if (!quitmode
) signal(SIGINT
, SIG_IGN
);
550 switch (linebuf
[0]) {
556 let
->status
= DELETED
;
557 if (next
!= let
)/* look into this */
566 next
= reversemode
? let
->prev
: let
->next
;
567 if (NULL
== next
) next
= let
;
572 for (savefile
= strtok(linebuf
+ 1, " \t\n");
574 savefile
= strtok((char *) NULL
, " \t\n")) {
575 savelet(let
, savefile
);
579 doshell(linebuf
+ 1);
589 fprintf(stderr
, "Illegal command\n");
596 int dummy
; /* to satisfy ANSI compilers */
598 longjmp(printjump
, 1);
601 void savelet(let
, savefile
)
608 if ((pid
= fork()) < 0) {
609 perror("mail: couldn't fork");
611 } else if (pid
!= 0) { /* parent */
619 if ((savefp
= fopen(savefile
, "a")) == NULL
) {
623 printlet(let
, savefp
);
624 if ((ferror(savefp
) != 0) | (fclose(savefp
) != 0)) {
625 fprintf(stderr
, "savefile write error:");
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 */
639 sprintf(lockname
, LOCKNAME
, whoami());
641 if (NULL
== (tempfp
= fopen(tempname
, "w"))) {
642 perror("mail: can't create temporary file");
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
));
652 if (ferror(tempfp
) || NULL
== (tempfp
= freopen(tempname
, "r", tempfp
))) {
653 perror("mail: temporary file write error");
658 /* Shut off signals during the update */
659 signal(SIGINT
, SIG_IGN
);
660 signal(SIGHUP
, SIG_IGN
);
661 signal(SIGQUIT
, SIG_IGN
);
663 if (usedrop
) while (link(mailbox
, lockname
) != 0) {
664 if (++locktries
>= LOCKTRIES
) {
665 fprintf(stderr
, "mail: couldn't lock maildrop for update\n");
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
);
679 while ((c
= getc(tempfp
)) != EOF
) putc(c
, boxfp
);
683 if (usedrop
) unlink(lockname
);
686 void printlet(let
, tofp
)
690 off_t current
, limit
;
693 fseek(boxfp
, (current
= let
->location
), 0);
694 limit
= (NULL
!= let
->next
) ? let
->next
->location
: -1;
696 while (current
!= limit
&& (c
= getc(boxfp
)) != EOF
) {
702 void doshell(command
)
708 if (NULL
== (shell
= getenv("SHELL"))) shell
= SHELL
;
710 if ((pid
= fork()) < 0) {
711 perror("mail: couldn't fork");
713 } else if (pid
!= 0) { /* parent */
723 execl(shell
, shell
, "-c", command
, (char *) NULL
);
724 fprintf(stderr
, "can't exec shell\n");
730 fprintf(stderr
, "usage: mail [-epqr] [-f file]\n");
731 fprintf(stderr
, " mail [-dtv] [-s subject] user [...]\n");
739 if (NULL
== (p
= rindex(name
, '/')))
749 if (NULL
!= (pw
= getpwuid(getuid())))
760 if ( (fp
= fopen(HELPFILE
, "r")) == NULL
) {
761 fprintf(stdout
, "can't open helpfile %s\n", HELPFILE
);
765 while (fgets(buffer
, 80, fp
))
766 fputs(buffer
, stdout
);
776 if (stat(name
, &buf
) == -1)
779 return (buf
.st_size
? 1 : 0);