]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-3.5-20200202
authorWietse Venema <wietse@porcupine.org>
Sun, 2 Feb 2020 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <postfix-users@dukhovni.org>
Mon, 3 Feb 2020 14:48:19 +0000 (09:48 -0500)
22 files changed:
postfix/HISTORY
postfix/RELEASE_NOTES
postfix/html/postqueue.1.html
postfix/html/postsuper.1.html
postfix/man/man1/postqueue.1
postfix/man/man1/postsuper.1
postfix/mantools/postlink
postfix/src/global/mail_proto.h
postfix/src/global/mail_queue.h
postfix/src/global/mail_version.h
postfix/src/oqmgr/qmgr.h
postfix/src/oqmgr/qmgr_active.c
postfix/src/oqmgr/qmgr_message.c
postfix/src/postqueue/postqueue.c
postfix/src/postqueue/showq_compat.c
postfix/src/postqueue/showq_json.c
postfix/src/postsuper/Makefile.in
postfix/src/postsuper/postsuper.c
postfix/src/qmgr/qmgr.h
postfix/src/qmgr/qmgr_active.c
postfix/src/qmgr/qmgr_message.c
postfix/src/showq/showq.c

index ecc1701152c6422816cd096c7c4ef97ded4221ce..c9cdfc58dd0f9ad55fed710d72dc20a8d5cee37f 100644 (file)
@@ -24574,3 +24574,51 @@ Apologies for any names omitted.
        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.
index 1cfa3aad11b663acd9066d97e58caa974b569392..88889883b92d763733e7c9e6a4e2dd61f5520d42 100644 (file)
@@ -25,6 +25,28 @@ more recent Eclipse Public License 2.0. Recipients can choose to take
 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
 ===========================================
 
index d64e6d1ec7f3a4931fa7f85eeb7121877b00c31c..c4b3e1e1d117ab3d7c9b5eba70a35eec6f97c05d 100644 (file)
@@ -80,23 +80,28 @@ POSTQUEUE(1)                                                      POSTQUEUE(1)
                      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
@@ -104,28 +109,34 @@ POSTQUEUE(1)                                                      POSTQUEUE(1)
        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 &lt;CR&gt;&lt;LF&gt; 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>
index abd540818454214c5fc9a060ab7b3a8bafecc473..085b25b268fe94c041b966a61f53359fd418b9d3 100644 (file)
@@ -10,7 +10,9 @@ POSTSUPER(1)                                                      POSTSUPER(1)
        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>]
 
@@ -22,8 +24,9 @@ POSTSUPER(1)                                                      POSTSUPER(1)
 
        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:
 
@@ -33,12 +36,12 @@ POSTSUPER(1)                                                      POSTSUPER(1)
               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 = "" }
@@ -47,73 +50,107 @@ POSTSUPER(1)                                                      POSTSUPER(1)
                         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  &lt;=
-              2.8;  and  with  Postfix  &gt;= 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 &lt;=
+              2.8; and with Postfix  &gt;=  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
@@ -157,22 +194,23 @@ POSTSUPER(1)                                                      POSTSUPER(1)
               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 &lt;= 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 &lt;= 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  &lt;=  2.8.
                      The procedure is as follows:
 
                      # postfix stop
@@ -182,11 +220,12 @@ POSTSUPER(1)                                                      POSTSUPER(1)
                      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>
@@ -194,9 +233,11 @@ POSTSUPER(1)                                                      POSTSUPER(1)
        <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
@@ -207,25 +248,25 @@ POSTSUPER(1)                                                      POSTSUPER(1)
        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>
@@ -235,7 +276,7 @@ POSTSUPER(1)                                                      POSTSUPER(1)
               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:
index 1edc75b2de83ec574851b0e1fc4214f47a56f269..ed0d8139d2324d05cab8e87a3e9665a30b3ac706 100644 (file)
@@ -79,6 +79,11 @@ selected for delivery.
 .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
@@ -124,6 +129,11 @@ The number of bytes in the message header and body. This
 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
index e165edfdd056575f04a8eebc72f81c5f2c55b10a..83a39e40d406e9839c1876f65e1a0b7efe734deb 100644 (file)
@@ -10,7 +10,8 @@ Postfix superintendent
 .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
@@ -24,8 +25,9 @@ such as listing or flushing the mail queue.
 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:
@@ -78,6 +80,40 @@ can distinguish within a second).
 \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
@@ -120,6 +156,8 @@ This feature is available in Postfix 2.0 and later.
 .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
