1 /*
2 * Copyright (c) 1998 Sendmail, Inc. All rights reserved.
3 * Copyright (c) 1990, 1993, 1994
4 * The Regents of the University of California. All rights reserved.
5 *
6 * By using this file, you agree to the terms and conditions set
7 * forth in the LICENSE file which can be found at the top level
8 * of the sendmail distribution.
9 */
10
11 /*
12 * Copyright 1994-2007 Sun Microsystems, Inc. All rights reserved.
13 * Use is subject to license terms.
14 */
15
16 #ifndef lint
17 static char copyright[] =
18 "@(#) Copyright (c) 1990, 1993, 1994\n\
19 The Regents of the University of California. All rights reserved.\n";
20 #endif /* not lint */
21
22 #ifndef lint
23 static char sccsid[] = "@(#)mail.local.c 8.83 (Berkeley) 12/17/98";
24 static char sccsi2[] = "%W% (Sun) %G%";
25 #endif /* not lint */
26
27 #include <sys/param.h>
28 #include <sys/stat.h>
29 #include <sys/socket.h>
30 #include <sys/file.h>
31
32 #include <netinet/in.h>
33
34 #include <errno.h>
35 #include <fcntl.h>
36 #include <netdb.h>
37 #include <pwd.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <signal.h>
41 #include <ctype.h>
42 #include <string.h>
43 #include <sysexits.h>
44 #include <time.h>
45 #include <unistd.h>
46 #include <maillock.h>
47 #include <grp.h>
48
49 #ifdef __STDC__
50 #include <stdarg.h>
51 #else
52 #include <varargs.h>
53 #endif
54
55 #include <syslog.h>
56
57 #include <sysexits.h>
58 #include <ctype.h>
59
60 #include <sm/conf.h>
61 #include <sendmail/pathnames.h>
62
63 /*
64 ** If you don't have flock, you could try using lockf instead.
65 */
66
67 #ifdef LDA_USE_LOCKF
68 # define flock(a, b) lockf(a, b, 0)
69 # ifdef LOCK_EX
70 # undef LOCK_EX
71 # endif /* LOCK_EX */
72 # define LOCK_EX F_LOCK
73 #endif /* LDA_USE_LOCKF */
74
75 #ifndef LOCK_EX
76 # include <sys/file.h>
77 #endif /* ! LOCK_EX */
78
79 #ifndef MAILER_DAEMON
80 # define MAILER_DAEMON "MAILER-DAEMON"
81 #endif
82
83 typedef int bool;
84
85 #define FALSE 0
86 #define TRUE 1
87
88 bool EightBitMime = TRUE; /* advertise 8BITMIME in LMTP */
89 static int eval = EX_OK; /* sysexits.h error value. */
90 static int lmtpmode = 0;
91 bool bouncequota = FALSE; /* permanent error when over quota */
92
93 #define _PATH_MAILDIR "/var/mail"
94 #define _PATH_LOCTMP "/tmp/local.XXXXXX"
95 #define _PATH_LOCHTMP "/tmp/lochd.XXXXXX"
96 #define FALSE 0
97 #define TRUE 1
98 #define MAXLINE 2048
99
100 static void deliver(int, int, char *, bool);
101 static void e_to_sys(int);
102 static void err(const char *fmt, ...);
103 static void notifybiff(char *);
104 static void store(char *, int);
105 static void usage(void);
106 static void vwarn();
107 static void warn(const char *fmt, ...);
108 static void mailerr(const char *, const char *, ...);
109 static void sigterm_handler();
110
111 static char unix_from_line[MAXLINE];
112 static int ulen;
113 static int content_length;
114 static int bfd, hfd; /* temp file */
115 static uid_t src_uid, targ_uid, saved_uid;
116 static int sigterm_caught;
117
118 int
main(argc,argv)119 main(argc, argv)
120 int argc;
121 char *argv[];
122 {
123 struct passwd *pw;
124 int ch;
125 uid_t uid;
126 char *from;
127 struct group *grpptr;
128 void dolmtp();
129
130 openlog("mail.local", 0, LOG_MAIL);
131
132 from = NULL;
133 pw = NULL;
134 sigterm_caught = FALSE;
135
136 (void) sigset(SIGTERM, sigterm_handler);
137
138 while ((ch = getopt(argc, argv, "7bdf:r:l")) != EOF)
139 switch (ch) {
140 case '7': /* Do not advertise 8BITMIME */
141 EightBitMime = FALSE;
142 break;
143
144 case 'b': /* bounce mail when over quota. */
145 bouncequota = TRUE;
146 break;
147
148 case 'd': /* Backward compatible. */
149 break;
150 case 'f':
151 case 'r': /* Backward compatible. */
152 if (from != NULL) {
153 warn("multiple -f options");
154 usage();
155 }
156 from = optarg;
157 break;
158 case 'l':
159 lmtpmode++;
160 break;
161 case '?':
162 default:
163 usage();
164 }
165 argc -= optind;
166 argv += optind;
167
168 notifybiff(NULL); /* initialize biff structures */
169
170 /*
171 * We expect sendmail will invoke us with saved id 0
172 * We then do setgid and setuid defore delivery
173 * setgid to mail group
174 */
175 if ((grpptr = getgrnam("mail")) != NULL)
176 (void) setgid(grpptr->gr_gid);
177 saved_uid = geteuid();
178
179 if (lmtpmode) {
180 if (saved_uid != 0) {
181 warn("only super-user can use -l option");
182 exit(EX_CANTCREAT);
183 }
184 dolmtp(bouncequota);
185 }
186
187 if (!*argv)
188 usage();
189
190 /*
191 * If from not specified, use the name from getlogin() if the
192 * uid matches, otherwise, use the name from the password file
193 * corresponding to the uid.
194 */
195 uid = getuid();
196 if (!from && (!(from = getlogin()) ||
197 !(pw = getpwnam(from)) || pw->pw_uid != uid))
198 from = (pw = getpwuid(uid)) ? pw->pw_name : "???";
199 src_uid = pw ? pw->pw_uid : uid;
200
201 /*
202 * There is no way to distinguish the error status of one delivery
203 * from the rest of the deliveries. So, if we failed hard on one
204 * or more deliveries, but had no failures on any of the others, we
205 * return a hard failure. If we failed temporarily on one or more
206 * deliveries, we return a temporary failure regardless of the other
207 * failures. This results in the delivery being reattempted later
208 * at the expense of repeated failures and multiple deliveries.
209 */
210
211 for (store(from, 0); *argv; ++argv)
212 deliver(hfd, bfd, *argv, bouncequota);
213 return (eval);
214 }
215
216 void
sigterm_handler()217 sigterm_handler()
218 {
219 sigterm_caught = TRUE;
220 (void) sigignore(SIGTERM);
221 }
222
223 char *
parseaddr(s)224 parseaddr(s)
225 char *s;
226 {
227 char *p;
228 int len;
229
230 if (*s++ != '<')
231 return NULL;
232
233 p = s;
234
235 /* at-domain-list */
236 while (*p == '@') {
237 p++;
238 if (*p == '[') {
239 p++;
240 while (isascii(*p) &&
241 (isalnum(*p) || *p == '.' ||
242 *p == '-' || *p == ':'))
243 p++;
244 if (*p++ != ']')
245 return NULL;
246 } else {
247 while ((isascii(*p) && isalnum(*p)) ||
248 strchr(".-_", *p))
249 p++;
250 }
251 if (*p == ',' && p[1] == '@')
252 p++;
253 else if (*p == ':' && p[1] != '@')
254 p++;
255 else
256 return NULL;
257 }
258
259 s = p;
260
261 /* local-part */
262 if (*p == '\"') {
263 p++;
264 while (*p && *p != '\"') {
265 if (*p == '\\') {
266 if (!*++p)
267 return NULL;
268 }
269 p++;
270 }
271 if (!*p++)
272 return NULL;
273 } else {
274 while (*p && *p != '@' && *p != '>') {
275 if (*p == '\\') {
276 if (!*++p)
277 return NULL;
278 } else {
279 if (*p <= ' ' || (*p & 128) ||
280 strchr("<>()[]\\,;:\"", *p))
281 return NULL;
282 }
283 p++;
284 }
285 }
286
287 /* @domain */
288 if (*p == '@') {
289 p++;
290 if (*p == '[') {
291 p++;
292 while (isascii(*p) &&
293 (isalnum(*p) || *p == '.' ||
294 *p == '-' || *p == ':'))
295 p++;
296 if (*p++ != ']')
297 return NULL;
298 } else {
299 while ((isascii(*p) && isalnum(*p)) ||
300 strchr(".-_", *p))
301 p++;
302 }
303 }
304
305 if (*p++ != '>')
306 return NULL;
307 if (*p && *p != ' ')
308 return NULL;
309 len = p - s - 1;
310
311 if (*s == '\0' || len <= 0)
312 {
313 s = MAILER_DAEMON;
314 len = strlen(s);
315 }
316
317 p = malloc(len + 1);
318 if (p == NULL) {
319 printf("421 4.3.0 memory exhausted\r\n");
320 exit(EX_TEMPFAIL);
321 }
322
323 strncpy(p, s, len);
324 p[len] = '\0';
325 return p;
326 }
327
328 char *
process_recipient(addr)329 process_recipient(addr)
330 char *addr;
331 {
332 if (getpwnam(addr) == NULL) {
333 return "550 5.1.1 user unknown";
334 }
335
336 return NULL;
337 }
338
339 #define RCPT_GROW 30
340
341 void
dolmtp(bouncequota)342 dolmtp(bouncequota)
343 bool bouncequota;
344 {
345 char *return_path = NULL;
346 char **rcpt_addr = NULL;
347 int rcpt_num = 0;
348 int rcpt_alloc = 0;
349 bool gotlhlo = FALSE;
350 char myhostname[MAXHOSTNAMELEN];
351 char buf[4096];
352 char *err;
353 char *p;
354 int i;
355
356 gethostname(myhostname, sizeof myhostname - 1);
357
358 printf("220 %s LMTP ready\r\n", myhostname);
359 for (;;) {
360 if (sigterm_caught) {
361 for (; rcpt_num > 0; rcpt_num--)
362 printf("451 4.3.0 shutting down\r\n");
363 exit(EX_OK);
364 }
365 fflush(stdout);
366 if (fgets(buf, sizeof(buf)-1, stdin) == NULL) {
367 exit(EX_OK);
368 }
369 p = buf + strlen(buf) - 1;
370 if (p >= buf && *p == '\n')
371 *p-- = '\0';
372 if (p >= buf && *p == '\r')
373 *p-- = '\0';
374
375 switch (buf[0]) {
376
377 case 'd':
378 case 'D':
379 if (strcasecmp(buf, "data") == 0) {
380 if (rcpt_num == 0) {
381 printf("503 5.5.1 No recipients\r\n");
382 continue;
383 }
384 store(return_path, rcpt_num);
385 if (bfd == -1 || hfd == -1)
386 continue;
387
388 for (i = 0; i < rcpt_num; i++) {
389 p = strchr(rcpt_addr[i], '+');
390 if (p != NULL)
391 *p++ = '\0';
392 deliver(hfd, bfd, rcpt_addr[i],
393 bouncequota);
394 }
395 close(bfd);
396 close(hfd);
397 goto rset;
398 }
399 goto syntaxerr;
400 /* NOTREACHED */
401 break;
402
403 case 'l':
404 case 'L':
405 if (strncasecmp(buf, "lhlo ", 5) == 0)
406 {
407 /* check for duplicate per RFC 1651 4.2 */
408 if (gotlhlo)
409 {
410 printf("503 %s Duplicate LHLO\r\n",
411 myhostname);
412 continue;
413 }
414 gotlhlo = TRUE;
415 printf("250-%s\r\n", myhostname);
416 if (EightBitMime)
417 printf("250-8BITMIME\r\n");
418 printf("250-ENHANCEDSTATUSCODES\r\n");
419 printf("250 PIPELINING\r\n");
420 continue;
421 }
422 goto syntaxerr;
423 /* NOTREACHED */
424 break;
425
426 case 'm':
427 case 'M':
428 if (strncasecmp(buf, "mail ", 5) == 0) {
429 if (return_path != NULL) {
430 printf("503 5.5.1 Nested MAIL command\r\n");
431 continue;
432 }
433 if (strncasecmp(buf+5, "from:", 5) != 0 ||
434 ((return_path = parseaddr(buf+10)) == NULL)) {
435 printf("501 5.5.4 Syntax error in parameters\r\n");
436 continue;
437 }
438 printf("250 2.5.0 ok\r\n");
439 continue;
440 }
441 goto syntaxerr;
442
443 case 'n':
444 case 'N':
445 if (strcasecmp(buf, "noop") == 0) {
446 printf("250 2.0.0 ok\r\n");
447 continue;
448 }
449 goto syntaxerr;
450
451 case 'q':
452 case 'Q':
453 if (strcasecmp(buf, "quit") == 0) {
454 printf("221 2.0.0 bye\r\n");
455 exit(EX_OK);
456 }
457 goto syntaxerr;
458
459 case 'r':
460 case 'R':
461 if (strncasecmp(buf, "rcpt ", 5) == 0) {
462 if (return_path == NULL) {
463 printf("503 5.5.1 Need MAIL command\r\n");
464 continue;
465 }
466 if (rcpt_num >= rcpt_alloc) {
467 rcpt_alloc += RCPT_GROW;
468 rcpt_addr = (char **)
469 realloc((char *)rcpt_addr,
470 rcpt_alloc * sizeof(char **));
471 if (rcpt_addr == NULL) {
472 printf("421 4.3.0 memory exhausted\r\n");
473 exit(EX_TEMPFAIL);
474 }
475 }
476 if (strncasecmp(buf+5, "to:", 3) != 0 ||
477 ((rcpt_addr[rcpt_num] = parseaddr(buf+8)) == NULL)) {
478 printf("501 5.5.4 Syntax error in parameters\r\n");
479 continue;
480 }
481 if ((err = process_recipient(rcpt_addr[rcpt_num])) != NULL) {
482 printf("%s\r\n", err);
483 continue;
484 }
485 rcpt_num++;
486 printf("250 2.1.5 ok\r\n");
487 continue;
488 }
489 else if (strcasecmp(buf, "rset") == 0) {
490 printf("250 2.0.0 ok\r\n");
491
492 rset:
493 while (rcpt_num > 0) {
494 free(rcpt_addr[--rcpt_num]);
495 }
496 if (return_path != NULL)
497 free(return_path);
498 return_path = NULL;
499 continue;
500 }
501 goto syntaxerr;
502
503 case 'v':
504 case 'V':
505 if (strncasecmp(buf, "vrfy ", 5) == 0) {
506 printf("252 2.3.3 try RCPT to attempt delivery\r\n");
507 continue;
508 }
509 goto syntaxerr;
510
511 default:
512 syntaxerr:
513 printf("500 5.5.2 Syntax error\r\n");
514 continue;
515 }
516 }
517 }
518
519 static void
store(from,lmtprcpts)520 store(from, lmtprcpts)
521 char *from;
522 int lmtprcpts;
523 {
524 FILE *fp = NULL;
525 time_t tval;
526 bool fullline = TRUE; /* current line is terminated */
527 bool prevfl; /* previous line was terminated */
528 char line[MAXLINE];
529 FILE *bfp, *hfp;
530 char *btn, *htn;
531 int in_header_section;
532 int newfd;
533
534 bfd = -1;
535 hfd = -1;
536 btn = strdup(_PATH_LOCTMP);
537 if ((bfd = mkstemp(btn)) == -1 || (bfp = fdopen(bfd, "w+")) == NULL) {
538 if (bfd != -1)
539 (void) close(bfd);
540 if (lmtprcpts) {
541 printf("451 4.3.0 unable to open temporary file\r\n");
542 return;
543 } else {
544 mailerr("451 4.3.0", "unable to open temporary file");
545 exit(eval);
546 }
547 }
548 (void) unlink(btn);
549 free(btn);
550
551 if (lmtpmode) {
552 printf("354 go ahead\r\n");
553 fflush(stdout);
554 }
555
556 htn = strdup(_PATH_LOCHTMP);
557 if ((hfd = mkstemp(htn)) == -1 || (hfp = fdopen(hfd, "w+")) == NULL) {
558 if (hfd != -1)
559 (void) close(hfd);
560 e_to_sys(errno);
561 err("unable to open temporary file");
562 }
563 (void) unlink(htn);
564 free(htn);
565
566 in_header_section = TRUE;
567 content_length = 0;
568 fp = hfp;
569
570 line[0] = '\0';
571 while (fgets(line, sizeof(line), stdin) != (char *)NULL)
572 {
573 size_t line_len = 0;
574 int peek;
575
576 prevfl = fullline; /* preserve state of previous line */
577 while (line[line_len] != '\n' && line_len < sizeof(line) - 2)
578 line_len++;
579 line_len++;
580
581 /* Check for dot-stuffing */
582 if (prevfl && lmtprcpts && line[0] == '.')
583 {
584 if (line[1] == '\n' ||
585 (line[1] == '\r' && line[2] == '\n'))
586 goto lmtpdot;
587 memcpy(line, line + 1, line_len);
588 line_len--;
589 }
590
591 /* Check to see if we have the full line from fgets() */
592 fullline = FALSE;
593 if (line_len > 0)
594 {
595 if (line[line_len - 1] == '\n')
596 {
597 if (line_len >= 2 &&
598 line[line_len - 2] == '\r')
599 {
600 line[line_len - 2] = '\n';
601 line[line_len - 1] = '\0';
602 line_len--;
603 }
604 fullline = TRUE;
605 }
606 else if (line[line_len - 1] == '\r')
607 {
608 /* Did we just miss the CRLF? */
609 peek = fgetc(stdin);
610 if (peek == '\n')
611 {
612 line[line_len - 1] = '\n';
613 fullline = TRUE;
614 }
615 else
616 (void) ungetc(peek, stdin);
617 }
618 }
619 else
620 fullline = TRUE;
621
622 if (prevfl && line[0] == '\n' && in_header_section) {
623 in_header_section = FALSE;
624 if (fflush(fp) == EOF || ferror(fp)) {
625 if (lmtprcpts) {
626 while (lmtprcpts--)
627 printf("451 4.3.0 temporary file write error\r\n");
628 fclose(fp);
629 return;
630 } else {
631 mailerr("451 4.3.0",
632 "temporary file write error");
633 fclose(fp);
634 exit(eval);
635 }
636 }
637 fp = bfp;
638 continue;
639 }
640
641 if (in_header_section) {
642 if (strncasecmp("Content-Length:", line, 15) == 0) {
643 continue; /* skip this header */
644 }
645 } else
646 content_length += strlen(line);
647 (void) fwrite(line, sizeof(char), line_len, fp);
648 if (ferror(fp)) {
649 if (lmtprcpts) {
650 while (lmtprcpts--)
651 printf("451 4.3.0 temporary file write error\r\n");
652 fclose(fp);
653 return;
654 } else {
655 mailerr("451 4.3.0",
656 "temporary file write error");
657 fclose(fp);
658 exit(eval);
659 }
660 }
661 }
662 if (sigterm_caught) {
663 if (lmtprcpts)
664 while (lmtprcpts--)
665 printf("451 4.3.0 shutting down\r\n");
666 else
667 mailerr("451 4.3.0", "shutting down");
668 fclose(fp);
669 exit(eval);
670 }
671
672 if (lmtprcpts) {
673 /* Got a premature EOF -- toss message and exit */
674 exit(EX_OK);
675 }
676
677 /* If message not newline terminated, need an extra. */
678 if (!strchr(line, '\n')) {
679 (void) putc('\n', fp);
680 content_length++;
681 }
682
683 lmtpdot:
684
685 /* Output a newline; note, empty messages are allowed. */
686 (void) putc('\n', fp);
687
688 if (fflush(fp) == EOF || ferror(fp)) {
689 if (lmtprcpts) {
690 while (lmtprcpts--) {
691 printf("451 4.3.0 temporary file write error\r\n");
692 }
693 fclose(fp);
694 return;
695 } else {
696 mailerr("451 4.3.0", "temporary file write error");
697 fclose(fp);
698 exit(eval);
699 }
700 }
701
702 if ((newfd = dup(bfd)) >= 0) {
703 fclose(bfp);
704 bfd = newfd;
705 }
706 if ((newfd = dup(hfd)) >= 0) {
707 fclose(hfp);
708 hfd = newfd;
709 }
710 (void) time(&tval);
711 (void) snprintf(unix_from_line, sizeof (unix_from_line), "From %s %s",
712 from, ctime(&tval));
713 ulen = strlen(unix_from_line);
714 }
715
716 static void
handle_error(err_num,bouncequota,path)717 handle_error(err_num, bouncequota, path)
718 int err_num;
719 bool bouncequota;
720 char *path;
721 {
722 #ifdef EDQUOT
723 if (err_num == EDQUOT && bouncequota) {
724 mailerr("552 5.2.2", "%s: %s", path, sm_errstring(err_num));
725 } else
726 #endif /* EDQUOT */
727 mailerr("450 4.2.0", "%s: %s", path, sm_errstring(err_num));
728 }
729
730 static void
deliver(hfd,bfd,name,bouncequota)731 deliver(hfd, bfd, name, bouncequota)
732 int hfd;
733 int bfd;
734 char *name;
735 bool bouncequota;
736 {
737 struct stat fsb, sb;
738 int mbfd = -1, nr, nw = 0, off;
739 char biffmsg[100], buf[8*1024], path[MAXPATHLEN];
740 off_t curoff, cursize;
741 int len;
742 struct passwd *pw = NULL;
743
744 /*
745 * Disallow delivery to unknown names -- special mailboxes
746 * can be handled in the sendmail aliases file.
747 */
748 if ((pw = getpwnam(name)) == NULL) {
749 eval = EX_TEMPFAIL;
750 mailerr("451 4.3.0", "cannot lookup name: %s", name);
751 return;
752 }
753 endpwent();
754
755 if (sigterm_caught) {
756 mailerr("451 4.3.0", "shutting down");
757 return;
758 }
759
760 /* mailbox may be NFS mounted, seteuid to user */
761 targ_uid = pw->pw_uid;
762 (void) seteuid(targ_uid);
763
764 if ((saved_uid != 0) && (src_uid != targ_uid)) {
765 /*
766 * If saved_uid == 0 (root), anything is OK; this is
767 * as it should be. But to prevent a random user from
768 * calling "mail.local foo" in an attempt to hijack
769 * foo's mail-box, make sure src_uid == targ_uid o/w.
770 */
771 warn("%s: wrong owner (is %d, should be %d)",
772 name, src_uid, targ_uid);
773 eval = EX_CANTCREAT;
774 return;
775 }
776
777 path[0] = '\0';
778 (void) snprintf(path, sizeof (path), "%s/%s", _PATH_MAILDIR, name);
779
780 /*
781 * If the mailbox is linked or a symlink, fail. There's an obvious
782 * race here, that the file was replaced with a symbolic link after
783 * the lstat returned, but before the open. We attempt to detect
784 * this by comparing the original stat information and information
785 * returned by an fstat of the file descriptor returned by the open.
786 *
787 * NB: this is a symptom of a larger problem, that the mail spooling
788 * directory is writeable by the wrong users. If that directory is
789 * writeable, system security is compromised for other reasons, and
790 * it cannot be fixed here.
791 *
792 * If we created the mailbox, set the owner/group. If that fails,
793 * just return. Another process may have already opened it, so we
794 * can't unlink it. Historically, binmail set the owner/group at
795 * each mail delivery. We no longer do this, assuming that if the
796 * ownership or permissions were changed there was a reason.
797 *
798 * XXX
799 * open(2) should support flock'ing the file.
800 */
801 tryagain:
802 /* should check lock status, but... maillock return no value */
803 maillock(name, 10);
804
805 if (sigterm_caught) {
806 mailerr("451 4.3.0", "shutting down");
807 goto err0;
808 }
809
810 if (lstat(path, &sb)) {
811 mbfd = open(path, O_APPEND|O_CREAT|O_EXCL|O_WRONLY,
812 S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP);
813 if (mbfd != -1)
814 (void) fchmod(mbfd, 0660);
815
816
817 if (mbfd == -1) {
818 if (errno == EEXIST) {
819 mailunlock();
820 goto tryagain;
821 }
822 }
823 } else if (sb.st_nlink != 1) {
824 mailerr("550 5.2.0", "%s: too many links", path);
825 goto err0;
826 } else if (!S_ISREG(sb.st_mode)) {
827 mailerr("550 5.2.0", "%s: irregular file", path);
828 goto err0;
829 } else {
830 mbfd = open(path, O_APPEND|O_WRONLY, 0);
831 if (mbfd != -1 &&
832 (fstat(mbfd, &fsb) || fsb.st_nlink != 1 ||
833 S_ISLNK(fsb.st_mode) || sb.st_dev != fsb.st_dev ||
834 sb.st_ino != fsb.st_ino)) {
835 eval = EX_TEMPFAIL;
836 mailerr("550 5.2.0",
837 "%s: fstat: file changed after open", path);
838 goto err1;
839 }
840 }
841
842 if (mbfd == -1) {
843 mailerr("450 4.2.0", "%s: %s", path, strerror(errno));
844 goto err0;
845 }
846
847 if (sigterm_caught) {
848 mailerr("451 4.3.0", "shutting down");
849 goto err0;
850 }
851
852 /* Get the starting offset of the new message for biff. */
853 curoff = lseek(mbfd, (off_t)0, SEEK_END);
854 (void) snprintf(biffmsg, sizeof (biffmsg), "%s@%ld\n", name, curoff);
855
856 /* Copy the message into the file. */
857 if (lseek(hfd, (off_t)0, SEEK_SET) == (off_t)-1) {
858 mailerr("450 4.2.0", "temporary file: %s", strerror(errno));
859 goto err1;
860 }
861 /* Copy the message into the file. */
862 if (lseek(bfd, (off_t)0, SEEK_SET) == (off_t)-1) {
863 mailerr("450 4.2.0", "temporary file: %s", strerror(errno));
864 goto err1;
865 }
866 if ((write(mbfd, unix_from_line, ulen)) != ulen) {
867 handle_error(errno, bouncequota, path);
868 goto err2;
869 }
870
871 if (sigterm_caught) {
872 mailerr("451 4.3.0", "shutting down");
873 goto err2;
874 }
875
876 while ((nr = read(hfd, buf, sizeof (buf))) > 0)
877 for (off = 0; off < nr; nr -= nw, off += nw)
878 if ((nw = write(mbfd, buf + off, nr)) < 0)
879 {
880 handle_error(errno, bouncequota, path);
881 goto err2;
882 }
883 if (nr < 0) {
884 handle_error(errno, bouncequota, path);
885 goto err2;
886 }
887
888 if (sigterm_caught) {
889 mailerr("451 4.3.0", "shutting down");
890 goto err2;
891 }
892
893 (void) snprintf(buf, sizeof (buf), "Content-Length: %d\n\n",
894 content_length);
895 len = strlen(buf);
896 if (write(mbfd, buf, len) != len) {
897 handle_error(errno, bouncequota, path);
898 goto err2;
899 }
900
901 if (sigterm_caught) {
902 mailerr("451 4.3.0", "shutting down");
903 goto err2;
904 }
905
906 while ((nr = read(bfd, buf, sizeof (buf))) > 0) {
907 for (off = 0; off < nr; nr -= nw, off += nw)
908 if ((nw = write(mbfd, buf + off, nr)) < 0) {
909 handle_error(errno, bouncequota, path);
910 goto err2;
911 }
912 if (sigterm_caught) {
913 mailerr("451 4.3.0", "shutting down");
914 goto err2;
915 }
916 }
917 if (nr < 0) {
918 handle_error(errno, bouncequota, path);
919 goto err2;
920 }
921
922 /* Flush to disk, don't wait for update. */
923 if (fsync(mbfd)) {
924 handle_error(errno, bouncequota, path);
925 err2: if (mbfd >= 0)
926 (void)ftruncate(mbfd, curoff);
927 err1: (void)close(mbfd);
928 err0: mailunlock();
929 (void)seteuid(saved_uid);
930 return;
931 }
932
933 /*
934 ** Save the current size so if the close() fails below
935 ** we can make sure no other process has changed the mailbox
936 ** between the failed close and the re-open()/re-lock().
937 ** If something else has changed the size, we shouldn't
938 ** try to truncate it as we may do more harm then good
939 ** (e.g., truncate a later message delivery).
940 */
941
942 if (fstat(mbfd, &sb) < 0)
943 cursize = 0;
944 else
945 cursize = sb.st_size;
946
947 /* Close and check -- NFS doesn't write until the close. */
948 if (close(mbfd))
949 {
950 handle_error(errno, bouncequota, path);
951 mbfd = open(path, O_WRONLY, 0);
952 if (mbfd < 0 ||
953 cursize == 0
954 || flock(mbfd, LOCK_EX) < 0 ||
955 fstat(mbfd, &sb) < 0 ||
956 sb.st_size != cursize ||
957 sb.st_nlink != 1 ||
958 !S_ISREG(sb.st_mode) ||
959 sb.st_dev != fsb.st_dev ||
960 sb.st_ino != fsb.st_ino ||
961 sb.st_uid != fsb.st_uid)
962 {
963 /* Don't use a bogus file */
964 if (mbfd >= 0)
965 {
966 (void) close(mbfd);
967 mbfd = -1;
968 }
969 }
970
971 /* Attempt to truncate back to pre-write size */
972 goto err2;
973 } else
974 notifybiff(biffmsg);
975
976 mailunlock();
977
978 (void)seteuid(saved_uid);
979
980 if (lmtpmode) {
981 printf("250 2.1.5 %s OK\r\n", name);
982 }
983 }
984
985 static void
notifybiff(msg)986 notifybiff(msg)
987 char *msg;
988 {
989 static struct sockaddr_in addr;
990 static int f = -1;
991 struct hostent *hp;
992 struct servent *sp;
993 int len;
994
995 if (msg == NULL) {
996 /* Be silent if biff service not available. */
997 if ((sp = getservbyname("biff", "udp")) == NULL)
998 return;
999 if ((hp = gethostbyname("localhost")) == NULL) {
1000 warn("localhost: %s", strerror(errno));
1001 return;
1002 }
1003 addr.sin_family = hp->h_addrtype;
1004 (void) memmove(&addr.sin_addr, hp->h_addr, hp->h_length);
1005 addr.sin_port = sp->s_port;
1006 return;
1007 }
1008
1009 if (addr.sin_family == 0)
1010 return; /* did not initialize */
1011
1012 if (f < 0 && (f = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
1013 warn("socket: %s", strerror(errno));
1014 return;
1015 }
1016 len = strlen(msg) + 1;
1017 if (sendto(f, msg, len, 0, (struct sockaddr *)&addr, sizeof (addr))
1018 != len)
1019 warn("sendto biff: %s", strerror(errno));
1020 }
1021
1022 static void
usage()1023 usage()
1024 {
1025 eval = EX_USAGE;
1026 err("usage: mail.local [-l] [-f from] user ...");
1027 }
1028
1029 static void
1030 /*VARARGS2*/
1031 #ifdef __STDC__
mailerr(const char * hdr,const char * fmt,...)1032 mailerr(const char *hdr, const char *fmt, ...)
1033 #else
1034 mailerr(hdr, fmt, va_alist)
1035 const char *hdr;
1036 const char *fmt;
1037 va_dcl
1038 #endif
1039 {
1040 va_list ap;
1041
1042 #ifdef __STDC__
1043 va_start(ap, fmt);
1044 #else
1045 va_start(ap);
1046 #endif
1047 if (lmtpmode)
1048 {
1049 if (hdr != NULL)
1050 printf("%s ", hdr);
1051 vprintf(fmt, ap);
1052 printf("\r\n");
1053 }
1054 else
1055 {
1056 e_to_sys(errno);
1057 vwarn(fmt, ap);
1058 }
1059 }
1060
1061 static void
1062 /*VARARGS1*/
1063 #ifdef __STDC__
err(const char * fmt,...)1064 err(const char *fmt, ...)
1065 #else
1066 err(fmt, va_alist)
1067 const char *fmt;
1068 va_dcl
1069 #endif
1070 {
1071 va_list ap;
1072
1073 #ifdef __STDC__
1074 va_start(ap, fmt);
1075 #else
1076 va_start(ap);
1077 #endif
1078 vwarn(fmt, ap);
1079 va_end(ap);
1080
1081 exit(eval);
1082 }
1083
1084 static void
1085 /*VARARGS1*/
1086 #ifdef __STDC__
warn(const char * fmt,...)1087 warn(const char *fmt, ...)
1088 #else
1089 warn(fmt, va_alist)
1090 const char *fmt;
1091 va_dcl
1092 #endif
1093 {
1094 va_list ap;
1095
1096 #ifdef __STDC__
1097 va_start(ap, fmt);
1098 #else
1099 va_start(ap);
1100 #endif
1101 vwarn(fmt, ap);
1102 va_end(ap);
1103 }
1104
1105 static void
vwarn(fmt,ap)1106 vwarn(fmt, ap)
1107 const char *fmt;
1108 va_list ap;
1109 {
1110 /*
1111 * Log the message to stderr.
1112 *
1113 * Don't use LOG_PERROR as an openlog() flag to do this,
1114 * it's not portable enough.
1115 */
1116 if (eval != EX_USAGE)
1117 (void) fprintf(stderr, "mail.local: ");
1118 (void) vfprintf(stderr, fmt, ap);
1119 (void) fprintf(stderr, "\n");
1120
1121 /* Log the message to syslog. */
1122 vsyslog(LOG_ERR, fmt, ap);
1123 }
1124
1125 /*
1126 * e_to_sys --
1127 * Guess which errno's are temporary. Gag me.
1128 */
1129 static void
e_to_sys(num)1130 e_to_sys(num)
1131 int num;
1132 {
1133 /* Temporary failures override hard errors. */
1134 if (eval == EX_TEMPFAIL)
1135 return;
1136
1137 switch (num) /* Hopefully temporary errors. */
1138 {
1139 #ifdef EDQUOT
1140 case EDQUOT: /* Disc quota exceeded */
1141 if (bouncequota)
1142 {
1143 eval = EX_UNAVAILABLE;
1144 break;
1145 }
1146 #endif /* EDQUOT */
1147 #ifdef EAGAIN
1148 /* FALLTHROUGH */
1149 case EAGAIN: /* Resource temporarily unavailable */
1150 #endif
1151 #ifdef EBUSY
1152 case EBUSY: /* Device busy */
1153 #endif
1154 #ifdef EPROCLIM
1155 case EPROCLIM: /* Too many processes */
1156 #endif
1157 #ifdef EUSERS
1158 case EUSERS: /* Too many users */
1159 #endif
1160 #ifdef ECONNABORTED
1161 case ECONNABORTED: /* Software caused connection abort */
1162 #endif
1163 #ifdef ECONNREFUSED
1164 case ECONNREFUSED: /* Connection refused */
1165 #endif
1166 #ifdef ECONNRESET
1167 case ECONNRESET: /* Connection reset by peer */
1168 #endif
1169 #ifdef EDEADLK
1170 case EDEADLK: /* Resource deadlock avoided */
1171 #endif
1172 #ifdef EFBIG
1173 case EFBIG: /* File too large */
1174 #endif
1175 #ifdef EHOSTDOWN
1176 case EHOSTDOWN: /* Host is down */
1177 #endif
1178 #ifdef EHOSTUNREACH
1179 case EHOSTUNREACH: /* No route to host */
1180 #endif
1181 #ifdef EMFILE
1182 case EMFILE: /* Too many open files */
1183 #endif
1184 #ifdef ENETDOWN
1185 case ENETDOWN: /* Network is down */
1186 #endif
1187 #ifdef ENETRESET
1188 case ENETRESET: /* Network dropped connection on reset */
1189 #endif
1190 #ifdef ENETUNREACH
1191 case ENETUNREACH: /* Network is unreachable */
1192 #endif
1193 #ifdef ENFILE
1194 case ENFILE: /* Too many open files in system */
1195 #endif
1196 #ifdef ENOBUFS
1197 case ENOBUFS: /* No buffer space available */
1198 #endif
1199 #ifdef ENOMEM
1200 case ENOMEM: /* Cannot allocate memory */
1201 #endif
1202 #ifdef ENOSPC
1203 case ENOSPC: /* No space left on device */
1204 #endif
1205 #ifdef EROFS
1206 case EROFS: /* Read-only file system */
1207 #endif
1208 #ifdef ESTALE
1209 case ESTALE: /* Stale NFS file handle */
1210 #endif
1211 #ifdef ETIMEDOUT
1212 case ETIMEDOUT: /* Connection timed out */
1213 #endif
1214 #if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
1215 case EWOULDBLOCK: /* Operation would block. */
1216 #endif
1217 eval = EX_TEMPFAIL;
1218 break;
1219 default:
1220 eval = EX_UNAVAILABLE;
1221 break;
1222 }
1223 }
1224