Typos: Viktor Dukhovni. File: HISTORY.
Documentation: haproxy2 support. File: proto/postconf.proto.
+
+20200120
+
+ [initially released as part of postfix-20200125-nonprod]
+ Feature: forced message expiration. The "postsuper -e"
+ option sets an 'expired' bit on one or more messages selected
+ by their message ID. The queue manager returns a message
+ as undeliverable when it moves the message to the active
+ queue. Messages in the hold queue stay in that queue.
+
+ If a force-expired message was deferred, then it is returned
+ with the reason for the delay. Otherwise, the message is
+ returned with "message is administratively expired". Design
+ by Wietse; Viktor suggested using the group execute permission
+ bit. Files: global/mail_queue.h, *qmgr/qmgr.h, *qmgr/qmgr_active.c,
+ *qmgr/qmgr_message.c, postsuper/Makefile.in, postsuper/postsuper.c.
+
+20200125
+
+ [initially released as part of postfix-20200125-nonprod]
+ Added support for "postsuper -f" to expire and optionally
+ release a message. Restructured the postsuper command so
+ that it will execute actions in the order of the -[defhr]
+ flags, instead of using an invisible fixed internal order.
+ The -e and -f options are idempotent (just like -h and -H).
+ Adjusted the summary at the end to make this more clear.
+ File: postsuper/postsuper.c.
+
+20200126
+
+ [initially released as part of postfix-20200126-nonprod]
+ Updated the mailq/postqueue commands to make forced message
+ expiration status available. In ASCII ouput this is indicated
+ with "#" appended to the queue file name, and in JSON output
+ this is indicated with the boolean "force_expired" attribute.
+ Files: showq/showq.c, postqueue/showq_compat.c,
+ postqueue/showq_json.c.
+
+ [initially released as part of postfix-20200126-nonprod]
+ Cleanup: minor tweaks to comments and code.
+
+ Safety: give maildrop queue files more time (week instead
+ of day) to reach completion, in case a message is submitted
+ by a really long-running program. File: postsuper/postsuper.c.
+
+ [initially released as part of postfix-2020XXXX-nonprod]
+ Cleanup: postsuper manpage indentation, word abbreviation.
+ Files: mantools/postlink, postsuper/postsuper.c.
the software under the license of their choice. Those who are more
comfortable with the IPL can continue with that license.
+Major changes with snapshot 20200202
+====================================
+
+Support to force-expire email messages. This introduces new
+postsuper(1) command-line options to request expiration, and
+additional information in mailq command output.
+
+The forced-to-expire status is stored in a queue file attribute.
+An expired message is returned to the sender when the queue manager
+attempts to deliver that message (note that Postfix will never
+deliver messages in the hold queue).
+
+The postsuper(1) -e and -f options both set the forced-to-expire
+queue file attribute. The difference is that -f will also release
+a message if it is in the hold queue. With -e, such a message would
+not be returned to the sender until it is released with -f or -H.
+
+In the mailq(1) default output, a forced-to-expire message is
+indicated with # after the queue name. In mailq(1) JSON output there
+is a new per-message field "forced_expire" with the value true or
+false.
+
Incompatible changes with snapshot 20191109
===========================================
ery attempt will be made until the mail is taken off
hold.
+ <b>#</b> The message is forced to expire. See the <a href="postsuper.1.html"><b>postsuper</b>(1)</a>
+ options <b>-e</b> or <b>-f</b>.
+
+ This feature is available in Postfix 3.5 and later.
+
<b>-s</b> <i>site</i>
- Schedule immediate delivery of all mail that is queued for the
- named <i>site</i>. A numerical site must be specified as a valid <a href="http://tools.ietf.org/html/rfc5321">RFC</a>
+ Schedule immediate delivery of all mail that is queued for the
+ named <i>site</i>. A numerical site must be specified as a valid <a href="http://tools.ietf.org/html/rfc5321">RFC</a>
<a href="http://tools.ietf.org/html/rfc5321">5321</a> address literal enclosed in [], just like in email
- addresses. The site must be eligible for the "fast flush" ser-
- vice. See <a href="flush.8.html"><b>flush</b>(8)</a> for more information about the "fast flush"
+ addresses. The site must be eligible for the "fast flush" ser-
+ vice. See <a href="flush.8.html"><b>flush</b>(8)</a> for more information about the "fast flush"
service.
- This option implements the traditional "<b>sendmail -qR</b><i>site</i>" com-
+ This option implements the traditional "<b>sendmail -qR</b><i>site</i>" com-
mand, by contacting the Postfix <a href="flush.8.html"><b>flush</b>(8)</a> daemon.
- <b>-v</b> Enable verbose logging for debugging purposes. Multiple <b>-v</b>
- options make the software increasingly verbose. As of Postfix
+ <b>-v</b> Enable verbose logging for debugging purposes. Multiple <b>-v</b>
+ options make the software increasingly verbose. As of Postfix
2.3, this option is available for the super-user only.
<b>JSON OBJECT FORMAT</b>
- Each JSON object represents one queue file; it is emitted as a single
+ Each JSON object represents one queue file; it is emitted as a single
text line followed by a newline character.
Object members have string values unless indicated otherwise. Programs
bers is expected to grow over time.
<b>queue_name</b>
- The name of the queue where the message was found. Note that
- the contents of the mail queue may change while it is being
- listed; some messages may appear more than once, and some mes-
+ The name of the queue where the message was found. Note that
+ the contents of the mail queue may change while it is being
+ listed; some messages may appear more than once, and some mes-
sages may be missed.
<b>queue_id</b>
The queue file name. The queue_id may be reused within a Postfix
instance unless "<a href="postconf.5.html#enable_long_queue_ids">enable_long_queue_ids</a> = true" and time is mono-
- tonic. Even then, the queue_id is not expected to be unique
- between different Postfix instances. Management tools that
- require a unique name should combine the queue_id with the
+ tonic. Even then, the queue_id is not expected to be unique
+ between different Postfix instances. Management tools that
+ require a unique name should combine the queue_id with the
<a href="postconf.5.html#myhostname">myhostname</a> setting of the Postfix instance.
<b>arrival_time</b>
The number of seconds since the start of the UNIX epoch.
<b>message_size</b>
- The number of bytes in the message header and body. This number
- does not include message envelope information. It is approxi-
- mately equal to the number of bytes that would be transmitted
+ The number of bytes in the message header and body. This number
+ does not include message envelope information. It is approxi-
+ mately equal to the number of bytes that would be transmitted
via SMTP including the <CR><LF> line endings.
+ <b>forced_expire</b>
+ The message is forced to expire (<b>true</b> or <b>false</b>). See the <a href="postsuper.1.html"><b>post-</b></a>
+ <a href="postsuper.1.html"><b>super</b>(1)</a> options <b>-e</b> or <b>-f</b>.
+
+ This feature is available in Postfix 3.5 and later.
+
<b>sender</b> The envelope sender address.
<b>recipients</b>
postsuper - Postfix superintendent
<b>SYNOPSIS</b>
- <b>postsuper</b> [<b>-psSv</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] [<b>-d</b> <i>queue</i><b>_</b><i>id</i>]
+ <b>postsuper</b> [<b>-psSv</b>]
+ [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] [<b>-d</b> <i>queue</i><b>_</b><i>id</i>]
+ [<b>-e</b> <i>queue</i><b>_</b><i>id</i>] [<b>-f</b> <i>queue</i><b>_</b><i>id</i>]
[<b>-h</b> <i>queue</i><b>_</b><i>id</i>] [<b>-H</b> <i>queue</i><b>_</b><i>id</i>]
[<b>-r</b> <i>queue</i><b>_</b><i>id</i>] [<i>directory ...</i>]
By default, <a href="postsuper.1.html"><b>postsuper</b>(1)</a> performs the operations requested with the <b>-s</b>
and <b>-p</b> command-line options on all Postfix queue directories - this
- includes the <b>incoming</b>, <b>active</b> and <b>deferred</b> directories with mail files
- and the <b>bounce</b>, <b>defer</b>, <b>trace</b> and <b>flush</b> directories with log files.
+ includes the <b>incoming</b>, <b>active</b>, <b>deferred</b>, and <b>hold</b> directories with mes-
+ sage files and the <b>bounce</b>, <b>defer</b>, <b>trace</b> and <b>flush</b> directories with log
+ files.
Options:
environment setting below.
<b>-d</b> <i>queue</i><b>_</b><i>id</i>
- Delete one message with the named queue ID from the named mail
+ Delete one message with the named queue ID from the named mail
queue(s) (default: <b>hold</b>, <b>incoming</b>, <b>active</b> and <b>deferred</b>).
- To delete multiple files, specify the <b>-d</b> option multiple times,
- or specify a <i>queue</i><b>_</b><i>id</i> of <b>-</b> to read queue IDs from standard
- input. For example, to delete all mail with exactly one recipi-
+ To delete multiple files, specify the <b>-d</b> option multiple times,
+ or specify a <i>queue</i><b>_</b><i>id</i> of <b>-</b> to read queue IDs from standard
+ input. For example, to delete all mail with exactly one recipi-
ent <b>user@example.com</b>:
mailq | tail -n +2 | grep -v '^ *(' | awk 'BEGIN { RS = "" }
print $1 }
' | tr -d '*!' | postsuper -d -
- Specify "<b>-d ALL</b>" to remove all messages; for example, specify
- "<b>-d ALL deferred</b>" to delete all mail in the <b>deferred</b> queue. As
- a safety measure, the word <b>ALL</b> must be specified in upper case.
+ Specify "<b>-d ALL</b>" to remove all messages; for example, specify
+ "<b>-d ALL deferred</b>" to delete all mail in the <b>deferred</b> queue. As
+ a safety measure, the word <b>ALL</b> must be specified in upper case.
- Warning: Postfix queue IDs are reused (always with Postfix <=
- 2.8; and with Postfix >= 2.9 when <a href="postconf.5.html#enable_long_queue_ids">enable_long_queue_ids</a>=no).
- There is a very small possibility that postsuper deletes the
- wrong message file when it is executed while the Postfix mail
+ Warning: Postfix queue IDs are reused (always with Postfix <=
+ 2.8; and with Postfix >= 2.9 when <a href="postconf.5.html#enable_long_queue_ids">enable_long_queue_ids</a>=no).
+ There is a very small possibility that postsuper deletes the
+ wrong message file when it is executed while the Postfix mail
system is delivering mail.
The scenario is as follows:
- 1) The Postfix queue manager deletes the message that <a href="postsuper.1.html"><b>post-</b></a>
- <a href="postsuper.1.html"><b>super</b>(1)</a> is asked to delete, because Postfix is finished
- with the message (it is delivered, or it is returned to
+ 1) The Postfix queue manager deletes the message that <a href="postsuper.1.html"><b>post-</b></a>
+ <a href="postsuper.1.html"><b>super</b>(1)</a> is asked to delete, because Postfix is finished
+ with the message (it is delivered, or it is returned to
the sender).
- 2) New mail arrives, and the new message is given the same
- queue ID as the message that <a href="postsuper.1.html"><b>postsuper</b>(1)</a> is supposed to
- delete. The probability for reusing a deleted queue ID
- is about 1 in 2**15 (the number of different microsecond
- values that the system clock can distinguish within a
+ 2) New mail arrives, and the new message is given the same
+ queue ID as the message that <a href="postsuper.1.html"><b>postsuper</b>(1)</a> is supposed to
+ delete. The probability for reusing a deleted queue ID
+ is about 1 in 2**15 (the number of different microsecond
+ values that the system clock can distinguish within a
second).
- 3) <a href="postsuper.1.html"><b>postsuper</b>(1)</a> deletes the new message, instead of the old
+ 3) <a href="postsuper.1.html"><b>postsuper</b>(1)</a> deletes the new message, instead of the old
message that it should have deleted.
+ <b>-e</b> <i>queue</i><b>_</b><i>id</i>
+
+ <b>-f</b> <i>queue</i><b>_</b><i>id</i>
+ Request forced expiration for one message with the named queue
+ ID in the named mail queue(s) (default: <b>hold</b>, <b>incoming</b>, <b>active</b>
+ and <b>deferred</b>).
+
+ <b>o</b> The message will be returned to the sender when the queue
+ manager attempts to deliver that message (note that Post-
+ fix will never deliver messages in the <b>hold</b> queue).
+
+ <b>o</b> The <b>-e</b> and <b>-f</b> options both request forced expiration. The
+ difference is that <b>-f</b> will also release a message if it
+ is in the <a href="QSHAPE_README.html#hold_queue">hold queue</a>. With <b>-e</b>, such a message would not
+ be returned to the sender until it is released with <b>-f</b> or
+ <b>-H</b>.
+
+ <b>o</b> When a deferred message is force-expired, the return mes-
+ sage will state the reason for the delay. Otherwise, the
+ reason will be "message is administratively expired".
+
+ To expire multiple files, specify the <b>-e</b> or <b>-f</b> option multiple
+ times, or specify a <i>queue</i><b>_</b><i>id</i> of <b>-</b> to read queue IDs from stan-
+ dard input (see the <b>-d</b> option above for an example, but be sure
+ to replace <b>-d</b> in the example).
+
+ Specify "<b>-e ALL</b>" or "<b>-f ALL</b>" to expire all messages; for exam-
+ ple, specify "<b>-e ALL deferred</b>" to expire all mail in the
+ <b>deferred</b> queue. As a safety measure, the word <b>ALL</b> must be spec-
+ ified in upper case.
+
+ These features are available in Postfix 3.5 and later.
+
<b>-h</b> <i>queue</i><b>_</b><i>id</i>
- Put mail "on hold" so that no attempt is made to deliver it.
- Move one message with the named queue ID from the named mail
- queue(s) (default: <b>incoming</b>, <b>active</b> and <b>deferred</b>) to the <b>hold</b>
+ Put mail "on hold" so that no attempt is made to deliver it.
+ Move one message with the named queue ID from the named mail
+ queue(s) (default: <b>incoming</b>, <b>active</b> and <b>deferred</b>) to the <b>hold</b>
queue.
To hold multiple files, specify the <b>-h</b> option multiple times, or
specify a <i>queue</i><b>_</b><i>id</i> of <b>-</b> to read queue IDs from standard input.
- Specify "<b>-h ALL</b>" to hold all messages; for example, specify "<b>-h</b>
- <b>ALL deferred</b>" to hold all mail in the <b>deferred</b> queue. As a
+ Specify "<b>-h ALL</b>" to hold all messages; for example, specify "<b>-h</b>
+ <b>ALL deferred</b>" to hold all mail in the <b>deferred</b> queue. As a
safety measure, the word <b>ALL</b> must be specified in upper case.
- Note: while mail is "on hold" it will not expire when its time
- in the queue exceeds the <b><a href="postconf.5.html#maximal_queue_lifetime">maximal_queue_lifetime</a></b> or
- <b><a href="postconf.5.html#bounce_queue_lifetime">bounce_queue_lifetime</a></b> setting. It becomes subject to expiration
+ Note: while mail is "on hold" it will not expire when its time
+ in the queue exceeds the <b><a href="postconf.5.html#maximal_queue_lifetime">maximal_queue_lifetime</a></b> or
+ <b><a href="postconf.5.html#bounce_queue_lifetime">bounce_queue_lifetime</a></b> setting. It becomes subject to expiration
after it is released from "hold".
This feature is available in Postfix 2.0 and later.
<b>-H</b> <i>queue</i><b>_</b><i>id</i>
- Release mail that was put "on hold". Move one message with the
- named queue ID from the named mail queue(s) (default: <b>hold</b>) to
+ Release mail that was put "on hold". Move one message with the
+ named queue ID from the named mail queue(s) (default: <b>hold</b>) to
the <b>deferred</b> queue.
To release multiple files, specify the <b>-H</b> option multiple times,
- or specify a <i>queue</i><b>_</b><i>id</i> of <b>-</b> to read queue IDs from standard
+ or specify a <i>queue</i><b>_</b><i>id</i> of <b>-</b> to read queue IDs from standard
input.
- Note: specify "<b>postsuper -r</b>" to release mail that was kept on
- hold for a significant fraction of <b>$<a href="postconf.5.html#maximal_queue_lifetime">maximal_queue_lifetime</a></b> or
+ Note: specify "<b>postsuper -r</b>" to release mail that was kept on
+ hold for a significant fraction of <b>$<a href="postconf.5.html#maximal_queue_lifetime">maximal_queue_lifetime</a></b> or
<b>$<a href="postconf.5.html#bounce_queue_lifetime">bounce_queue_lifetime</a></b>, or longer.
- Specify "<b>-H ALL</b>" to release all mail that is "on hold". As a
+ Specify "<b>-H ALL</b>" to release all mail that is "on hold". As a
safety measure, the word <b>ALL</b> must be specified in upper case.
This feature is available in Postfix 2.0 and later.
- <b>-p</b> Purge old temporary files that are left over after system or
- software crashes.
+ <b>-p</b> Purge old temporary files that are left over after system or
+ software crashes. The <b>-p</b>, <b>-s</b>, and <b>-S</b> operations are done before
+ other operations.
<b>-r</b> <i>queue</i><b>_</b><i>id</i>
Requeue the message with the named queue ID from the named mail
This feature is available in Postfix 1.1 and later.
<b>-s</b> Structure check and structure repair. This should be done once
- before Postfix startup.
+ before Postfix startup. The <b>-p</b>, <b>-s</b>, and <b>-S</b> operations are done
+ before other operations.
- <b>o</b> Rename files whose name does not match the message file
+ <b>o</b> Rename files whose name does not match the message file
inode number. This operation is necessary after restoring
- a mail queue from a different machine or from backup,
+ a mail queue from a different machine or from backup,
when queue files were created with Postfix <= 2.8 or with
"<a href="postconf.5.html#enable_long_queue_ids">enable_long_queue_ids</a> = no".
- <b>o</b> Move queue files that are in the wrong place in the file
- system hierarchy and remove subdirectories that are no
- longer needed. File position rearrangements are neces-
+ <b>o</b> Move queue files that are in the wrong place in the file
+ system hierarchy and remove subdirectories that are no
+ longer needed. File position rearrangements are neces-
sary after a change in the <b><a href="postconf.5.html#hash_queue_names">hash_queue_names</a></b> and/or
<b><a href="postconf.5.html#hash_queue_depth">hash_queue_depth</a></b> configuration parameters.
- <b>o</b> Rename queue files created with "<a href="postconf.5.html#enable_long_queue_ids">enable_long_queue_ids</a> =
- yes" to short names, for migration to Postfix <= 2.8.
+ <b>o</b> Rename queue files created with "<a href="postconf.5.html#enable_long_queue_ids">enable_long_queue_ids</a> =
+ yes" to short names, for migration to Postfix <= 2.8.
The procedure is as follows:
# postfix stop
Run <a href="postsuper.1.html"><b>postsuper</b>(1)</a> repeatedly until it stops reporting file
name changes.
- <b>-S</b> A redundant version of <b>-s</b> that requires that long file names
+ <b>-S</b> A redundant version of <b>-s</b> that requires that long file names
also match the message file inode number. This option exists for
- testing purposes, and is available with Postfix 2.9 and later.
+ testing purposes, and is available with Postfix 2.9 and later.
+ The <b>-p</b>, <b>-s</b>, and <b>-S</b> operations are done before other operations.
- <b>-v</b> Enable verbose logging for debugging purposes. Multiple <b>-v</b>
+ <b>-v</b> Enable verbose logging for debugging purposes. Multiple <b>-v</b>
options make the software increasingly verbose.
<b>DIAGNOSTICS</b>
<a href="postlogd.8.html"><b>postlogd</b>(8)</a>.
<a href="postsuper.1.html"><b>postsuper</b>(1)</a> reports the number of messages deleted with <b>-d</b>, the number
- of messages requeued with <b>-r</b>, and the number of messages whose queue
- file name was fixed with <b>-s</b>. The report is written to the standard
- error stream and to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>.
+ of messages expired with <b>-e</b>, the number of messages expired or released
+ with <b>-f</b>, the number of messages held or released with <b>-h</b> or <b>-H</b>, the
+ number of messages requeued with <b>-r</b>, and the number of messages whose
+ queue file name was fixed with <b>-s</b>. The report is written to the stan-
+ dard error stream and to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>.
<b>ENVIRONMENT</b>
MAIL_CONFIG
cannot be placed "on hold".
<b>CONFIGURATION PARAMETERS</b>
- The following <a href="postconf.5.html"><b>main.cf</b></a> parameters are especially relevant to this pro-
- gram. The text below provides only a parameter summary. See <a href="postconf.5.html"><b>post-</b></a>
+ The following <a href="postconf.5.html"><b>main.cf</b></a> parameters are especially relevant to this pro-
+ gram. The text below provides only a parameter summary. See <a href="postconf.5.html"><b>post-</b></a>
<a href="postconf.5.html"><b>conf</b>(5)</a> for more details including examples.
<b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
- The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
figuration files.
<b><a href="postconf.5.html#hash_queue_depth">hash_queue_depth</a> (1)</b>
- The number of subdirectory levels for queue directories listed
+ The number of subdirectory levels for queue directories listed
with the <a href="postconf.5.html#hash_queue_names">hash_queue_names</a> parameter.
<b><a href="postconf.5.html#hash_queue_names">hash_queue_names</a> (deferred, defer)</b>
- The names of queue directories that are split across multiple
+ The names of queue directories that are split across multiple
subdirectory levels.
<b><a href="postconf.5.html#import_environment">import_environment</a> (see 'postconf -d' output)</b>
- The list of environment parameters that a privileged Postfix
- process will import from a non-Postfix parent process, or
+ The list of environment parameters that a privileged Postfix
+ process will import from a non-Postfix parent process, or
name=value environment overrides.
<b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
The syslog facility of Postfix logging.
<b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
- A prefix that is prepended to the process name in syslog
+ A prefix that is prepended to the process name in syslog
records, so that, for example, "smtpd" becomes "prefix/smtpd".
Available in Postfix version 2.9 and later:
.IP \fB!\fR
The message is in the \fBhold\fR queue, i.e. no further delivery
attempt will be made until the mail is taken off hold.
+.IP \fB#\fR
+The message is forced to expire. See the \fBpostsuper\fR(1)
+options \fB\-e\fR or \fB\-f\fR.
+.sp
+This feature is available in Postfix 3.5 and later.
.RE
.IP "\fB\-s \fIsite\fR"
Schedule immediate delivery of all mail that is queued for the named
number does not include message envelope information. It
is approximately equal to the number of bytes that would
be transmitted via SMTP including the <CR><LF> line endings.
+.IP \fBforced_expire\fR
+The message is forced to expire (\fBtrue\fR or \fBfalse\fR).
+See the \fBpostsuper\fR(1) options \fB\-e\fR or \fB\-f\fR.
+.sp
+This feature is available in Postfix 3.5 and later.
.IP \fBsender\fR
The envelope sender address.
.IP \fBrecipients\fR
.nf
.fi
\fBpostsuper\fR [\fB\-psSv\fR]
-[\fB\-c \fIconfig_dir\fR] [\fB\-d \fIqueue_id\fR]
+ [\fB\-c \fIconfig_dir\fR] [\fB\-d \fIqueue_id\fR]
+ [\fB\-e \fIqueue_id\fR] [\fB\-f \fIqueue_id\fR]
[\fB\-h \fIqueue_id\fR] [\fB\-H \fIqueue_id\fR]
[\fB\-r \fIqueue_id\fR] [\fIdirectory ...\fR]
.SH DESCRIPTION
By default, \fBpostsuper\fR(1) performs the operations
requested with the
\fB\-s\fR and \fB\-p\fR command\-line options on all Postfix queue
-directories \- this includes the \fBincoming\fR, \fBactive\fR and
-\fBdeferred\fR directories with mail files and the \fBbounce\fR,
+directories \- this includes the \fBincoming\fR, \fBactive\fR,
+\fBdeferred\fR, and \fBhold\fR directories with message
+files and the \fBbounce\fR,
\fBdefer\fR, \fBtrace\fR and \fBflush\fR directories with log files.
Options:
\fBpostsuper\fR(1) deletes the new message, instead of the old
message that it should have deleted.
.RE
+.IP "\fB\-e \fIqueue_id\fR"
+.IP "\fB\-f \fIqueue_id\fR"
+Request forced expiration for one message with the named
+queue ID in the named mail queue(s) (default: \fBhold\fR,
+\fBincoming\fR, \fBactive\fR and \fBdeferred\fR).
+.RS
+.IP \(bu
+The message will be returned to the sender when the queue
+manager attempts to deliver that message (note that Postfix
+will never deliver messages in the \fBhold\fR queue).
+.IP \(bu
+The \fB\-e\fR and \fB\-f\fR options both request forced
+expiration. The difference is that \fB\-f\fR will also release
+a message if it is in the hold queue. With \fB\-e\fR, such
+a message would not be returned to the sender until it is
+released with \fB\-f\fR or \fB\-H\fR.
+.IP \(bu
+When a deferred message is force\-expired, the return message
+will state the reason for the delay. Otherwise, the reason
+will be "message is administratively expired".
+.RE
+.IP
+To expire multiple files, specify the \fB\-e\fR or \fB\-f\fR
+option multiple times, or specify a \fIqueue_id\fR of \fB\-\fR
+to read queue IDs from standard input (see the \fB\-d\fR option
+above for an example, but be sure to replace \fB\-d\fR in
+the example).
+.sp
+Specify "\fB\-e ALL\fR" or "\fB\-f ALL\fR" to expire all
+messages; for example, specify "\fB\-e ALL deferred\fR" to
+expire all mail in the \fBdeferred\fR queue. As a safety
+measure, the word \fBALL\fR must be specified in upper case.
+.sp
+These features are available in Postfix 3.5 and later.
.IP "\fB\-h \fIqueue_id\fR"
Put mail "on hold" so that no attempt is made to deliver it.
Move one message with the named queue ID from the named
.IP \fB\-p\fR
Purge old temporary files that are left over after system or
software crashes.
+The \fB\-p\fR, \fB\-s\fR, and \fB\-S\fR operations are done
+before other operations.
.IP "\fB\-r \fIqueue_id\fR"
Requeue the message with the named queue ID from the named
mail queue(s) (default: \fBhold\fR, \fBincoming\fR, \fBactive\fR and
.IP \fB\-s\fR
Structure check and structure repair. This should be done once
before Postfix startup.
+The \fB\-p\fR, \fB\-s\fR, and \fB\-S\fR operations are done
+before other operations.
.RS
.IP \(bu
Rename files whose name does not match the message file inode
file names also match the message file inode number. This
option exists for testing purposes, and is available with
Postfix 2.9 and later.
+The \fB\-p\fR, \fB\-s\fR, and \fB\-S\fR operations are done
+before other operations.
.IP \fB\-v\fR
Enable verbose logging for debugging purposes. Multiple \fB\-v\fR
options make the software increasingly verbose.
Problems are reported to the standard error stream and to
\fBsyslogd\fR(8) or \fBpostlogd\fR(8).
-\fBpostsuper\fR(1) reports the number of messages deleted with \fB\-d\fR,
-the number of messages requeued with \fB\-r\fR, and the number of
-messages whose queue file name was fixed with \fB\-s\fR. The report
-is written to the standard error stream and to \fBsyslogd\fR(8)
-or \fBpostlogd\fR(8).
+\fBpostsuper\fR(1) reports the number of messages deleted
+with \fB\-d\fR, the number of messages expired with \fB\-e\fR,
+the number of messages expired or released with \fB\-f\fR,
+the number of messages held or released with \fB\-h\fR or
+\fB\-H\fR, the number of messages requeued with \fB\-r\fR,
+and the number of messages whose queue file name was fixed
+with \fB\-s\fR. The report is written to the standard error
+stream and to \fBsyslogd\fR(8) or \fBpostlogd\fR(8).
.SH "ENVIRONMENT"
.na
.nf
s/[<bB>]*postmap[<\/bB>]*\(1\)/<a href="postmap.1.html">$&<\/a>/g;
s/[<bB>]*postmulti[<\/bB>]*\(1\)/<a href="postmulti.1.html">$&<\/a>/g;
s/[<bB>]*postqueue[<\/bB>]*\(1\)/<a href="postqueue.1.html">$&<\/a>/g;
- s/[<bB>]*post[-<\/bB>]*\n*[ <bB>]*super[<\/bB>]*\(1\)/<a href="postsuper.1.html">$&<\/a>/g;
+ s/[<bB>]*post[-<\/bB>]*\n*[ <bB>]*su[-<\/bB>]*\n*[ <bB>]*per[<\/bB>]*\(1\)/<a href="postsuper.1.html">$&<\/a>/g;
s/[<bB>]*post[-<\/bB>]*\n*[ <bB>]*tls-finger[<\/bB>]*\(1\)/<a href="posttls-finger.1.html">$&<\/a>/g;
s/[<bB>]*send[-<\/bB>]*\n*[ <bB>]*mail[<\/bB>]*\(1\)/<a href="sendmail.1.html">$&<\/a>/g;
s/[<bB>]*smtp-[<\/bB>]*\n* *[<bB>]*source[<\/bB>]*\(1\)/<a href="smtp-source.1.html">$&<\/a>/g;
#define MAIL_ATTR_LOG_IDENT "log_ident"
#define MAIL_ATTR_RWR_CONTEXT "rewrite_context"
#define MAIL_ATTR_POL_CONTEXT "policy_context"
+#define MAIL_ATTR_FORCED_EXPIRE "forced_expire"
#define MAIL_ATTR_RWR_LOCAL "local"
#define MAIL_ATTR_RWR_REMOTE "remote"
#define MAIL_QUEUE_STAT_CORRUPT (S_IRUSR)
#ifndef MAIL_QUEUE_STAT_UNTHROTTLE
#define MAIL_QUEUE_STAT_UNTHROTTLE (S_IRGRP)
+#define MAIL_QUEUE_STAT_EXPIRE (S_IXGRP)
#endif
extern struct VSTREAM *mail_queue_enter(const char *, mode_t, struct timeval *);
* Patches change both the patchlevel and the release date. Snapshots have no
* patchlevel; they change the release date only.
*/
-#define MAIL_RELEASE_DATE "20200126"
+#define MAIL_RELEASE_DATE "20200202"
#define MAIL_VERSION_NUMBER "3.5"
#ifdef SNAPSHOT
#define QMGR_FLUSH_ONCE (1<<2) /* unthrottle once */
#define QMGR_FLUSH_DFXP (1<<3) /* override defer_transports */
#define QMGR_FLUSH_EACH (1<<4) /* unthrottle per message */
+#define QMGR_FORCE_EXPIRE (1<<5) /* force-defer and force-expire */
/*
* qmgr_scan.c
* a minimal amount of time.
*/
#define QMGR_FLUSH_AFTER (QMGR_FLUSH_EACH | QMGR_FLUSH_DFXP)
+#define MAYBE_FLUSH_AFTER(mode) \
+ (((mode) & MAIL_QUEUE_STAT_UNTHROTTLE) ? QMGR_FLUSH_AFTER : 0)
+#define MAYBE_FORCE_EXPIRE(mode) \
+ (((mode) & MAIL_QUEUE_STAT_EXPIRE) ? QMGR_FORCE_EXPIRE : 0)
+#define MAYBE_UPDATE_MODE(mode) \
+ (((mode) & MAIL_QUEUE_STAT_UNTHROTTLE) ? \
+ (mode) & ~MAIL_QUEUE_STAT_UNTHROTTLE : 0)
if ((message = qmgr_message_alloc(MAIL_QUEUE_ACTIVE, queue_id,
- (st.st_mode & MAIL_QUEUE_STAT_UNTHROTTLE) ?
- scan_info->flags | QMGR_FLUSH_AFTER :
- scan_info->flags,
- (st.st_mode & MAIL_QUEUE_STAT_UNTHROTTLE) ?
- st.st_mode & ~MAIL_QUEUE_STAT_UNTHROTTLE :
- 0)) == 0) {
+ scan_info->flags
+ | MAYBE_FLUSH_AFTER(st.st_mode)
+ | MAYBE_FORCE_EXPIRE(st.st_mode),
+ MAYBE_UPDATE_MODE(st.st_mode))) == 0) {
qmgr_active_corrupt(queue_id);
return (0);
} else if (message == QMGR_MESSAGE_LOCKED) {
static void qmgr_active_done_25_generic(QMGR_MESSAGE *message)
{
const char *myname = "qmgr_active_done_25_generic";
+ const char *expire_status = 0;
/*
* If we get to this point we have tried all recipients for this message.
* daemon waits for the qmgr to accept the "new mail" trigger.
*/
if (message->flags) {
- if (event_time() >= message->create_time +
- (*message->sender ? var_max_queue_time : var_dsn_queue_time)) {
- msg_info("%s: from=<%s>, status=expired, returned to sender",
- message->queue_id, info_log_addr_form_sender(message->sender));
+ if ((message->qflags & QMGR_FORCE_EXPIRE) != 0) {
+ expire_status = "force-expired";
+ } else if (event_time() >= message->create_time +
+ (*message->sender ? var_max_queue_time : var_dsn_queue_time)) {
+ expire_status = "expired";
+ } else {
+ expire_status = 0;
+ }
+ if (expire_status != 0) {
+ msg_info("%s: from=<%s>, status=%s, returned to sender",
+ message->queue_id, info_log_addr_form_sender(message->sender),
+ expire_status);
if (message->verp_delims == 0 || var_verp_bounce_off)
adefer_flush(BOUNCE_FLAG_KEEP,
message->queue_name,
"5.1.3 null recipient address");
}
+ /*
+ * Redirect a forced-to-expire message without defer log to the retry
+ * service, so that its defer log will contain an appropriate reason.
+ * Do not redirect such a message to the error service, because if
+ * that request fails, a defer log would be created with reason
+ * "bounce or trace service failure" which would make no sense. Note
+ * that if the bounce service fails to create a defer log, the
+ * message will be returned as undeliverable anyway, because it is
+ * expired.
+ */
+ if ((message->qflags & QMGR_FORCE_EXPIRE) != 0) {
+ QMGR_REDIRECT(&reply, MAIL_SERVICE_RETRY,
+ "4.7.0 message is administratively expired");
+ }
+
/*
* Discard mail to the local double bounce address here, so this
* system can run without a local delivery agent. They'd still have
{
const char *myname = "qmgr_message_alloc";
QMGR_MESSAGE *message;
+ struct stat st;
if (msg_verbose)
msg_info("%s: %s %s", myname, queue_name, queue_id);
if (mode != 0 && fchmod(vstream_fileno(message->fp), mode) < 0)
msg_fatal("fchmod %s: %m", VSTREAM_PATH(message->fp));
+ /*
+ * If this message is forced to expire, use the existing defer
+ * logfile records and do not assign any deliveries, leaving the
+ * refcount at zero. If this message is forced to expire, but no
+ * defer logfile records are available, assign deliveries to the
+ * retry transport so that the sender will still find out what
+ * recipients are affected and why. Either way, do not assign normal
+ * deliveries because that would be undesirable especially with mail
+ * that was expired in the 'hold' queue.
+ */
+ if ((message->qflags & QMGR_FORCE_EXPIRE) != 0
+ && stat(mail_queue_path((VSTRING *) 0, MAIL_QUEUE_DEFER,
+ queue_id), &st) == 0 && st.st_size > 0) {
+ /* Use this defer log; don't assign deliveries (refcount == 0). */
+ message->flags = 1; /* simplify downstream code */
+ qmgr_message_close(message);
+ return (message);
+ }
+
/*
* Reset the defer log. This code should not be here, but we must
* reset the defer log *after* acquiring the exclusive lock on the
/* .IP \fB!\fR
/* The message is in the \fBhold\fR queue, i.e. no further delivery
/* attempt will be made until the mail is taken off hold.
+/* .IP \fB#\fR
+/* The message is forced to expire. See the \fBpostsuper\fR(1)
+/* options \fB-e\fR or \fB-f\fR.
+/* .sp
+/* This feature is available in Postfix 3.5 and later.
/* .RE
/* .IP "\fB-s \fIsite\fR"
/* Schedule immediate delivery of all mail that is queued for the named
/* number does not include message envelope information. It
/* is approximately equal to the number of bytes that would
/* be transmitted via SMTP including the <CR><LF> line endings.
+/* .IP \fBforced_expire\fR
+/* The message is forced to expire (\fBtrue\fR or \fBfalse\fR).
+/* See the \fBpostsuper\fR(1) options \fB-e\fR or \fB-f\fR.
+/* .sp
+/* This feature is available in Postfix 3.5 and later.
/* .IP \fBsender\fR
/* The envelope sender address.
/* .IP \fBrecipients\fR
static VSTRING *why = 0;
long arrival_time;
long message_size;
- int message_status;
char *saved_reason = mystrdup("");
const char *show_reason;
int padding;
int showq_status;
time_t time_t_arrival_time;
+ int forced_expire;
/*
* One-time initialization.
RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id),
RECV_ATTR_LONG(MAIL_ATTR_TIME, &arrival_time),
RECV_ATTR_LONG(MAIL_ATTR_SIZE, &message_size),
+ RECV_ATTR_INT(MAIL_ATTR_FORCED_EXPIRE, &forced_expire),
RECV_ATTR_STR(MAIL_ATTR_SENDER, addr),
- ATTR_TYPE_END) != 5)
+ ATTR_TYPE_END) != 6)
msg_fatal_status(EX_SOFTWARE, "malformed showq server response");
/*
* left-aligned, followed by other status info and the sender address
* which is already in externalized RFC 5321 form.
*/
- message_status = (strcmp(STR(queue_name), MAIL_QUEUE_ACTIVE) == 0 ? '*' :
- strcmp(STR(queue_name), MAIL_QUEUE_HOLD) == 0 ? '!' : ' ');
- vstring_sprintf(id_status, "%s%c", STR(queue_id), message_status);
+ vstring_strcpy(id_status, STR(queue_id));
+ if (strcmp(STR(queue_name), MAIL_QUEUE_ACTIVE) == 0)
+ vstring_strcat(id_status, "*");
+ else if (strcmp(STR(queue_name), MAIL_QUEUE_HOLD) == 0)
+ vstring_strcat(id_status, "!");
+ if (forced_expire)
+ vstring_strcat(id_status, "#");
time_t_arrival_time = arrival_time;
vstream_printf(var_long_queue_ids ?
L_SENDER_FORMAT : S_SENDER_FORMAT, STR(id_status),
long message_size;
int showq_status;
int rcpt_count = 0;
+ int forced_expire;
/*
* One-time initialization.
RECV_ATTR_STR(MAIL_ATTR_QUEUEID, queue_id),
RECV_ATTR_LONG(MAIL_ATTR_TIME, &arrival_time),
RECV_ATTR_LONG(MAIL_ATTR_SIZE, &message_size),
+ RECV_ATTR_INT(MAIL_ATTR_FORCED_EXPIRE, &forced_expire),
RECV_ATTR_STR(MAIL_ATTR_SENDER, addr),
- ATTR_TYPE_END) != 5)
+ ATTR_TYPE_END) != 6)
msg_fatal_status(EX_SOFTWARE, "malformed showq server response");
vstream_printf("{");
vstream_printf("\"queue_name\": \"%s\", ",
json_quote(quote_buf, STR(queue_id)));
vstream_printf("\"arrival_time\": %ld, ", arrival_time);
vstream_printf("\"message_size\": %ld, ", message_size);
+ vstream_printf("\"forced_expire\": %s, ", forced_expire ? "true" : "false");
vstream_printf("\"sender\": \"%s\", ",
json_quote(quote_buf, STR(addr)));
postsuper.o: ../../include/msg_vstream.h
postsuper.o: ../../include/mymalloc.h
postsuper.o: ../../include/myrand.h
+postsuper.o: ../../include/name_mask.h
postsuper.o: ../../include/safe.h
+postsuper.o: ../../include/safe_open.h
postsuper.o: ../../include/safe_ultostr.h
postsuper.o: ../../include/sane_fsops.h
postsuper.o: ../../include/scan_dir.h
/* SYNOPSIS
/* .fi
/* \fBpostsuper\fR [\fB-psSv\fR]
-/* [\fB-c \fIconfig_dir\fR] [\fB-d \fIqueue_id\fR]
+/* [\fB-c \fIconfig_dir\fR] [\fB-d \fIqueue_id\fR]
+/* [\fB-e \fIqueue_id\fR] [\fB-f \fIqueue_id\fR]
/* [\fB-h \fIqueue_id\fR] [\fB-H \fIqueue_id\fR]
/* [\fB-r \fIqueue_id\fR] [\fIdirectory ...\fR]
/* DESCRIPTION
/* By default, \fBpostsuper\fR(1) performs the operations
/* requested with the
/* \fB-s\fR and \fB-p\fR command-line options on all Postfix queue
-/* directories - this includes the \fBincoming\fR, \fBactive\fR and
-/* \fBdeferred\fR directories with mail files and the \fBbounce\fR,
+/* directories - this includes the \fBincoming\fR, \fBactive\fR,
+/* \fBdeferred\fR, and \fBhold\fR directories with message
+/* files and the \fBbounce\fR,
/* \fBdefer\fR, \fBtrace\fR and \fBflush\fR directories with log files.
/*
/* Options:
/* \fBpostsuper\fR(1) deletes the new message, instead of the old
/* message that it should have deleted.
/* .RE
+/* .IP "\fB-e \fIqueue_id\fR"
+/* .IP "\fB-f \fIqueue_id\fR"
+/* Request forced expiration for one message with the named
+/* queue ID in the named mail queue(s) (default: \fBhold\fR,
+/* \fBincoming\fR, \fBactive\fR and \fBdeferred\fR).
+/* .RS
+/* .IP \(bu
+/* The message will be returned to the sender when the queue
+/* manager attempts to deliver that message (note that Postfix
+/* will never deliver messages in the \fBhold\fR queue).
+/* .IP \(bu
+/* The \fB-e\fR and \fB-f\fR options both request forced
+/* expiration. The difference is that \fB-f\fR will also release
+/* a message if it is in the hold queue. With \fB-e\fR, such
+/* a message would not be returned to the sender until it is
+/* released with \fB-f\fR or \fB-H\fR.
+/* .IP \(bu
+/* When a deferred message is force-expired, the return message
+/* will state the reason for the delay. Otherwise, the reason
+/* will be "message is administratively expired".
+/* .RE
+/* .IP
+/* To expire multiple files, specify the \fB-e\fR or \fB-f\fR
+/* option multiple times, or specify a \fIqueue_id\fR of \fB-\fR
+/* to read queue IDs from standard input (see the \fB-d\fR option
+/* above for an example, but be sure to replace \fB-d\fR in
+/* the example).
+/* .sp
+/* Specify "\fB-e ALL\fR" or "\fB-f ALL\fR" to expire all
+/* messages; for example, specify "\fB-e ALL deferred\fR" to
+/* expire all mail in the \fBdeferred\fR queue. As a safety
+/* measure, the word \fBALL\fR must be specified in upper case.
+/* .sp
+/* These features are available in Postfix 3.5 and later.
/* .IP "\fB-h \fIqueue_id\fR"
/* Put mail "on hold" so that no attempt is made to deliver it.
/* Move one message with the named queue ID from the named
/* .IP \fB-p\fR
/* Purge old temporary files that are left over after system or
/* software crashes.
+/* The \fB-p\fR, \fB-s\fR, and \fB-S\fR operations are done
+/* before other operations.
/* .IP "\fB-r \fIqueue_id\fR"
/* Requeue the message with the named queue ID from the named
/* mail queue(s) (default: \fBhold\fR, \fBincoming\fR, \fBactive\fR and
/* .IP \fB-s\fR
/* Structure check and structure repair. This should be done once
/* before Postfix startup.
+/* The \fB-p\fR, \fB-s\fR, and \fB-S\fR operations are done
+/* before other operations.
/* .RS
/* .IP \(bu
/* Rename files whose name does not match the message file inode
/* file names also match the message file inode number. This
/* option exists for testing purposes, and is available with
/* Postfix 2.9 and later.
+/* The \fB-p\fR, \fB-s\fR, and \fB-S\fR operations are done
+/* before other operations.
/* .IP \fB-v\fR
/* Enable verbose logging for debugging purposes. Multiple \fB-v\fR
/* options make the software increasingly verbose.
/* Problems are reported to the standard error stream and to
/* \fBsyslogd\fR(8) or \fBpostlogd\fR(8).
/*
-/* \fBpostsuper\fR(1) reports the number of messages deleted with \fB-d\fR,
-/* the number of messages requeued with \fB-r\fR, and the number of
-/* messages whose queue file name was fixed with \fB-s\fR. The report
-/* is written to the standard error stream and to \fBsyslogd\fR(8)
-/* or \fBpostlogd\fR(8).
+/* \fBpostsuper\fR(1) reports the number of messages deleted
+/* with \fB-d\fR, the number of messages expired with \fB-e\fR,
+/* the number of messages expired or released with \fB-f\fR,
+/* the number of messages held or released with \fB-h\fR or
+/* \fB-H\fR, the number of messages requeued with \fB-r\fR,
+/* and the number of messages whose queue file name was fixed
+/* with \fB-s\fR. The report is written to the standard error
+/* stream and to \fBsyslogd\fR(8) or \fBpostlogd\fR(8).
/* ENVIRONMENT
/* .ad
/* .fi
#include <myrand.h>
#include <warn_stat.h>
#include <clean_env.h>
+#include <safe_open.h>
+#include <name_mask.h>
/* Global library. */
/* Application-specific. */
-#define MAX_TEMP_AGE (60 * 60 * 24) /* temp file maximal age */
+#define MAX_TEMP_AGE (7 * 60 * 60 * 24) /* temp file maximal age */
#define STR vstring_str /* silly little macro */
#define ACTION_STRUCT (1<<0) /* fix file organization */
#define ACTION_RELEASE_ONE (1<<8) /* release named queue file(s) */
#define ACTION_RELEASE_ALL (1<<9) /* release all "on hold" mail */
#define ACTION_STRUCT_RED (1<<10) /* fix long queue ID inode fields */
+#define ACTION_EXPIRE_ONE (1<<11) /* expire named queue file(s) */
+#define ACTION_EXPIRE_ALL (1<<12) /* expire all queue file(s) */
+#define ACTION_EXP_REL_ONE (1<<13) /* expire+release named queue file(s) */
+#define ACTION_EXP_REL_ALL (1<<14) /* expire+release all queue file(s) */
#define ACTION_DEFAULT (ACTION_STRUCT | ACTION_PURGE)
/*
* Actions that operate on individually named queue files. These must never
- * be done when queue file names are changed to match their inode number.
+ * be done after fixing queue file names to match their inode number because
+ * the target file may have been replaced. Actions that move files are safe
+ * only when queue file names match their inode number, otherwise mail can
+ * be lost due to filename collisions.
*/
#define ACTIONS_BY_QUEUE_ID (ACTION_DELETE_ONE | ACTION_REQUEUE_ONE \
- | ACTION_HOLD_ONE | ACTION_RELEASE_ONE)
+ | ACTION_HOLD_ONE | ACTION_RELEASE_ONE \
+ | ACTION_EXPIRE_ONE | ACTION_EXP_REL_ONE)
/*
- * Mass rename operations that are postponed to a second pass after queue
- * file names are changed to match their inode number.
+ * Mass actions. Actions that move files are safe only when queue file names
+ * match their inode number, otherwise mail can be lost due to filename
+ * collisions.
*/
-#define ACTIONS_AFTER_INUM_FIX (ACTION_REQUEUE_ALL | ACTION_HOLD_ALL \
- | ACTION_RELEASE_ALL)
+#define ACTIONS_BY_WILDCARD (ACTION_DELETE_ALL | ACTION_REQUEUE_ALL \
+ | ACTION_HOLD_ALL | ACTION_RELEASE_ALL \
+ | ACTION_EXPIRE_ALL | ACTION_EXP_REL_ALL)
+
+#define ACTIONS_FOR_REPAIR (ACTION_PURGE | ACTION_STRUCT \
+ | ACTION_STRUCT_RED)
/*
* Information about queue directories and what we expect to do there. If a
static int message_held = 0; /* messages put on hold */
static int message_released = 0; /* messages released from hold */
static int message_deleted = 0; /* deleted messages */
+static int message_expired = 0; /* expired messages */
static int inode_fixed = 0; /* queue id matched to inode number */
static int inode_mismatch = 0; /* queue id inode mismatch */
static int position_mismatch = 0; /* file position mismatch */
return (ret);
}
+/* postexpire - expire file, setting the group execute permission */
+
+static int postexpire(const char *path)
+{
+ static VSTRING *why = 0;
+ VSTREAM *fp;
+ struct stat st;
+
+ /*
+ * Initialize.
+ */
+ if (why == 0)
+ why = vstring_alloc(100);
+
+ /*
+ * We don't actually verify the file content, therefore safe_open() the
+ * queue file so that we won't add group execute permission to some file
+ * outside of the mail queue.
+ */
+ if ((fp = safe_open(path, O_RDWR, 0, &st, -1, -1, why)) == 0) {
+ if (errno != ENOENT)
+ msg_warn("expire file %s: %s", path, vstring_str(why));
+ return (-1);
+ }
+#define POSTEXPIRE_RETURN(x) do { \
+ (void) vstream_fclose(fp); \
+ return (x); \
+ } while (0)
+
+ if (!READY_MESSAGE(st))
+ POSTEXPIRE_RETURN(-1); /* must not expire */
+ if ((st.st_mode & MAIL_QUEUE_STAT_EXPIRE) != 0)
+ POSTEXPIRE_RETURN(-1); /* already expired */
+ if (fchmod(vstream_fileno(fp),
+ (st.st_mode | MAIL_QUEUE_STAT_EXPIRE) & ~S_IFMT) < 0) {
+ msg_warn("expire file %s: cannot set permission: %m", path);
+ POSTEXPIRE_RETURN(-1);
+ }
+ POSTEXPIRE_RETURN(0);
+}
+
/* postrename - rename file with extreme prejudice */
static int postrename(const char *old, const char *new)
/* delete_one - delete one message instance and all its associated files */
-static int delete_one(const char **queue_names, const char *queue_id)
+static void delete_one(const char **queue_names, const char *queue_id)
{
struct stat st;
const char **msg_qpp;
*/
if (!mail_queue_id_ok(queue_id)) {
msg_warn("invalid mail queue id: %s", queue_id);
- return (0);
+ return;
}
log_path_buf = vstring_alloc(100);
}
}
vstring_free(log_path_buf);
- return (found);
+ message_deleted += found;
}
/* requeue_one - requeue one message instance and delete its logfiles */
-static int requeue_one(const char **queue_names, const char *queue_id)
+static void requeue_one(const char **queue_names, const char *queue_id)
{
struct stat st;
const char **msg_qpp;
*/
if (!mail_queue_id_ok(queue_id)) {
msg_warn("invalid mail queue id: %s", queue_id);
- return (0);
+ return;
}
new_path_buf = vstring_alloc(100);
}
}
vstring_free(new_path_buf);
- return (found);
+ message_requeued += found;
}
/* hold_one - put "on hold" one message instance */
-static int hold_one(const char **queue_names, const char *queue_id)
+static void hold_one(const char **queue_names, const char *queue_id)
{
struct stat st;
const char **msg_qpp;
*/
if (!mail_queue_id_ok(queue_id)) {
msg_warn("invalid mail queue id: %s", queue_id);
- return (0);
+ return;
}
new_path_buf = vstring_alloc(100);
}
}
vstring_free(new_path_buf);
- return (found);
+ message_held += found;
}
/* release_one - release one message instance that was placed "on hold" */
-static int release_one(const char **queue_names, const char *queue_id)
+static void release_one(const char **queue_names, const char *queue_id)
{
struct stat st;
const char **msg_qpp;
*/
if (!mail_queue_id_ok(queue_id)) {
msg_warn("invalid mail queue id: %s", queue_id);
- return (0);
+ return;
}
new_path_buf = vstring_alloc(100);
}
}
vstring_free(new_path_buf);
- return (found);
+ message_released += found;
+}
+
+/* expire_one - expire one message instance */
+
+static void expire_one(const char **queue_names, const char *queue_id)
+{
+ struct stat st;
+ const char **msg_qpp;
+ const char *msg_path;
+ int found;
+ int tries;
+
+ /*
+ * Sanity check. No early returns beyond this point.
+ */
+ if (!mail_queue_id_ok(queue_id)) {
+ msg_warn("invalid mail queue id: %s", queue_id);
+ return;
+ }
+
+ /*
+ * Skip meta file directories.
+ */
+ for (found = 0, tries = 0; found == 0 && tries < 2; tries++) {
+ for (msg_qpp = queue_names; *msg_qpp != 0; msg_qpp++) {
+ if (!MESSAGE_QUEUE(find_queue_info(*msg_qpp)))
+ continue;
+ if (strcmp(*msg_qpp, MAIL_QUEUE_MAILDROP) == 0)
+ continue;
+ if (mail_open_ok(*msg_qpp, queue_id, &st, &msg_path) != MAIL_OPEN_YES)
+ continue;
+ if (postexpire(msg_path) == 0) {
+ found = 1;
+ msg_info("%s: expired", queue_id);
+ break;
+ } /* else: maybe lost a race */
+ }
+ }
+ message_expired += found;
+}
+
+/* exp_rel_one - expire or release one message instance */
+
+static void exp_rel_one(const char **queue_names, const char *queue_id)
+{
+ expire_one(queue_names, queue_id);
+ release_one(queue_names, queue_id);
}
/* operate_stream - operate on queue IDs given on stream */
-static int operate_stream(VSTREAM *fp,
- int (*operator) (const char **, const char *),
- const char **queues)
+static void operate_stream(VSTREAM *fp,
+ void (*operator) (const char **, const char *),
+ const char **queues)
{
VSTRING *buf = vstring_alloc(20);
- int found = 0;
while (vstring_get_nonl(buf, fp) != VSTREAM_EOF)
- found += operator(queues, STR(buf));
+ operator(queues, STR(buf));
vstring_free(buf);
- return (found);
}
/* fix_queue_id - make message queue ID match inode number */
int long_name;
int error;
+ /*
+ * This routine was originally written to do multiple mass operations in
+ * one pass. However this hard-coded the order of operations which became
+ * difficult to explain. As of Postfix 3.5 this routine is called for one
+ * mass operation at a time, in the user-specified order. The exception
+ * is that repair operations (purging stale files, queue hashing, and
+ * file-inode match) are combined and done before other mass operations.
+ */
+
/*
* Make sure every file is in the right place, clean out stale files, and
* remove non-file/non-directory objects.
* otherwise we can hit the wrong files.
*/
vstring_sprintf(actual_path, "%s/%s", scan_dir_path(info), path);
- if (stat(STR(actual_path), &st) < 0)
+ if (lstat(STR(actual_path), &st) < 0)
continue;
/*
|| (inum != (unsigned long) st.st_ino
&& (long_name == 0 || (action & ACTION_STRUCT_RED)))) {
inode_mismatch++; /* before we fix */
- action &= ~ACTIONS_AFTER_INUM_FIX;
+ action &= ~ACTIONS_BY_WILDCARD;
fix_queue_id(STR(actual_path), queue_name, path, &st);
/* At this point, path and actual_path are invalidated. */
continue;
continue;
}
+ /*
+ * Many of the following actions may move queue files. To avoid
+ * loss of of email due to file name collisions. we should do
+ * such actions only when the queue file names are known to match
+ * their inode number. Even with non-repeating queue IDs a name
+ * collision may happen when different queues are merged.
+ */
+
+ /*
+ * Mass expiration. We count the expiration of mail that this
+ * system has taken responsibility for.
+ */
+ if ((action & (ACTION_EXPIRE_ALL | ACTION_EXP_REL_ALL))
+ && MESSAGE_QUEUE(qp) && READY_MESSAGE(st)
+ && strcmp(queue_name, MAIL_QUEUE_MAILDROP) != 0
+ && postexpire(STR(actual_path)) == 0)
+ message_expired++;
+
/*
* Mass renaming to the "on hold" queue. XXX This option does not
* use mail_queue_rename(), so that it can avoid having to first
* first move queue files to the "right" subdirectory level. Like
* the release_one() routine, this code must not touch logfiles.
*/
- if ((action & ACTION_RELEASE_ALL)
+ if ((action & (ACTION_RELEASE_ALL | ACTION_EXP_REL_ALL))
&& strcmp(queue_name, MAIL_QUEUE_HOLD) == 0) {
(void) mail_queue_path(wanted_path, MAIL_QUEUE_DEFERRED, path);
if (postrename(STR(actual_path), STR(wanted_path)) == 0)
int action = 0;
const char **queues;
int c;
- ARGV *requeue_names = 0;
- ARGV *delete_names = 0;
- ARGV *hold_names = 0;
- ARGV *release_names = 0;
- char **cpp;
ARGV *import_env;
+ int saved_optind;
/*
* Defaults. The structural checks must fix the directory levels of "log
MAIL_QUEUE_FLUSH,
0,
};
- static char *default_hold_queues[] = {
- MAIL_QUEUE_INCOMING,
- MAIL_QUEUE_ACTIVE,
- MAIL_QUEUE_DEFERRED,
- 0,
- };
- static char *default_release_queues[] = {
- MAIL_QUEUE_HOLD,
- 0,
- };
/*
* Fingerprint executables and core dumps.
/*
* Parse JCL.
+ *
+ * First, find out what kind of actions are requested, without executing
+ * them. Later, we execute actions in mostly user-specified order.
*/
- while ((c = GETOPT(argc, argv, "c:d:h:H:pr:sSv")) > 0) {
+#define GETOPT_LIST "c:d:e:f:h:H:pr:sSv"
+
+ saved_optind = optind;
+ while ((c = GETOPT(argc, argv, GETOPT_LIST)) > 0) {
switch (c) {
default:
msg_fatal("usage: %s "
"[-c config_dir] "
"[-d queue_id (delete)] "
+ "[-e queue_id (expire)] "
+ "[-f queue_id (expire and/or un-hold)] "
"[-h queue_id (hold)] [-H queue_id (un-hold)] "
"[-p (purge temporary files)] [-r queue_id (requeue)] "
"[-s (structure fix)] [-S (redundant structure fix)]"
msg_fatal("setenv: %m");
break;
case 'd':
- if (delete_names == 0)
- delete_names = argv_alloc(1);
- argv_add(delete_names, optarg, (char *) 0);
action |= (strcmp(optarg, "ALL") == 0 ?
ACTION_DELETE_ALL : ACTION_DELETE_ONE);
break;
+ case 'e':
+ action |= (strcmp(optarg, "ALL") == 0 ?
+ ACTION_EXPIRE_ALL : ACTION_EXPIRE_ONE);
+ break;
+ case 'f':
+ action |= (strcmp(optarg, "ALL") == 0 ?
+ ACTION_EXP_REL_ALL : ACTION_EXP_REL_ONE);
+ break;
case 'h':
- if (hold_names == 0)
- hold_names = argv_alloc(1);
- argv_add(hold_names, optarg, (char *) 0);
action |= (strcmp(optarg, "ALL") == 0 ?
ACTION_HOLD_ALL : ACTION_HOLD_ONE);
break;
case 'H':
- if (release_names == 0)
- release_names = argv_alloc(1);
- argv_add(release_names, optarg, (char *) 0);
action |= (strcmp(optarg, "ALL") == 0 ?
ACTION_RELEASE_ALL : ACTION_RELEASE_ONE);
break;
action |= ACTION_PURGE;
break;
case 'r':
- if (requeue_names == 0)
- requeue_names = argv_alloc(1);
- argv_add(requeue_names, optarg, (char *) 0);
action |= (strcmp(optarg, "ALL") == 0 ?
ACTION_REQUEUE_ALL : ACTION_REQUEUE_ONE);
break;
signal(SIGHUP, interrupted);
msg_cleanup(fatal_warning);
- /*
- * Sanity checks.
- */
- if ((action & ACTION_DELETE_ALL) && (action & ACTION_DELETE_ONE)) {
- msg_warn("option \"-d ALL\" will ignore other command line queue IDs");
- action &= ~ACTION_DELETE_ONE;
- }
- if ((action & ACTION_REQUEUE_ALL) && (action & ACTION_REQUEUE_ONE)) {
- msg_warn("option \"-r ALL\" will ignore other command line queue IDs");
- action &= ~ACTION_REQUEUE_ONE;
- }
- if ((action & ACTION_HOLD_ALL) && (action & ACTION_HOLD_ONE)) {
- msg_warn("option \"-h ALL\" will ignore other command line queue IDs");
- action &= ~ACTION_HOLD_ONE;
- }
- if ((action & ACTION_RELEASE_ALL) && (action & ACTION_RELEASE_ONE)) {
- msg_warn("option \"-H ALL\" will ignore other command line queue IDs");
- action &= ~ACTION_RELEASE_ONE;
- }
-
/*
* Execute the explicitly specified (or default) action, on the
* explicitly specified (or default) queues.
action = ACTION_DEFAULT;
if (argv[optind] != 0)
queues = (const char **) argv + optind;
- else if (action == ACTION_HOLD_ALL)
- queues = (const char **) default_hold_queues;
- else if (action == ACTION_RELEASE_ALL)
- queues = (const char **) default_release_queues;
else
queues = (const char **) default_queues;
/*
- * Basic queue maintenance, as well as mass deletion, mass requeuing, and
- * mass name-to-inode fixing. This ensures that queue files are in the
- * right place before the file-by-name operations are done.
+ * Basic queue maintenance, including mass name-to-inode fixing. This
+ * ensures that queue files are in the right place before any other
+ * operations are done.
*/
- if (action & ~ACTIONS_BY_QUEUE_ID)
- super(queues, action);
+ if (action & ACTIONS_FOR_REPAIR)
+ super(queues, action & ACTIONS_FOR_REPAIR);
/*
* If any file names needed changing to match the message file inode
* operations that had to be skipped in the first pass.
*/
if (inode_mismatch > 0)
- super(queues, action);
+ super(queues, action & ACTIONS_FOR_REPAIR);
/*
* Don't do actions by queue file name if any queue files changed name
}
/*
- * Delete queue files by name. This must not be done when queue file
- * names have changed names as a result of inode number mismatches,
- * because we could be deleting the wrong message.
+ * Execute actions by queue ID and by wildcard in the user-specified
+ * order.
*/
- if (action & ACTION_DELETE_ONE) {
- argv_terminate(delete_names);
- queues = (const char **)
- (argv[optind] ? argv + optind : default_queues);
- for (cpp = delete_names->argv; *cpp; cpp++) {
- if (strcmp(*cpp, "ALL") == 0)
- continue;
- if (strcmp(*cpp, "-") == 0)
- message_deleted +=
- operate_stream(VSTREAM_IN, delete_one, queues);
+ optind = saved_optind;
+ while ((c = GETOPT(argc, argv, GETOPT_LIST)) > 0) {
+ switch (c) {
+ default:
+ msg_panic("%s: unexpected option: %c", argv[0], c);
+ case 'c':
+ case 'p':
+ case 'S':
+ case 's':
+ case 'v':
+ /* Already handled. */
+ break;
+ case 'd':
+ if (strcmp(optarg, "ALL") == 0)
+ super(queues, ACTION_DELETE_ALL);
+ else if (strcmp(optarg, "-") == 0)
+ operate_stream(VSTREAM_IN, delete_one, queues);
else
- message_deleted += delete_one(queues, *cpp);
- }
- }
-
- /*
- * Requeue queue files by name. This must not be done when queue file
- * names have changed names as a result of inode number mismatches,
- * because we could be requeuing the wrong message.
- */
- if (action & ACTION_REQUEUE_ONE) {
- argv_terminate(requeue_names);
- queues = (const char **)
- (argv[optind] ? argv + optind : default_queues);
- for (cpp = requeue_names->argv; *cpp; cpp++) {
- if (strcmp(*cpp, "ALL") == 0)
- continue;
- if (strcmp(*cpp, "-") == 0)
- message_requeued +=
- operate_stream(VSTREAM_IN, requeue_one, queues);
+ delete_one(queues, optarg);
+ break;
+ case 'e':
+ if (strcmp(optarg, "ALL") == 0)
+ super(queues, ACTION_EXPIRE_ALL);
+ else if (strcmp(optarg, "-") == 0)
+ operate_stream(VSTREAM_IN, expire_one, queues);
else
- message_requeued += requeue_one(queues, *cpp);
- }
- }
-
- /*
- * Put on hold queue files by name. This must not be done when queue file
- * names have changed names as a result of inode number mismatches,
- * because we could put on hold the wrong message.
- */
- if (action & ACTION_HOLD_ONE) {
- argv_terminate(hold_names);
- queues = (const char **)
- (argv[optind] ? argv + optind : default_hold_queues);
- for (cpp = hold_names->argv; *cpp; cpp++) {
- if (strcmp(*cpp, "ALL") == 0)
- continue;
- if (strcmp(*cpp, "-") == 0)
- message_held +=
- operate_stream(VSTREAM_IN, hold_one, queues);
+ expire_one(queues, optarg);
+ break;
+ case 'f':
+ if (strcmp(optarg, "ALL") == 0)
+ super(queues, ACTION_EXP_REL_ALL);
+ else if (strcmp(optarg, "-") == 0)
+ operate_stream(VSTREAM_IN, exp_rel_one, queues);
else
- message_held += hold_one(queues, *cpp);
- }
- }
-
- /*
- * Take "off hold" queue files by name. This must not be done when queue
- * file names have changed names as a result of inode number mismatches,
- * because we could take off hold the wrong message.
- */
- if (action & ACTION_RELEASE_ONE) {
- argv_terminate(release_names);
- queues = (const char **)
- (argv[optind] ? argv + optind : default_release_queues);
- for (cpp = release_names->argv; *cpp; cpp++) {
- if (strcmp(*cpp, "ALL") == 0)
- continue;
- if (strcmp(*cpp, "-") == 0)
- message_released +=
- operate_stream(VSTREAM_IN, release_one, queues);
+ exp_rel_one(queues, optarg);
+ break;
+ case 'h':
+ if (strcmp(optarg, "ALL") == 0)
+ super(queues, ACTION_HOLD_ALL);
+ else if (strcmp(optarg, "-") == 0)
+ operate_stream(VSTREAM_IN, hold_one, queues);
+ else
+ hold_one(queues, optarg);
+ break;
+ case 'H':
+ if (strcmp(optarg, "ALL") == 0)
+ super(queues, ACTION_RELEASE_ALL);
+ else if (strcmp(optarg, "-") == 0)
+ operate_stream(VSTREAM_IN, release_one, queues);
else
- message_released += release_one(queues, *cpp);
+ release_one(queues, optarg);
+ break;
+ case 'r':
+ if (strcmp(optarg, "ALL") == 0)
+ super(queues, ACTION_REQUEUE_ALL);
+ else if (strcmp(optarg, "-") == 0)
+ operate_stream(VSTREAM_IN, requeue_one, queues);
+ else
+ requeue_one(queues, optarg);
+ break;
}
}
/*
* Report.
*/
- if (message_requeued > 0)
+ if (action & (ACTION_REQUEUE_ONE | ACTION_REQUEUE_ALL))
msg_info("Requeued: %d message%s", message_requeued,
- message_requeued > 1 ? "s" : "");
- if (message_deleted > 0)
+ message_requeued != 1 ? "s" : "");
+ if (action & (ACTION_DELETE_ONE | ACTION_DELETE_ALL))
msg_info("Deleted: %d message%s", message_deleted,
- message_deleted > 1 ? "s" : "");
- if (message_held > 0)
+ message_deleted != 1 ? "s" : "");
+ if (action & (ACTION_EXPIRE_ONE | ACTION_EXPIRE_ALL
+ | ACTION_EXP_REL_ONE | ACTION_EXP_REL_ALL))
+ msg_info("Force-expired: %d message%s", message_expired,
+ message_expired != 1 ? "s" : "");
+ if (action & (ACTION_HOLD_ONE | ACTION_HOLD_ALL))
msg_info("Placed on hold: %d message%s",
- message_held, message_held > 1 ? "s" : "");
- if (message_released > 0)
+ message_held, message_held != 1 ? "s" : "");
+ if (action & (ACTION_RELEASE_ONE | ACTION_RELEASE_ALL
+ | ACTION_EXP_REL_ONE | ACTION_EXP_REL_ALL))
msg_info("Released from hold: %d message%s",
- message_released, message_released > 1 ? "s" : "");
+ message_released, message_released != 1 ? "s" : "");
if (inode_fixed > 0)
msg_info("Renamed to match inode number: %d message%s", inode_fixed,
- inode_fixed > 1 ? "s" : "");
+ inode_fixed != 1 ? "s" : "");
if (inode_mismatch > 0 || inode_fixed > 0)
msg_warn("QUEUE FILE NAMES WERE CHANGED TO MATCH INODE NUMBERS");
- /*
- * Clean up.
- */
- if (requeue_names)
- argv_free(requeue_names);
- if (delete_names)
- argv_free(delete_names);
- if (hold_names)
- argv_free(hold_names);
- if (release_names)
- argv_free(release_names);
-
exit(0);
}
#define QMGR_FLUSH_ONCE (1<<2) /* unthrottle once */
#define QMGR_FLUSH_DFXP (1<<3) /* override defer_transports */
#define QMGR_FLUSH_EACH (1<<4) /* unthrottle per message */
+#define QMGR_FORCE_EXPIRE (1<<5) /* force-defer and force-expire */
/*
* qmgr_scan.c
* a minimal amount of time.
*/
#define QMGR_FLUSH_AFTER (QMGR_FLUSH_EACH | QMGR_FLUSH_DFXP)
+#define MAYBE_FLUSH_AFTER(mode) \
+ (((mode) & MAIL_QUEUE_STAT_UNTHROTTLE) ? QMGR_FLUSH_AFTER : 0)
+#define MAYBE_FORCE_EXPIRE(mode) \
+ (((mode) & MAIL_QUEUE_STAT_EXPIRE) ? QMGR_FORCE_EXPIRE : 0)
+#define MAYBE_UPDATE_MODE(mode) \
+ (((mode) & MAIL_QUEUE_STAT_UNTHROTTLE) ? \
+ (mode) & ~MAIL_QUEUE_STAT_UNTHROTTLE : 0)
if ((message = qmgr_message_alloc(MAIL_QUEUE_ACTIVE, queue_id,
- (st.st_mode & MAIL_QUEUE_STAT_UNTHROTTLE) ?
- scan_info->flags | QMGR_FLUSH_AFTER :
- scan_info->flags,
- (st.st_mode & MAIL_QUEUE_STAT_UNTHROTTLE) ?
- st.st_mode & ~MAIL_QUEUE_STAT_UNTHROTTLE :
- 0)) == 0) {
+ scan_info->flags
+ | MAYBE_FLUSH_AFTER(st.st_mode)
+ | MAYBE_FORCE_EXPIRE(st.st_mode),
+ MAYBE_UPDATE_MODE(st.st_mode))) == 0) {
qmgr_active_corrupt(queue_id);
return (0);
} else if (message == QMGR_MESSAGE_LOCKED) {
static void qmgr_active_done_25_generic(QMGR_MESSAGE *message)
{
const char *myname = "qmgr_active_done_25_generic";
+ const char *expire_status = 0;
/*
* If we get to this point we have tried all recipients for this message.
* daemon waits for the qmgr to accept the "new mail" trigger.
*/
if (message->flags) {
- if (event_time() >= message->create_time +
- (*message->sender ? var_max_queue_time : var_dsn_queue_time)) {
- msg_info("%s: from=<%s>, status=expired, returned to sender",
- message->queue_id, info_log_addr_form_sender(message->sender));
+ if ((message->qflags & QMGR_FORCE_EXPIRE) != 0) {
+ expire_status = "force-expired";
+ } else if (event_time() >= message->create_time +
+ (*message->sender ? var_max_queue_time : var_dsn_queue_time)) {
+ expire_status = "expired";
+ } else {
+ expire_status = 0;
+ }
+ if (expire_status != 0) {
+ msg_info("%s: from=<%s>, status=%s, returned to sender",
+ message->queue_id, info_log_addr_form_sender(message->sender),
+ expire_status);
if (message->verp_delims == 0 || var_verp_bounce_off)
adefer_flush(BOUNCE_FLAG_KEEP,
message->queue_name,
"5.1.3 null recipient address");
}
+ /*
+ * Redirect a forced-to-expire message without defer log to the retry
+ * service, so that its defer log will contain an appropriate reason.
+ * Do not redirect such a message to the error service, because if
+ * that request fails, a defer log would be created with reason
+ * "bounce or trace service failure" which would make no sense. Note
+ * that if the bounce service fails to create a defer log, the
+ * message will be returned as undeliverable anyway, because it is
+ * expired.
+ */
+ if ((message->qflags & QMGR_FORCE_EXPIRE) != 0) {
+ QMGR_REDIRECT(&reply, MAIL_SERVICE_RETRY,
+ "4.7.0 message is administratively expired");
+ }
+
/*
* Discard mail to the local double bounce address here, so this
* system can run without a local delivery agent. They'd still have
{
const char *myname = "qmgr_message_alloc";
QMGR_MESSAGE *message;
+ struct stat st;
if (msg_verbose)
msg_info("%s: %s %s", myname, queue_name, queue_id);
if (mode != 0 && fchmod(vstream_fileno(message->fp), mode) < 0)
msg_fatal("fchmod %s: %m", VSTREAM_PATH(message->fp));
+ /*
+ * If this message is forced to expire, use the existing defer
+ * logfile records and do not assign any deliveries, leaving the
+ * refcount at zero. If this message is forced to expire, but no
+ * defer logfile records are available, assign deliveries to the
+ * retry transport so that the sender will still find out what
+ * recipients are affected and why. Either way, do not assign normal
+ * deliveries because that would be undesirable especially with mail
+ * that was expired in the 'hold' queue.
+ */
+ if ((message->qflags & QMGR_FORCE_EXPIRE) != 0
+ && stat(mail_queue_path((VSTRING *) 0, MAIL_QUEUE_DEFER,
+ queue_id), &st) == 0 && st.st_size > 0) {
+ /* Use this defer log; don't assign deliveries (refcount == 0). */
+ message->flags = 1; /* simplify downstream code */
+ qmgr_message_close(message);
+ return (message);
+ }
+
/*
* Reset the defer log. This code should not be here, but we must
* reset the defer log *after* acquiring the exclusive lock on the
/* showq_report - report status of sender and recipients */
static void showq_report(VSTREAM *client, char *queue, char *id,
- VSTREAM *qfile, long size, time_t mtime)
+ VSTREAM *qfile, long size, time_t mtime,
+ mode_t mode)
{
VSTRING *buf = vstring_alloc(100);
VSTRING *printable_quoted_addr = vstring_alloc(100);
SEND_ATTR_LONG(MAIL_ATTR_TIME, arrival_time > 0 ?
arrival_time : mtime),
SEND_ATTR_LONG(MAIL_ATTR_SIZE, msg_size),
+ SEND_ATTR_INT(MAIL_ATTR_FORCED_EXPIRE,
+ (mode & MAIL_QUEUE_STAT_EXPIRE) != 0),
SEND_ATTR_STR(MAIL_ATTR_SENDER,
STR(printable_quoted_addr)),
ATTR_TYPE_END);
if (status == MAIL_OPEN_YES) {
if ((qfile = mail_queue_open(qp->name, id, O_RDONLY, 0)) != 0) {
showq_report(client, qp->name, id, qfile, (long) st.st_size,
- st.st_mtime);
+ st.st_mtime, st.st_mode);
if (vstream_fclose(qfile))
msg_warn("close file %s %s: %m", qp->name, id);
} else if (errno != ENOENT) {