@@ -167,6 +205,8 @@ This feature is available in Postfix 1.1 and later.
 .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
@@ -201,6 +241,8 @@ A redundant version of \fB\-s\fR 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.
+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.
@@ -210,11 +252,14 @@ 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
index 4d5817d2a18dbfd86dc37aa9d08d28b0c3cec594..46f187e91cd31d2f2d9bb8c16191904ffd1c621c 100755 (executable)
@@ -842,7 +842,7 @@ while (<>) {
     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;
index 3428374e3f1ef976989fab53fdfdc63920092af2..a45466213c43a43004c2f37d836d1c14128f868a 100644 (file)
@@ -163,6 +163,7 @@ extern char *mail_pathname(const char *, const char *);
 #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"
index ae9dc17774220050f94b279e7a103e810f014b93..4928d60cc180c43967a842d1e9c55e3982ffd8af 100644 (file)
@@ -49,6 +49,7 @@
 #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 *);
index 7b011202ec38a1e074bd206dd6cf8bfc2dff1d42..5ee91ae0c453364b815cefe890ce2420f05e0aeb 100644 (file)
@@ -20,7 +20,7 @@
   * 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
index 15c941b5a145ac9f719158740c2dbe8d30f1928a..cc22ca51df41feedd33f2bfbfda8ab4eec582da4 100644 (file)
@@ -399,6 +399,7 @@ struct QMGR_SCAN {
 #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
index 460e92494b18434b07fbcb54d7120cf6c09d7f38..b1c1a4adcc677b645be3ad7354a64275b326acd1 100644 (file)
@@ -234,14 +234,19 @@ int     qmgr_active_feed(QMGR_SCAN *scan_info, const char *queue_id)
      * 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) {
@@ -433,6 +438,7 @@ static void qmgr_active_done_25_trace_flush(int status, void *context)
 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.
@@ -442,10 +448,18 @@ static void qmgr_active_done_25_generic(QMGR_MESSAGE *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,
index 126d34e366938439055574d2d854163448419f19..ea55615611b029e0381467a2750105b97da100c4 100644 (file)
@@ -1077,6 +1077,21 @@ static void qmgr_message_resolve(QMGR_MESSAGE *message)
                          "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
@@ -1349,6 +1364,7 @@ QMGR_MESSAGE *qmgr_message_alloc(const char *queue_name, const char *queue_id,
 {
     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);
@@ -1387,6 +1403,25 @@ QMGR_MESSAGE *qmgr_message_alloc(const char *queue_name, const char *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
index d838808f1cefab69212a737a54fbdb87a8abe3f6..7efc01ff975b07e2ad3732d3715864d75e8ae324 100644 (file)
 /* .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
index c1868e1a48a940a2c8d2f803b1a99daea1a55d57..2c287830605b5e1f544a93755d1a7a51d81ffa4d 100644 (file)
@@ -88,12 +88,12 @@ static unsigned long showq_message(VSTREAM *showq_stream)
     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.
@@ -114,8 +114,9 @@ static unsigned long showq_message(VSTREAM *showq_stream)
                  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");
 
     /*
@@ -123,9 +124,13 @@ static unsigned long showq_message(VSTREAM *showq_stream)
      * 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),
index 67fbada4a21cd5b3ad9e2b200e37047548c48d7f..e9d4fb5703c6c3a12235c583dc4d7b47f40c3412 100644 (file)
@@ -134,6 +134,7 @@ static void format_json(VSTREAM *showq_stream)
     long    message_size;
     int     showq_status;
     int     rcpt_count = 0;
+    int     forced_expire;
 
     /*
      * One-time initialization.
@@ -154,8 +155,9 @@ static void format_json(VSTREAM *showq_stream)
                  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\", ",
@@ -164,6 +166,7 @@ static void format_json(VSTREAM *showq_stream)
                   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)));
 
index 38ae3a92fcb8ad5c725afeec89c0f8df0c5614ee..30b540a6114a250c6f2cca4e53d76f5701e1925b 100644 (file)
@@ -74,7 +74,9 @@ postsuper.o: ../../include/msg.h
 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
index 957cf8f769fbf432a31bf2b16c3d3c562a308aed..196990ca9b3a34f95303961d0c38989592b0cb6d 100644 (file)
@@ -6,7 +6,8 @@
 /* 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
@@ -18,8 +19,9 @@
 /*     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
@@ -398,6 +458,7 @@ static int message_requeued = 0;    /* requeued messages */
 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 */
@@ -437,6 +498,47 @@ static int postremove(const char *path)
     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)
@@ -474,7 +576,7 @@ static int postrmdir(const char *path)
 
 /* 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;
@@ -489,7 +591,7 @@ static int delete_one(const char **queue_names, const char *queue_id)
      */
     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);
 
@@ -515,12 +617,12 @@ static int delete_one(const char **queue_names, const char *queue_id)
        }
     }
     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;
@@ -535,7 +637,7 @@ static int requeue_one(const char **queue_names, const char *queue_id)
      */
     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);
 
