1 /* $NetBSD: mail_stream.c,v 1.3 2022/10/08 16:12:45 christos Exp $ */
2
3 /*++
4 /* NAME
5 /* mail_stream 3
6 /* SUMMARY
7 /* mail stream management
8 /* SYNOPSIS
9 /* #include <mail_stream.h>
10 /*
11 /* typedef struct {
12 /* .in +4
13 /* VSTREAM *stream; /* read/write stream */
14 /* char *id; /* queue ID */
15 /* struct timeval ctime; /* create time */
16 /* private members...
17 /* .in -4
18 /* } MAIL_STREAM;
19 /*
20 /* MAIL_STREAM *mail_stream_file(queue, class, service, mode)
21 /* const char *queue;
22 /* const char *class;
23 /* const char *service;
24 /* int mode;
25 /*
26 /* MAIL_STREAM *mail_stream_service(class, service)
27 /* const char *class;
28 /* const char *service;
29 /*
30 /* MAIL_STREAM *mail_stream_command(command)
31 /* const char *command;
32 /*
33 /* void mail_stream_cleanup(info)
34 /* MAIL_STREAM *info;
35 /*
36 /* int mail_stream_finish(info, why)
37 /* MAIL_STREAM *info;
38 /* VSTRING *why;
39 /*
40 /* void mail_stream_ctl(info, op, ...)
41 /* MAIL_STREAM *info;
42 /* int op;
43 /* DESCRIPTION
44 /* This module provides a generic interface to Postfix queue file
45 /* format messages to file, to Postfix server, or to external command.
46 /* The routines that open a stream return a handle with an initialized
47 /* stream and queue id member. The handle is either given to a cleanup
48 /* routine, to dispose of a failed request, or to a finish routine, to
49 /* complete the request.
50 /*
51 /* mail_stream_file() opens a mail stream to a newly-created file and
52 /* arranges for trigger delivery at finish time. This call never fails.
53 /* But it may take forever. The mode argument specifies additional
54 /* file permissions that will be OR-ed in when the file is finished.
55 /* While embryonic files have mode 0600, finished files have mode 0700.
56 /*
57 /* mail_stream_command() opens a mail stream to external command,
58 /* and receives queue ID information from the command. The result
59 /* is a null pointer when the initial handshake fails. The command
60 /* is given to the shell only when necessary. At finish time, the
61 /* command is expected to send a completion status.
62 /*
63 /* mail_stream_service() opens a mail stream to Postfix service,
64 /* and receives queue ID information from the command. The result
65 /* is a null pointer when the initial handshake fails. At finish
66 /* time, the daemon is expected to send a completion status.
67 /*
68 /* mail_stream_cleanup() cancels the operation that was started with
69 /* any of the mail_stream_xxx() routines, and destroys the argument.
70 /* It is up to the caller to remove incomplete file objects.
71 /*
72 /* mail_stream_finish() completes the operation that was started with
73 /* any of the mail_stream_xxx() routines, and destroys the argument.
74 /* The result is any of the status codes defined in <cleanup_user.h>.
75 /* It is up to the caller to remove incomplete file objects.
76 /* The why argument can be a null pointer.
77 /*
78 /* mail_stream_ctl() selectively overrides information that
79 /* was specified with mail_stream_file(); none of the attributes
80 /* are applicable for other mail stream types. The arguments
81 /* are a list macros with arguments, terminated with
82 /* CA_MAIL_STREAM_CTL_END which has none. The following lists
83 /* the macros and the types of the corresponding arguments.
84 /* .IP "CA_MAIL_STREAM_CTL_QUEUE(const char *)"
85 /* The argument specifies an alternate destination queue. The
86 /* queue file is moved to the specified queue before the call
87 /* returns. Failure to rename the queue file results in a fatal
88 /* error.
89 /* .IP "CA_MAIL_STREAM_CTL_CLASS(const char *)"
90 /* The argument specifies an alternate trigger class.
91 /* .IP "CA_MAIL_STREAM_CTL_SERVICE(const char *)"
92 /* The argument specifies an alternate trigger service.
93 /* .IP "CA_MAIL_STREAM_CTL_MODE(int)"
94 /* The argument specifies alternate permissions that override
95 /* the permissions specified with mail_stream_file().
96 /* .IP "CA_MAIL_STREAM_CTL_DELAY(int)"
97 /* Attempt to postpone initial delivery by advancing the queue
98 /* file modification time stamp by this amount. This has
99 /* effect only within the deferred mail queue.
100 /* This feature may have no effect with remote file systems.
101 /* LICENSE
102 /* .ad
103 /* .fi
104 /* The Secure Mailer license must be distributed with this software.
105 /* AUTHOR(S)
106 /* Wietse Venema
107 /* IBM T.J. Watson Research
108 /* P.O. Box 704
109 /* Yorktown Heights, NY 10598, USA
110 /*
111 /* Wietse Venema
112 /* Google, Inc.
113 /* 111 8th Avenue
114 /* New York, NY 10011, USA
115 /*--*/
116
117 /* System library. */
118
119 #include <sys_defs.h>
120 #include <sys/stat.h>
121 #include <unistd.h>
122 #include <errno.h>
123 #include <utime.h>
124 #include <string.h>
125 #include <stdarg.h>
126
127 /* Utility library. */
128
129 #include <msg.h>
130 #include <mymalloc.h>
131 #include <vstring.h>
132 #include <vstream.h>
133 #include <stringops.h>
134 #include <argv.h>
135 #include <sane_fsops.h>
136 #include <warn_stat.h>
137
138 /* Global library. */
139
140 #include <cleanup_user.h>
141 #include <mail_proto.h>
142 #include <mail_queue.h>
143 #include <opened.h>
144 #include <mail_params.h>
145 #include <mail_stream.h>
146 #include <mail_parm_split.h>
147
148 /* Application-specific. */
149
150 static VSTRING *id_buf;
151
152 #define FREE_AND_WIPE(free, arg) do { if (arg) free(arg); arg = 0; } while (0)
153
154 #define STR(x) vstring_str(x)
155
156 /* mail_stream_cleanup - clean up after success or failure */
157
mail_stream_cleanup(MAIL_STREAM * info)158 void mail_stream_cleanup(MAIL_STREAM *info)
159 {
160 if (info->stream && info->close(info->stream))
161 msg_warn("mail_stream_cleanup: close error");
162 FREE_AND_WIPE(myfree, info->queue);
163 FREE_AND_WIPE(myfree, info->id);
164 FREE_AND_WIPE(myfree, info->class);
165 FREE_AND_WIPE(myfree, info->service);
166 myfree((void *) info);
167 }
168
169 #if defined(HAS_FUTIMES_AT)
170 #define CAN_STAMP_BY_STREAM
171
172 /* stamp_stream - update open file [am]time stamp */
173
stamp_stream(VSTREAM * fp,time_t when)174 static int stamp_stream(VSTREAM *fp, time_t when)
175 {
176 struct timeval tv[2];
177
178 if (when != 0) {
179 tv[0].tv_sec = tv[1].tv_sec = when;
180 tv[0].tv_usec = tv[1].tv_usec = 0;
181 return (futimesat(vstream_fileno(fp), (char *) 0, tv));
182 } else {
183 return (futimesat(vstream_fileno(fp), (char *) 0, (struct timeval *) 0));
184 }
185 }
186
187 #elif defined(HAS_FUTIMES)
188 #define CAN_STAMP_BY_STREAM
189
190 /* stamp_stream - update open file [am]time stamp */
191
stamp_stream(VSTREAM * fp,time_t when)192 static int stamp_stream(VSTREAM *fp, time_t when)
193 {
194 struct timeval tv[2];
195
196 if (when != 0) {
197 tv[0].tv_sec = tv[1].tv_sec = when;
198 tv[0].tv_usec = tv[1].tv_usec = 0;
199 return (futimes(vstream_fileno(fp), tv));
200 } else {
201 return (futimes(vstream_fileno(fp), (struct timeval *) 0));
202 }
203 }
204
205 #endif
206
207 /* stamp_path - update file [am]time stamp by pathname */
208
stamp_path(const char * path,time_t when)209 static int stamp_path(const char *path, time_t when)
210 {
211 struct utimbuf tbuf;
212
213 if (when != 0) {
214 tbuf.actime = tbuf.modtime = when;
215 return (utime(path, &tbuf));
216 } else {
217 return (utime(path, (struct utimbuf *) 0));
218 }
219 }
220
221 /* mail_stream_finish_file - finish file mail stream */
222
mail_stream_finish_file(MAIL_STREAM * info,VSTRING * unused_why)223 static int mail_stream_finish_file(MAIL_STREAM *info, VSTRING *unused_why)
224 {
225 int status = CLEANUP_STAT_OK;
226 static char wakeup[] = {TRIGGER_REQ_WAKEUP};
227 struct stat st;
228 char *path_to_reset = 0;
229 static int incoming_fs_clock_ok = 0;
230 static int incoming_clock_warned = 0;
231 int check_incoming_fs_clock;
232 int err;
233 time_t want_stamp;
234 time_t expect_stamp;
235
236 /*
237 * Make sure the message makes it to file. Set the execute bit when no
238 * write error was detected. Some people believe that this code has a
239 * problem if the system crashes before fsync() returns; fchmod() could
240 * take effect before all the data blocks are written. Wietse claims that
241 * this is not a problem. Postfix rejects incomplete queue files, even
242 * when the +x attribute is set. Every Postfix queue file record has a
243 * type code and a length field. Files with missing records are rejected,
244 * as are files with unknown record type codes. Every Postfix queue file
245 * must end with an explicit END record. Postfix queue files without END
246 * record are discarded.
247 *
248 * Attempt to detect file system clocks that are ahead of local time, but
249 * don't check the file system clock all the time. The effect of file
250 * system clock drift can be difficult to understand (Postfix ignores new
251 * mail until the local clock catches up with the file mtime stamp).
252 *
253 * This clock drift detection code may not work with file systems that work
254 * on a local copy of the file and that update the server only after the
255 * file is closed.
256 *
257 * Optionally set a cooldown time.
258 *
259 * XXX: We assume that utime() does control the file modification time even
260 * when followed by an fchmod(), fsync(), close() sequence. This may fail
261 * with remote file systems when fsync() actually updates the file. Even
262 * then, we still delay the average message by 1/2 of the
263 * queue_run_delay.
264 *
265 * XXX: Victor does not like running utime() after the close(), since this
266 * creates a race even with local filesystems. But Wietse is not
267 * confident that utime() before fsync() and close() will work reliably
268 * with remote file systems.
269 *
270 * XXX Don't run the clock skew tests with Postfix sendmail submissions.
271 * Don't whine against unsuspecting users or applications.
272 */
273 check_incoming_fs_clock =
274 (!incoming_fs_clock_ok && !strcmp(info->queue, MAIL_QUEUE_INCOMING));
275
276 #ifdef DELAY_ACTION
277 if (strcmp(info->queue, MAIL_QUEUE_DEFERRED) != 0)
278 info->delay = 0;
279 if (info->delay > 0)
280 want_stamp = time((time_t *) 0) + info->delay;
281 else
282 #endif
283 want_stamp = 0;
284
285 /*
286 * If we can cheaply set the file time stamp (no pathname lookup) do it
287 * anyway, so that we can avoid whining later about file server/client
288 * clock skew.
289 *
290 * Otherwise, if we must set the file time stamp for delayed delivery, use
291 * whatever means we have to get the job done, no matter if it is
292 * expensive.
293 *
294 * XXX Unfortunately, Linux futimes() is not usable because it uses /proc.
295 * This may not be available because of chroot, or because of access
296 * restrictions after a process changes privileges.
297 */
298 if (vstream_fflush(info->stream)
299 #ifdef CAN_STAMP_BY_STREAM
300 || stamp_stream(info->stream, want_stamp)
301 #else
302 || (want_stamp && stamp_path(VSTREAM_PATH(info->stream), want_stamp))
303 #endif
304 || fchmod(vstream_fileno(info->stream), 0700 | info->mode)
305 #ifdef HAS_FSYNC
306 || fsync(vstream_fileno(info->stream))
307 #endif
308 || (check_incoming_fs_clock
309 && fstat(vstream_fileno(info->stream), &st) < 0)
310 )
311 status = (errno == EFBIG ? CLEANUP_STAT_SIZE : CLEANUP_STAT_WRITE);
312 #ifdef TEST
313 st.st_mtime += 10;
314 #endif
315
316 /*
317 * Work around file system clock skew. If the file system clock is ahead
318 * of the local clock, Postfix won't deliver mail immediately, which is
319 * bad for performance. If the file system clock falls behind the local
320 * clock, it just looks silly in mail headers.
321 */
322 if (status == CLEANUP_STAT_OK && check_incoming_fs_clock) {
323 /* Do NOT use time() result from before fsync(). */
324 expect_stamp = want_stamp ? want_stamp : time((time_t *) 0);
325 if (st.st_mtime > expect_stamp) {
326 path_to_reset = mystrdup(VSTREAM_PATH(info->stream));
327 if (incoming_clock_warned == 0) {
328 msg_warn("file system clock is %d seconds ahead of local clock",
329 (int) (st.st_mtime - expect_stamp));
330 msg_warn("resetting file time stamps - this hurts performance");
331 incoming_clock_warned = 1;
332 }
333 } else {
334 if (st.st_mtime < expect_stamp - 100)
335 msg_warn("file system clock is %d seconds behind local clock",
336 (int) (expect_stamp - st.st_mtime));
337 incoming_fs_clock_ok = 1;
338 }
339 }
340
341 /*
342 * Close the queue file and mark it as closed. Be prepared for
343 * vstream_fclose() to fail even after vstream_fflush() and fsync()
344 * reported no error. Reason: after a file is closed, some networked file
345 * systems copy the file out to another machine. Running the queue on a
346 * remote file system is not recommended, if only for performance
347 * reasons.
348 */
349 err = info->close(info->stream);
350 info->stream = 0;
351 if (status == CLEANUP_STAT_OK && err != 0)
352 status = (errno == EFBIG ? CLEANUP_STAT_SIZE : CLEANUP_STAT_WRITE);
353
354 /*
355 * Work around file system clocks that are ahead of local time.
356 */
357 if (path_to_reset != 0) {
358 if (status == CLEANUP_STAT_OK) {
359 if (stamp_path(path_to_reset, expect_stamp) < 0 && errno != ENOENT)
360 msg_fatal("%s: update file time stamps: %m", info->id);
361 }
362 myfree(path_to_reset);
363 }
364
365 /*
366 * When all is well, notify the next service that a new message has been
367 * queued.
368 */
369 if (status == CLEANUP_STAT_OK && info->class && info->service)
370 mail_trigger(info->class, info->service, wakeup, sizeof(wakeup));
371
372 /*
373 * Cleanup.
374 */
375 mail_stream_cleanup(info);
376 return (status);
377 }
378
379 /* mail_stream_finish_ipc - finish IPC mail stream */
380
mail_stream_finish_ipc(MAIL_STREAM * info,VSTRING * why)381 static int mail_stream_finish_ipc(MAIL_STREAM *info, VSTRING *why)
382 {
383 int status = CLEANUP_STAT_WRITE;
384
385 /*
386 * Receive the peer's completion status.
387 */
388 if ((why && attr_scan(info->stream, ATTR_FLAG_STRICT,
389 RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
390 RECV_ATTR_STR(MAIL_ATTR_WHY, why),
391 ATTR_TYPE_END) != 2)
392 || (!why && attr_scan(info->stream, ATTR_FLAG_MISSING,
393 RECV_ATTR_INT(MAIL_ATTR_STATUS, &status),
394 ATTR_TYPE_END) != 1))
395 status = CLEANUP_STAT_WRITE;
396
397 /*
398 * Cleanup.
399 */
400 mail_stream_cleanup(info);
401 return (status);
402 }
403
404 /* mail_stream_finish - finish action */
405
mail_stream_finish(MAIL_STREAM * info,VSTRING * why)406 int mail_stream_finish(MAIL_STREAM *info, VSTRING *why)
407 {
408 return (info->finish(info, why));
409 }
410
411 /* mail_stream_file - destination is file */
412
mail_stream_file(const char * queue,const char * class,const char * service,int mode)413 MAIL_STREAM *mail_stream_file(const char *queue, const char *class,
414 const char *service, int mode)
415 {
416 struct timeval tv;
417 MAIL_STREAM *info;
418 VSTREAM *stream;
419
420 stream = mail_queue_enter(queue, 0600 | mode, &tv);
421 if (msg_verbose)
422 msg_info("open %s", VSTREAM_PATH(stream));
423
424 info = (MAIL_STREAM *) mymalloc(sizeof(*info));
425 info->stream = stream;
426 info->finish = mail_stream_finish_file;
427 info->close = vstream_fclose;
428 info->queue = mystrdup(queue);
429 info->id = mystrdup(basename(VSTREAM_PATH(stream)));
430 info->class = mystrdup(class);
431 info->service = mystrdup(service);
432 info->mode = mode;
433 #ifdef DELAY_ACTION
434 info->delay = 0;
435 #endif
436 info->ctime = tv;
437 return (info);
438 }
439
440 /* mail_stream_service - destination is service */
441
mail_stream_service(const char * class,const char * name)442 MAIL_STREAM *mail_stream_service(const char *class, const char *name)
443 {
444 VSTREAM *stream;
445 MAIL_STREAM *info;
446
447 if (id_buf == 0)
448 id_buf = vstring_alloc(10);
449
450 stream = mail_connect_wait(class, name);
451 if (attr_scan(stream, ATTR_FLAG_STRICT,
452 RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_CLEANUP),
453 RECV_ATTR_STR(MAIL_ATTR_QUEUEID, id_buf), 0) != 1) {
454 vstream_fclose(stream);
455 return (0);
456 } else {
457 info = (MAIL_STREAM *) mymalloc(sizeof(*info));
458 info->stream = stream;
459 info->finish = mail_stream_finish_ipc;
460 info->close = vstream_fclose;
461 info->queue = 0;
462 info->id = mystrdup(vstring_str(id_buf));
463 info->class = 0;
464 info->service = 0;
465 return (info);
466 }
467 }
468
469 /* mail_stream_command - destination is command */
470
mail_stream_command(const char * command)471 MAIL_STREAM *mail_stream_command(const char *command)
472 {
473 VSTREAM *stream;
474 MAIL_STREAM *info;
475 ARGV *export_env;
476 int status;
477
478 if (id_buf == 0)
479 id_buf = vstring_alloc(10);
480
481 /*
482 * Treat fork() failure as a transient problem. Treat bad handshake as a
483 * permanent error.
484 *
485 * XXX Are we invoking a Postfix process or a non-Postfix process? In the
486 * former case we can share the full environment; in the latter case only
487 * a restricted environment should be propagated. Even though we are
488 * talking a Postfix-internal protocol there is no way we can tell what
489 * is being executed except by duplicating a lot of existing code.
490 */
491 export_env = mail_parm_split(VAR_EXPORT_ENVIRON, var_export_environ);
492 while ((stream = vstream_popen(O_RDWR,
493 CA_VSTREAM_POPEN_COMMAND(command),
494 CA_VSTREAM_POPEN_EXPORT(export_env->argv),
495 CA_VSTREAM_POPEN_END)) == 0) {
496 msg_warn("fork: %m");
497 sleep(10);
498 }
499 argv_free(export_env);
500 vstream_control(stream,
501 CA_VSTREAM_CTL_PATH(command),
502 CA_VSTREAM_CTL_END);
503
504 if (attr_scan(stream, ATTR_FLAG_STRICT,
505 RECV_ATTR_STREQ(MAIL_ATTR_PROTO, MAIL_ATTR_PROTO_POSTDROP),
506 RECV_ATTR_STR(MAIL_ATTR_QUEUEID, id_buf), 0) != 1) {
507 if ((status = vstream_pclose(stream)) != 0)
508 msg_warn("command \"%s\" exited with status %d", command, status);
509 return (0);
510 } else {
511 info = (MAIL_STREAM *) mymalloc(sizeof(*info));
512 info->stream = stream;
513 info->finish = mail_stream_finish_ipc;
514 info->close = vstream_pclose;
515 info->queue = 0;
516 info->id = mystrdup(vstring_str(id_buf));
517 info->class = 0;
518 info->service = 0;
519 return (info);
520 }
521 }
522
523 /* mail_stream_ctl - update file-based mail stream properties */
524
mail_stream_ctl(MAIL_STREAM * info,int op,...)525 void mail_stream_ctl(MAIL_STREAM *info, int op,...)
526 {
527 const char *myname = "mail_stream_ctl";
528 va_list ap;
529 char *new_queue = 0;
530 char *string_value;
531
532 /*
533 * Sanity check. None of the attributes below are applicable unless the
534 * target is a file-based stream.
535 */
536 if (info->finish != mail_stream_finish_file)
537 msg_panic("%s: attempt to update non-file stream %s",
538 myname, info->id);
539
540 for (va_start(ap, op); op != MAIL_STREAM_CTL_END; op = va_arg(ap, int)) {
541
542 switch (op) {
543
544 /*
545 * Change the queue directory. We do this at the end of this
546 * call.
547 */
548 case MAIL_STREAM_CTL_QUEUE:
549 if ((new_queue = va_arg(ap, char *)) == 0)
550 msg_panic("%s: NULL queue",
551 myname);
552 break;
553
554 /*
555 * Change the service that needs to be notified.
556 */
557 case MAIL_STREAM_CTL_CLASS:
558 FREE_AND_WIPE(myfree, info->class);
559 if ((string_value = va_arg(ap, char *)) != 0)
560 info->class = mystrdup(string_value);
561 break;
562
563 case MAIL_STREAM_CTL_SERVICE:
564 FREE_AND_WIPE(myfree, info->service);
565 if ((string_value = va_arg(ap, char *)) != 0)
566 info->service = mystrdup(string_value);
567 break;
568
569 /*
570 * Change the (finished) file access mode.
571 */
572 case MAIL_STREAM_CTL_MODE:
573 info->mode = va_arg(ap, int);
574 break;
575
576 /*
577 * Advance the (finished) file modification time.
578 */
579 #ifdef DELAY_ACTION
580 case MAIL_STREAM_CTL_DELAY:
581 if ((info->delay = va_arg(ap, int)) < 0)
582 msg_panic("%s: bad delay time %d", myname, info->delay);
583 break;
584 #endif
585
586 default:
587 msg_panic("%s: bad op code %d", myname, op);
588 }
589 }
590 va_end(ap);
591
592 /*
593 * Rename the queue file after allocating memory for new information, so
594 * that the caller can still remove an embryonic file when memory
595 * allocation fails (there is no risk of deleting the wrong file).
596 *
597 * Wietse opposed the idea to update run-time error handler information
598 * here, because this module wasn't designed to defend against internal
599 * concurrency issues with error handlers that attempt to follow dangling
600 * pointers.
601 *
602 * This code duplicates mail_queue_rename(), except that we need the new
603 * path to update the stream pathname.
604 */
605 if (new_queue != 0 && strcmp(info->queue, new_queue) != 0) {
606 char *saved_queue = info->queue;
607 char *saved_path = mystrdup(VSTREAM_PATH(info->stream));
608 VSTRING *new_path = vstring_alloc(100);
609
610 (void) mail_queue_path(new_path, new_queue, info->id);
611 info->queue = mystrdup(new_queue);
612 vstream_control(info->stream, CA_VSTREAM_CTL_PATH(STR(new_path)),
613 CA_VSTREAM_CTL_END);
614
615 if (sane_rename(saved_path, STR(new_path)) == 0
616 || (mail_queue_mkdirs(STR(new_path)) == 0
617 && sane_rename(saved_path, STR(new_path)) == 0)) {
618 if (msg_verbose)
619 msg_info("%s: placed in %s queue", info->id, info->queue);
620 } else {
621 msg_fatal("%s: move to %s queue failed: %m", info->id,
622 info->queue);
623 }
624
625 myfree(saved_path);
626 myfree(saved_queue);
627 vstring_free(new_path);
628 }
629 }
630