Home | History | Annotate | Line # | Download | only in global
      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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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