@@ -565,12 +667,12 @@ static int requeue_one(const char **queue_names, const char *queue_id)
        }
     }
     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;
@@ -584,7 +686,7 @@ static int hold_one(const char **queue_names, const char *queue_id)
      */
     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);
 
@@ -616,12 +718,12 @@ static int hold_one(const char **queue_names, const char *queue_id)
        }
     }
     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;
@@ -634,7 +736,7 @@ static int release_one(const char **queue_names, const char *queue_id)
      */
     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);
 
@@ -656,23 +758,68 @@ static int release_one(const char **queue_names, const char *queue_id)
        }
     }
     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 */
@@ -757,6 +904,15 @@ static void super(const char **queues, int action)
     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.
@@ -828,7 +984,7 @@ static void super(const char **queues, int action)
             * 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;
 
            /*
@@ -952,7 +1108,7 @@ static void super(const char **queues, int action)
                    || (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;
@@ -977,6 +1133,24 @@ static void super(const char **queues, int action)
                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
@@ -1003,7 +1177,7 @@ static void super(const char **queues, int action)
             * 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)
@@ -1084,12 +1258,8 @@ int     main(int argc, char **argv)
     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
@@ -1109,16 +1279,6 @@ int     main(int argc, char **argv)
        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.
@@ -1175,13 +1335,21 @@ int     main(int argc, char **argv)
 
     /*
      * 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)]"
@@ -1193,23 +1361,22 @@ int     main(int argc, char **argv)
                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;
@@ -1217,9 +1384,6 @@ int     main(int argc, char **argv)
            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;
@@ -1287,26 +1451,6 @@ int     main(int argc, char **argv)
        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.
@@ -1321,20 +1465,16 @@ int     main(int argc, char **argv)
        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
@@ -1343,7 +1483,7 @@ int     main(int argc, char **argv)
      * 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
@@ -1357,117 +1497,97 @@ int     main(int argc, char **argv)
     }
 
     /*
-     * 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);
 }
index 67d0cf52352aa5a0a6bca07e5d31cb185d41df95..4a205b48ee29e81d718b5fb53f7db8fbbedc6d2c 100644 (file)
@@ -508,6 +508,7 @@ struct QMGR_SCAN {
 #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
index 460e92494b18434b07fbcb54d7120cf6c09d7f38..b1c1a4adcc677b645be3ad7354a64275b326acd1 100644 (file)
@@ -234,14 +234,19 @@ int     qmgr_active_feed(QMGR_SCAN *scan_info, const char *queue_id)
      * 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) {
@@ -433,6 +438,7 @@ static void qmgr_active_done_25_trace_flush(int status, void *context)
 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.
@@ -442,10 +448,18 @@ static void qmgr_active_done_25_generic(QMGR_MESSAGE *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,
index 7d7464c289d2a95021d98f5b19a5f2adfab4206e..02a73911163e08c3c6372ac41cb1e3b0c12c13ce 100644 (file)
@@ -1136,6 +1136,21 @@ static void qmgr_message_resolve(QMGR_MESSAGE *message)
                          "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
@@ -1470,6 +1485,7 @@ QMGR_MESSAGE *qmgr_message_alloc(const char *queue_name, const char *queue_id,
 {
     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);
@@ -1508,6 +1524,25 @@ QMGR_MESSAGE *qmgr_message_alloc(const char *queue_name, const char *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
index 95d6abf84700157d1a3f1df580d8f091eb2d631e..e6ca68feb28a6d9733fcf2dd16d0f1043e91c5eb 100644 (file)
@@ -160,7 +160,8 @@ static void showq_reasons(VSTREAM *, BOUNCE_LOG *, RCPT_BUF *, DSN_BUF *,
 /* 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);
@@ -240,6 +241,8 @@ static void showq_report(VSTREAM *client, char *queue, char *id,
                       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);
@@ -385,7 +388,7 @@ static void showq_service(VSTREAM *client, char *unused_service, char **argv)
            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) {