BSD 3 development
[unix-history] / usr / src / cmd / mail.c
CommitLineData
7f0f90d9
KS
1#include <ctype.h>
2#include <stdio.h>
3#include <pwd.h>
4#include <utmp.h>
5#include <signal.h>
6#include <sys/types.h>
7#include <sys/stat.h>
8#include <setjmp.h>
9#include <whoami.h>
10
11/*copylet flags */
12 /*remote mail, add rmtmsg */
13#define REMOTE 1
14 /* zap header and trailing empty line */
15#define ZAP 3
16#define ORDINARY 2
17#define FORWARD 4
18#define LSIZE 256
19#define MAXLET 300 /* maximum number of letters */
20#define MAILMODE (~0644) /* mode of created mail */
21#define RMAIL "/usr/net/bin/sendberkmail"
22#define LOCNAM1 "csvax"
23#define LOCNAM2 "ucbvax"
24#define LOCNAM3 "vax"
25#define LOCNAM4 "v"
26
27char line[LSIZE];
28char resp[LSIZE];
29struct let {
30 long adr;
31 char change;
32} let[MAXLET];
33int nlet = 0;
34char lfil[50];
35long iop, time();
36char *getenv();
37char *index();
38char lettmp[] = "/tmp/maXXXXX";
39char maildir[] = "/usr/spool/mail/";
40char mailfile[] = "/usr/spool/mail/xxxxxxxxxxxxxxxxxxxxxxx";
41char dead[] = "dead.letter";
42char *thissys = sysname;
43char *netname = "vax";
44char forwmsg[] = " forwarded\n";
45FILE *tmpf;
46FILE *malf;
47char *my_name;
48char *getlogin();
49struct passwd *getpwuid();
50int error;
51int changed;
52int forward;
53char from[] = "From ";
54long ftell();
55int delete();
56char *ctime();
57int flgf;
58int flgp;
59int delflg = 1;
60int hseqno;
61jmp_buf sjbuf;
62
63main(argc, argv)
64char **argv;
65{
66 register i;
67 char sobuf[BUFSIZ];
68
69 setbuf(stdout, sobuf);
70 mktemp(lettmp);
71 unlink(lettmp);
72 my_name = getlogin();
73 if (my_name == NULL || strlen(my_name) == 0) {
74 struct passwd *pwent;
75 pwent = getpwuid(getuid());
76 if (pwent==NULL)
77 my_name = "???";
78 else
79 my_name = pwent->pw_name;
80 }
81 if(setjmp(sjbuf)) done();
82 for (i=0; i<20; i++)
83 setsig(i, delete);
84 tmpf = fopen(lettmp, "w");
85 if (tmpf == NULL) {
86 fprintf(stderr, "mail: cannot open %s for writing\n", lettmp);
87 done();
88 }
89 if (argv[0][0] != 'r' && /* no favors for rmail*/
90 (argc == 1 || argv[1][0] == '-' && !any(argv[1][1], "rh")))
91 printmail(argc, argv);
92 else
93 sendmail(argc, argv);
94 done();
95}
96
97setsig(i, f)
98int i;
99int (*f)();
100{
101 if(signal(i, SIG_IGN)!=SIG_IGN)
102 signal(i, f);
103}
104
105any(c, str)
106 register int c;
107 register char *str;
108{
109
110 while (*str)
111 if (c == *str++)
112 return(1);
113 return(0);
114}
115
116printmail(argc, argv)
117char **argv;
118{
119 int flg, i, j, print;
120 char *p, *getarg();
121
122 setuid(getuid());
123 cat(mailfile, maildir, my_name);
124 for (; argc>1; argv++, argc--) {
125 if (argv[1][0]=='-') {
126 if (argv[1][1]=='q')
127 delflg = 0;
128 else if (argv[1][1]=='p') {
129 flgp++;
130 delflg = 0;
131 } else if (argv[1][1]=='f') {
132 if (argc>=3) {
133 strcpy(mailfile, argv[2]);
134 argv++;
135 argc--;
136 }
137 } else if (argv[1][1]=='r') {
138 forward = 1;
139 } else if (argv[1][1]=='h') {
140 forward = 1;
141 } else {
142 fprintf(stderr, "mail: unknown option %c\n", argv[1][1]);
143 done();
144 }
145 } else
146 break;
147 }
148 malf = fopen(mailfile, "r");
149 if (malf == NULL) {
150 fprintf(stdout, "No mail.\n");
151 return;
152 }
153 lock(mailfile);
154 copymt(malf, tmpf);
155 fclose(malf);
156 fclose(tmpf);
157 unlock();
158 tmpf = fopen(lettmp, "r");
159
160 changed = 0;
161 print = 1;
162 for (i = 0; i < nlet; ) {
163 j = forward ? i : nlet - i - 1;
164 if(setjmp(sjbuf)) {
165 print=0;
166 } else {
167 if (print)
168 copylet(j, stdout, ORDINARY);
169 print = 1;
170 }
171 if (flgp) {
172 i++;
173 continue;
174 }
175 setjmp(sjbuf);
176 fprintf(stdout, "? ");
177 fflush(stdout);
178 if (fgets(resp, LSIZE, stdin) == NULL)
179 break;
180 switch (resp[0]) {
181
182 default:
183 fprintf(stderr, "usage\n");
184 case '?':
185 print = 0;
186 fprintf(stderr, "q\tquit\n");
187 fprintf(stderr, "x\texit without changing mail\n");
188 fprintf(stderr, "p\tprint\n");
189 fprintf(stderr, "s[file]\tsave (default mbox)\n");
190 fprintf(stderr, "w[file]\tsame without header\n");
191 fprintf(stderr, "-\tprint previous\n");
192 fprintf(stderr, "d\tdelete\n");
193 fprintf(stderr, "+\tnext (no delete)\n");
194 fprintf(stderr, "m user\tmail to user\n");
195 fprintf(stderr, "! cmd\texecute cmd\n");
196 break;
197
198 case '+':
199 case 'n':
200 case '\n':
201 i++;
202 break;
203 case 'x':
204 changed = 0;
205 case 'q':
206 goto donep;
207 case 'p':
208 break;
209 case '^':
210 case '-':
211 if (--i < 0)
212 i = 0;
213 break;
214 case 'y':
215 case 'w':
216 case 's':
217 flg = 0;
218 if (resp[1] != '\n' && resp[1] != ' ') {
219 printf("illegal\n");
220 flg++;
221 print = 0;
222 continue;
223 }
224 if (resp[1] == '\n' || resp[1] == '\0') {
225 p = getenv("HOME");
226 if(p != 0)
227 cat(resp+1, p, "/mbox");
228 else
229 cat(resp+1, "", "mbox");
230 }
231 for (p = resp+1; (p = getarg(lfil, p)) != NULL; ) {
232 malf = fopen(lfil, "a");
233 if (malf == NULL) {
234 fprintf(stdout, "mail: cannot append to %s\n", lfil);
235 flg++;
236 continue;
237 }
238 copylet(j, malf, resp[0]=='w'? ZAP: ORDINARY);
239 fclose(malf);
240 }
241 if (flg)
242 print = 0;
243 else {
244 let[j].change = 'd';
245 changed++;
246 i++;
247 }
248 break;
249 case 'm':
250 flg = 0;
251 if (resp[1] == '\n' || resp[1] == '\0') {
252 i++;
253 continue;
254 }
255 if (resp[1] != ' ') {
256 printf("invalid command\n");
257 flg++;
258 print = 0;
259 continue;
260 }
261 for (p = resp+1; (p = getarg(lfil, p)) != NULL; )
262 if (!sendrmt(j, lfil, "/bin/mail")) /* couldn't send it */
263 flg++;
264 if (flg)
265 print = 0;
266 else {
267 let[j].change = 'd';
268 changed++;
269 i++;
270 }
271 break;
272 case '!':
273 system(resp+1);
274 printf("!\n");
275 print = 0;
276 break;
277 case 'd':
278 let[j].change = 'd';
279 changed++;
280 i++;
281 if (resp[1] == 'q')
282 goto donep;
283 break;
284 }
285 }
286 donep:
287 if (changed)
288 copyback();
289}
290
291copyback() /* copy temp or whatever back to /usr/spool/mail */
292{
293 register i, n, c;
294 int new = 0;
295 struct stat stbuf;
296
297 signal(SIGINT, SIG_IGN);
298 signal(SIGHUP, SIG_IGN);
299 signal(SIGQUIT, SIG_IGN);
300 lock(mailfile);
301 stat(mailfile, &stbuf);
302 if (stbuf.st_size != let[nlet].adr) { /* new mail has arrived */
303 malf = fopen(mailfile, "r");
304 if (malf == NULL) {
305 fprintf(stdout, "mail: can't re-read %s\n", mailfile);
306 done();
307 }
308 fseek(malf, let[nlet].adr, 0);
309 fclose(tmpf);
310 tmpf = fopen(lettmp, "a");
311 fseek(tmpf, let[nlet].adr, 0);
312 while ((c = fgetc(malf)) != EOF)
313 fputc(c, tmpf);
314 fclose(malf);
315 fclose(tmpf);
316 tmpf = fopen(lettmp, "r");
317 let[++nlet].adr = stbuf.st_size;
318 new = 1;
319 }
320 malf = fopen(mailfile, "w");
321 if (malf == NULL) {
322 fprintf(stderr, "mail: can't rewrite %s\n", lfil);
323 done();
324 }
325 n = 0;
326 for (i = 0; i < nlet; i++)
327 if (let[i].change != 'd') {
328 copylet(i, malf, ORDINARY);
329 n++;
330 }
331 fclose(malf);
332 if (new)
333 fprintf(stdout, "new mail arrived\n");
334 unlock();
335}
336
337copymt(f1, f2) /* copy mail (f1) to temp (f2) */
338FILE *f1, *f2;
339{
340 long nextadr;
341
342 nlet = nextadr = 0;
343 let[0].adr = 0;
344 while (fgets(line, LSIZE, f1) != NULL) {
345 if (isfrom(line))
346 let[nlet++].adr = nextadr;
347 nextadr += strlen(line);
348 fputs(line, f2);
349 }
350 let[nlet].adr = nextadr; /* last plus 1 */
351}
352
353copylet(n, f, type) FILE *f;
354{ int ch, k;
355 fseek(tmpf, let[n].adr, 0);
356 k = let[n+1].adr - let[n].adr;
357 while(k-- > 1 && (ch=fgetc(tmpf))!='\n')
358 if(type!=ZAP) fputc(ch,f);
359 if(type==REMOTE)
360 fprintf(f, " remote from %s\n", thissys);
361 else if (type==FORWARD)
362 fprintf(f, forwmsg);
363 else if(type==ORDINARY)
364 fputc(ch,f);
365 while(k-->1)
366 fputc(ch=fgetc(tmpf), f);
367 if(type!=ZAP || ch!= '\n')
368 fputc(fgetc(tmpf), f);
369}
370
371isfrom(lp)
372register char *lp;
373{
374 register char *p;
375
376 for (p = from; *p; )
377 if (*lp++ != *p++)
378 return(0);
379 return(1);
380}
381
382sendmail(argc, argv)
383char **argv;
384{
385 char truename[100];
386 int first;
387 register char *cp;
388 int gaver = 0;
389
390 truename[0] = 0;
391 line[0] = '\0';
392
393 /*
394 * When we fall out of this, argv[1] should be first name,
395 * argc should be number of names + 1.
396 */
397
398 while (argc > 1 && *argv[1] == '-') {
399 cp = *++argv;
400 argc--;
401 switch (cp[1]) {
402 case 'r':
403 if (argc <= 0) {
404 usage();
405 done();
406 }
407 gaver++;
408 strcpy(truename, argv[1]);
409 fgets(line, LSIZE, stdin);
410 if (strcmpn("From", line, 4) == 0)
411 line[0] = '\0';
412 argv++;
413 argc--;
414 break;
415
416 case 'h':
417 if (argc <= 0) {
418 usage();
419 done();
420 }
421 hseqno = atoi(argv[1]);
422 argv++;
423 argc--;
424 break;
425
426 default:
427 usage();
428 done();
429 }
430 }
431 if (argc <= 1) {
432 usage();
433 done();
434 }
435 if (gaver == 0)
436 strcpy(truename, my_name);
437 /*
438 if (argc > 4 && strcmp(argv[1], "-r") == 0) {
439 strcpy(truename, argv[2]);
440 argc -= 2;
441 argv += 2;
442 fgets(line, LSIZE, stdin);
443 if (strcmpn("From", line, 4) == 0)
444 line[0] = '\0';
445 } else
446 strcpy(truename, my_name);
447 */
448 time(&iop);
449 fprintf(tmpf, "%s%s %s", from, truename, ctime(&iop));
450 iop = ftell(tmpf);
451 flgf = 1;
452 for (first = 1;; first = 0) {
453 if (first && line[0] == '\0' && fgets(line, LSIZE, stdin) == NULL)
454 break;
455 if (!first && fgets(line, LSIZE, stdin) == NULL)
456 break;
457 if (line[0] == '.' && line[1] == '\n' && isatty(fileno(stdin)))
458 break;
459 if (isfrom(line))
460 fputs(">", tmpf);
461 fputs(line, tmpf);
462 flgf = 0;
463 }
464 fputs("\n", tmpf);
465 nlet = 1;
466 let[0].adr = 0;
467 let[1].adr = ftell(tmpf);
468 fclose(tmpf);
469 if (flgf)
470 return;
471 tmpf = fopen(lettmp, "r");
472 if (tmpf == NULL) {
473 fprintf(stderr, "mail: cannot reopen %s for reading\n", lettmp);
474 return;
475 }
476 while (--argc > 0)
477 if (!send(0, *++argv, truename))
478 error++;
479 if (error) {
480 setuid(getuid());
481 malf = fopen(dead, "w");
482 if (malf == NULL) {
483 fprintf(stdout, "mail: cannot open %s\n", dead);
484 fclose(tmpf);
485 return;
486 }
487 copylet(0, malf, ZAP);
488 fclose(malf);
489 fprintf(stdout, "Mail saved in %s\n", dead);
490 }
491 fclose(tmpf);
492}
493
494sendrmt(n, name, rcmd)
495char *name;
496char *rcmd;
497{
498 FILE *rmf, *popen();
499 register char *p;
500 char rsys[64], cmd[64];
501 register local, pid;
502 int sts;
503
504 local = 0;
505 if (index(name, '^')) {
506 while (p = index(name, '^'))
507 *p = '!';
508 if (strncmp(name, "researc", 7)) {
509 strcpy(rsys, "research");
510 if (*name != '!')
511 --name;
512 goto skip;
513 }
514 }
515 if (*name=='!')
516 name++;
517 for(p=rsys; *name!='!'; *p++ = *name++)
518 if (*name=='\0') {
519 local++;
520 break;
521 }
522 *p = '\0';
523 if ((!local && *name=='\0') || (local && *rsys=='\0')) {
524 fprintf(stdout, "null name\n");
525 return(0);
526 }
527skip:
528 if ((pid = fork()) == -1) {
529 fprintf(stderr, "mail: can't create proc for remote\n");
530 return(0);
531 }
532 if (pid) {
533 while (wait(&sts) != pid) {
534 if (wait(&sts)==-1)
535 return(0);
536 }
537 return(!sts);
538 }
539 setuid(getuid());
540 if (local)
541 sprintf(cmd, "%s %s", rcmd, rsys);
542 else {
543 if (index(name+1, '!'))
544 sprintf(cmd, "uux - %s!rmail \\(%s\\)", rsys, name+1);
545 else
546 sprintf(cmd, "uux - %s!rmail %s", rsys, name+1);
547 }
548 if ((rmf=popen(cmd, "w")) == NULL)
549 exit(1);
550 copylet(n, rmf, local ? !strcmp(rcmd, "/bin/mail") ? FORWARD : ORDINARY : REMOTE);
551 pclose(rmf);
552 exit(0);
553}
554
555/*
556 * Send mail on the Berkeley network.
557 * Sorry Bill, sendrmt() is so awful we just gave up.
558 */
559
560sendberkmail(n, name, fromaddr)
561 char name[];
562 char fromaddr[];
563{
564 char cmd[200];
565 register FILE *cmdf;
566
567 sprintf(cmd, "%s -h %d -f %s -t %s", RMAIL, hseqno, fromaddr, name);
568 if ((cmdf = popen(cmd, "w")) == NULL) {
569 perror(RMAIL);
570 return(0);
571 }
572 copylet(n, cmdf, ORDINARY);
573 pclose(cmdf);
574 return(9);
575}
576
577usage()
578{
579
580 fprintf(stderr, "Usage: mail [ -f ] people . . .\n");
581}
582
583send(n, name, fromaddr)
584int n;
585char *name;
586char *fromaddr;
587{
588 char file[50];
589 register char *p;
590 register mask;
591 struct passwd *pw, *getpwnam();
592
593 stripfx(LOCNAM1, &name);
594 stripfx(LOCNAM2, &name);
595 stripfx(LOCNAM3, &name);
596 stripfx(LOCNAM4, &name);
597 if(*name == ':')name++; /* skip colon in to-name */
598 for(p=name; *p!=':' &&*p!='\0'; p++);
599 /* if(*p == ':') return(sendrmt(n, name, RMAIL)); */
600 if (*p == ':')
601 return(sendberkmail(n, name, fromaddr));
602 else if (strcmp(name, "msgs") == 0) return(sendrmt(n, "-s", "/usr/ucb/msgs"));
603 for(p=name; *p!='!'&&*p!='^' &&*p!='\0'; p++)
604 ;
605 if (*p == '!'|| *p=='^')
606 return(sendrmt(n, name, 0));
607 if ((pw = getpwnam(name)) == NULL) {
608 fprintf(stdout, "mail: can't send to %s\n", name);
609 return(0);
610 }
611 cat(file, maildir, name);
612 mask = umask(MAILMODE);
613 malf = fopen(file, "a");
614 umask(mask);
615 if (malf == NULL) {
616 fprintf(stdout, "mail: cannot append to %s\n", file);
617 return(0);
618 }
619 lock(file);
620 chown(file, pw->pw_uid, pw->pw_gid);
621 copylet(n, malf, ORDINARY);
622 fclose(malf);
623 unlock();
624 return(1);
625}
626
627delete(i)
628{
629 setsig(i, delete);
630 fprintf(stderr, "\n");
631 if(delflg)
632 longjmp(sjbuf, 1);
633 done();
634}
635
636/*
637 * Lock the specified mail file by setting the file mailfile.lock.
638 * We must, of course, be careful to unlink the lock file by a call
639 * to unlock before we stop. The algorithm used here is to see if
640 * the lock exists, and if it does, to check its modify time. If it
641 * is older than 30 seconds, we assume error and set our own file.
642 * Otherwise, we wait for 5 seconds and try again.
643 */
644
645char *maillock = ".lock"; /* Lock suffix for mailname */
646char *lockname = "/usr/spool/mail/tmXXXXXX";
647char locktmp[30]; /* Usable lock temporary */
648char curlock[50]; /* Last used name of lock */
649int locked; /* To note that we locked it */
650
651lock(file)
652char *file;
653{
654 register int f;
655 struct stat sbuf;
656 long curtime;
657 int statfailed;
658
659 if (locked || flgf)
660 return(0);
661 strcpy(curlock, file);
662 strcat(curlock, maillock);
663 strcpy(locktmp, lockname);
664 mktemp(locktmp);
665 unlink(locktmp);
666 statfailed = 0;
667 for (;;) {
668 f = lock1(locktmp, curlock);
669 if (f == 0) {
670 locked = 1;
671 return(0);
672 }
673 if (stat(curlock, &sbuf) < 0) {
674 if (statfailed++ > 5)
675 return(-1);
676 sleep(5);
677 continue;
678 }
679 statfailed = 0;
680 time(&curtime);
681 if (curtime < sbuf.st_ctime + 30) {
682 sleep(5);
683 continue;
684 }
685 unlink(curlock);
686 }
687}
688
689/*
690 * Remove the mail lock, and note that we no longer
691 * have it locked.
692 */
693
694unlock()
695{
696
697 unlink(curlock);
698 locked = 0;
699}
700
701/*
702 * Attempt to set the lock by creating the temporary file,
703 * then doing a link/unlink. If it fails, return -1 else 0
704 */
705
706lock1(tempfile, name)
707 char tempfile[], name[];
708{
709 register int fd;
710
711 fd = creat(tempfile, 0);
712 if (fd < 0)
713 return(-1);
714 close(fd);
715 if (link(tempfile, name) < 0) {
716 unlink(tempfile);
717 return(-1);
718 }
719 unlink(tempfile);
720 return(0);
721}
722
723done()
724{
725 if(locked)
726 unlock();
727 unlink(lettmp);
728 unlink(locktmp);
729 exit(error);
730}
731
732cat(to, from1, from2)
733char *to, *from1, *from2;
734{
735 int i, j;
736
737 j = 0;
738 for (i=0; from1[i]; i++)
739 to[j++] = from1[i];
740 for (i=0; from2[i]; i++)
741 to[j++] = from2[i];
742 to[j] = 0;
743}
744
745char *getarg(s, p) /* copy p... into s, update p */
746register char *s, *p;
747{
748 while (*p == ' ' || *p == '\t')
749 p++;
750 if (*p == '\n' || *p == '\0')
751 return(NULL);
752 while (*p != ' ' && *p != '\t' && *p != '\n' && *p != '\0')
753 *s++ = *p++;
754 *s = '\0';
755 return(p);
756}
757/*
758 stripfx(prefix string, pointer to string)
759
760 takes a ptr to string and compares it to prefix string.
761 may be called multiple times
762*/
763stripfx(pfx, name)
764 char *pfx;
765 char **name;
766{
767 register char *cp = *name;
768
769 while (*pfx && (*cp == *pfx || *cp == toupper(*pfx)))
770 cp++, pfx++;
771 if (*cp != ':' || *pfx != 0)
772 return;
773 *name = cp;
774}