]> git.ipfire.org Git - thirdparty/postfix.git/commitdiff
postfix-3.12-20260720
authorWietse Z Venema <wietse@porcupine.org>
Mon, 20 Jul 2026 05:00:00 +0000 (00:00 -0500)
committerViktor Dukhovni <ietf-dane@dukhovni.org>
Wed, 22 Jul 2026 14:34:03 +0000 (00:34 +1000)
23 files changed:
postfix/HISTORY
postfix/html/posttls-finger.1.html
postfix/html/socketmap_table.5.html
postfix/man/man1/posttls-finger.1
postfix/man/man5/socketmap_table.5
postfix/proto/socketmap_table
postfix/proto/stop
postfix/proto/stop.double-cc
postfix/proto/stop.double-history
postfix/src/flush/flush.c
postfix/src/global/mail_queue.h
postfix/src/global/mail_version.h
postfix/src/global/nbdb_clnt.c
postfix/src/nbdb_reindexd/Makefile.in
postfix/src/nbdb_reindexd/nbdb_process_test.c
postfix/src/nbdb_reindexd/nbdb_safe.c
postfix/src/nbdb_reindexd/nbdb_safe.h
postfix/src/nbdb_reindexd/nbdb_safe_test.c
postfix/src/pipe/pipe.c
postfix/src/postsuper/postsuper.c
postfix/src/posttls-finger/posttls-finger.c
postfix/src/testing/mock_open_as.c
postfix/src/util/dict_dbm.c

index a044aa6060ee34990bdb18407df5bc7820868f27..0163698c017c5072e60b60c68c3b72595c988dcc 100644 (file)
@@ -31746,6 +31746,64 @@ Apologies for any names omitted.
        result. Files: postlink, socketmap_table, dict_sockmap.[hc],
        global/mail_params.[hc].
 
+20260716
+
+       Documentation for the new socketmap_max_query_size feature.
+       File: socketmap_table.
+
+       Bug (defect introduced: Postfix 1,1, date: 20010524): the
+       postsuper command under-read a very short queue filename. No
+       crash, information leak, or privilege escalation. Reported by
+       Qualys, assisted by Claude Mythos Preview. File: postsuper.c.
+
+20260717
+
+       Bug (defect introduced: Postfix 2.9, date: 20120114): a
+       lock file descriptor was not closed after failure to open a
+       DBM database. Reported by Qualys, assisted by Claude Mythos
+       Preview. File: dict_dbm.c.
+
+       Bug (defect introduced: Postfix-beta, date: 19990119): the
+       pipe(8) delivery agent deleted a command-line argument if
+       the argument contained $user AND $user expanded to an empty
+       string, breaking the positional order of arguments. This
+       was a workaround for a problem that hopefully no longer
+       exists. Reported by Qualys, assisted by Claude Mythos
+       Preview. File: pipe.c.
+
+20260720:
+
+       Bug (defect introduced: Postfix 1,1, date: 20010524): the
+       postsuper command over-read a very short queue filename.
+       No crash, information leak, or privilege escalation. Reported
+       by Qualys, assisted by Claude Mythos Preview. File:
+       mail_queue.h.
+
+       Code health: In the non-Berkeley DB migration client, connect
+       at least once. Qualys, assisted by Mythos Preview. File:
+       nbdb_clnt.c.
+
+       Bug (defect introduced: Postfix 1.0, date: 20000928): the
+       fast flush server, used by the SMTP command "ETRN", and by
+       the commands "postqueue -s site" and "postqueue -i queue_id"
+       (and their sendmail(1) equivalents), used the wrong duplicate
+       suppression API, resulting in too many queue scans by the
+       queue manager. Reported by Qualys, assisted by Claude Mythos
+       Preview. File: flush.c.
+
+       Fixed a mock vstream_fopen_as() error message. File:
+       mock_open_as.c.
+
+       Bug (introduced: Postfix 2.11, date: 20130422): the
+       posttls-finger test program falsely reported failure to
+       make a UNIX_domain connection. Reported by Qualys, assisted
+       by Claude Mythos Preview. File: posttls-finger.c.
+
+       Hardened the database parent directory permission checks
+       for automatic re-indexing with the non-Berkeley-DB migration
+       service. Files: nbdb_safe.c, nbdb_safe.h, nbdb_safe_test.c,
+       nbdb_process_test.c.
+
 TODO
 
        Reorganize PTEST_LIB, PMOCK_LIB, TESTLIB, TESTLIBS, etc.
index 5c27d704f11c219b115f65e3baeaa62615997221..c31c74b3974f9eed8fb9487577e476f8dc43a5e2 100644 (file)
@@ -343,13 +343,14 @@ POSTTLS-FINGER(1)                                            POSTTLS-FINGER(1)
               able via <b>native</b> lookups).
 
        <b>unix:</b><i>pathname</i>
-              Connect to the UNIX-domain socket at <i>pathname</i>. LMTP only.
+              Connect  to  the  UNIX-domain  socket  at <i>pathname</i>. This feature
+              requires "<b>-S</b>".
 
        <b>match ...</b>
               With no match arguments specified, certificate peername matching
               uses the compiled-in default strategies for each security level.
-              If  you specify one or more arguments, these will be used as the
-              list of certificate or public-key digests to match for the  <b>fin-</b>
+              If you specify one or more arguments, these will be used as  the
+              list  of certificate or public-key digests to match for the <b>fin-</b>
               <b>gerprint</b> level, or as the list of DNS names to match in the cer-
               tificate at the <b>verify</b> and <b>secure</b> levels.  If the security level
               is <b>dane</b>, or <b>dane-only</b> the match names are ignored, and <b>hostname,</b>
index b4e431971f6164406902d81f6b2acb0bdcf82bd1..50ca8c042863ab6c853892e0e6ce1a9aab1c039e 100644 (file)
@@ -83,8 +83,12 @@ SOCKETMAP_TABLE(5)                                          SOCKETMAP_TABLE(5)
        neither the connection nor the server are authenticated.
 
 <b><a name="configuration_parameters">CONFIGURATION PARAMETERS</a></b>
+       <b><a href="postconf.5.html#socketmap_max_query_size">socketmap_max_query_size</a> (10000)</b>
+              The maximum allowed query  size  for  a  socketmap  client,  not
+              including the socketmap and netstring encapsulation.
+
        <b><a href="postconf.5.html#socketmap_max_reply_size">socketmap_max_reply_size</a> (100000)</b>
-              The maximum allowed reply size  from  a  socketmap  server,  not
+              The  maximum  allowed  reply  size  from a socketmap server, not
               including the netstring encapsulation.
 
 <b><a name="see_also">SEE ALSO</a></b>
index 66cb9ea32596cc7ca5d9608ba63898895d1f1675..c7943a915bee3c3fc3009dce0ff0d1ff996c2a17 100644 (file)
@@ -309,7 +309,8 @@ fully\-qualified names or also enable \fBnative\fR host lookups
 (these don't support \fBdane\fR or \fBdane\-only\fR as no DNSSEC
 validation information is available via \fBnative\fR lookups).
 .IP "\fBunix:\fIpathname\fR"
-Connect to the UNIX\-domain socket at \fIpathname\fR. LMTP only.
+Connect to the UNIX\-domain socket at \fIpathname\fR. This feature
+requires "\fB\-S\fR".
 .IP "\fBmatch ...\fR"
 With no match arguments specified, certificate peername matching uses
 the compiled\-in default strategies for each security level.  If you
index 0e065b20047fb1097d626e254d332dbd1775772b..8279f4094978c2d7f4514fdee54923c2582e9003 100644 (file)
@@ -100,6 +100,9 @@ because neither the connection nor the server are authenticated.
 .nf
 .ad
 .fi
+.IP "\fBsocketmap_max_query_size (10000)\fR"
+The maximum allowed query size for a socketmap client, not
+including the socketmap and netstring encapsulation.
 .IP "\fBsocketmap_max_reply_size (100000)\fR"
 The maximum allowed reply size from a socketmap server, not
 including the netstring encapsulation.
index 3cbc187a2f1cc2152eeb2b9bc81a85f0db4e69bb..0edc4334fca78dd92e5ee119066a02eae5a47005 100644 (file)
@@ -82,6 +82,9 @@
 # CONFIGURATION PARAMETERS
 # .ad
 # .fi
+# .IP "\fBsocketmap_max_query_size (10000)\fR"
+#      The maximum allowed query size for a socketmap client, not
+#      including the socketmap and netstring encapsulation.
 # .IP "\fBsocketmap_max_reply_size (100000)\fR"
 #      The maximum allowed reply size from a socketmap server, not
 #      including the netstring encapsulation.
index 510fdbe0c2463d70f52dce382dc06f7abf44d73d..671e3d6d2134026e919dc5f76147e69ec6e1a938 100644 (file)
@@ -1732,3 +1732,4 @@ msgfree
 optimizers
 resloop
 tokenized
+fortra
index fb7a0e31686ad517b4c8eb4e96faed5bea2424ba..5096013be614e715e06259c6166f6760bb56086a 100644 (file)
@@ -365,3 +365,5 @@ application  application specific
 void  void defer_ctx 
  To undo a ptest_defer call call the function with a
 charset  charset 
+nbdb_safe_for_uid  nbdb_safe_for_uid owned by the user or root not group or other writable 
+ Qualys Mythos 202606 int unsigned unsigned is non transitive 
index 28e51888fa8c5137a1e6780934f956f8db9ed680..a42ef13ec04e3817628a0223d92fbe267cacfc29 100644 (file)
@@ -266,3 +266,4 @@ proto  proto stop proto stop double cc
  are not printable or whitespace Files smtpd smtpd c 
  support File smtpd smtpd c 
  sendmail sendmail c 
+ postqueue postqueue c 
index b8fae7788d9b008e905d68d6d250fef43944af77..62e8b9097dac030afa3012ad35d202a31481abba 100644 (file)
@@ -572,7 +572,7 @@ static int flush_send_path(const char *path, int how)
            continue;
        }
        if (dup_filter->used >= FLUSH_DUP_FILTER_SIZE
-           || htable_find(dup_filter, STR(queue_id)) == 0) {
+           || htable_locate(dup_filter, STR(queue_id)) == 0) {
            if (msg_verbose)
                msg_info("%s: logfile %s: update queue file %s time stamps",
                         myname, path, STR(queue_id));
index 1a6c66c207a9015f5bda20595ac92d20a2480876..c387afeac8243c540d5057f20c63480b326ae2b3 100644 (file)
@@ -110,7 +110,9 @@ extern int mail_queue_id_ok(const char *);
        if (((long_form) = MQID_FIND_LG_INUM_SEPARATOR(_cp, (path))) != 0) { \
            MQID_LG_DECODE_INUM(_cp + 1, (inum), (error)); \
        } else { \
-           MQID_SH_DECODE_INUM((path) + MQID_SH_USEC_PAD, (inum), (error)); \
+           if (strlen(path) > MQID_SH_USEC_PAD) \
+             MQID_SH_DECODE_INUM((path) + MQID_SH_USEC_PAD, (inum), (error)); \
+           else { (errno) = EINVAL; error = 1; } \
        } \
     } while (0)
 
index e9db761b625caaa8295fe2212901b99b34c36cd5..bd20a5f2eff80acb1592424bd9f244c1ee5f020a 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      "20260716"
+#define MAIL_RELEASE_DATE      "20260720"
 #define MAIL_VERSION_NUMBER    "3.12"
 
 #ifdef SNAPSHOT
index fbbd145c62c4e363fb59ffbd6da033b81749ac88..67b0d38dffb174575374c3f750688e883ba4a4c0 100644 (file)
@@ -139,6 +139,10 @@ int     nbdb_clnt_request(const char *type, const char *path,
     int     status;
     int     count;
 
+    /* 202607 Qualys+Mythos: try at least once. */
+    if (max_try < 1)
+       msg_panic("%s: bad max_try: %d", __func__, max_try);
+
     /*
      * A custom client endpoint that tries only a few times and that may be
      * called by:
index 0b2334062631a9fef7e759e11c241a5c6e9f35cd..b949e55f9a30d206c47591e12aa335050827b2a7 100644 (file)
@@ -50,7 +50,7 @@ nbdb_sniffer_test: nbdb_sniffer_test.o $(SNIFFER_TEST_OBJ) $(LIBS)
 test_nbdb_sniffer: nbdb_sniffer_test
        $(SHLIB_ENV) $(VALGRIND) ./nbdb_sniffer_test
 
-SAFE_TEST_OBJ = nbdb_safe.o
+SAFE_TEST_OBJ = nbdb_safe.o $(TESTLIB)
 
 nbdb_safe_test: nbdb_safe_test.o $(SAFE_TEST_OBJ) $(LIBS)
        $(CC) $(CFLAGS) -o $@ $@.o $(SAFE_TEST_OBJ) $(LIBS) $(SYSLIBS)
@@ -206,6 +206,8 @@ nbdb_safe.o: ../../include/argv.h
 nbdb_safe.o: ../../include/check_arg.h
 nbdb_safe.o: ../../include/mail_conf.h
 nbdb_safe.o: ../../include/mail_params.h
+nbdb_safe.o: ../../include/msg.h
+nbdb_safe.o: ../../include/mymalloc.h
 nbdb_safe.o: ../../include/sys_defs.h
 nbdb_safe.o: ../../include/vbuf.h
 nbdb_safe.o: ../../include/vstring.h
@@ -218,6 +220,7 @@ nbdb_safe_test.o: ../../include/argv.h
 nbdb_safe_test.o: ../../include/check_arg.h
 nbdb_safe_test.o: ../../include/mail_conf.h
 nbdb_safe_test.o: ../../include/mail_params.h
+nbdb_safe_test.o: ../../include/mock_stat.h
 nbdb_safe_test.o: ../../include/msg.h
 nbdb_safe_test.o: ../../include/msg_vstream.h
 nbdb_safe_test.o: ../../include/stringops.h
index 1aa953a1e3be72574a1e991ac191362f5d6644f5..6659d2568f37115e9ed033d8edf2de2b192ce1b9 100644 (file)
@@ -102,6 +102,8 @@ typedef struct TEST_CASE {
     MOCK_STAT_REQ mock_stat_source;
     MOCK_STAT_REQ mock_stat_leg_idx;
     MOCK_STAT_REQ mock_stat_parent;
+    MOCK_STAT_REQ mock_stat_slash_path;        /* "/path" */
+    MOCK_STAT_REQ mock_stat_slash;     /* "/" */
     MOCK_STAT_REQ mock_stat_new_idx;
     MOCK_OPEN_AS_REQ mock_open;
     MOCK_SPAWN_CMD_REQ mock_spawn;
@@ -143,6 +145,7 @@ static int test_nbdb_process(const TEST_CASE *tp)
     nbdb_util_init(tp->params.migr_level);
     setup_mock_cdb("{{x = x}}");
     setup_mock_lmdb("{{x = x}}");
+    teardown_mock_stat();
     if (nbdb_level >= NBDB_LEV_CODE_REINDEX) {
        var_nbdb_allow_root_pfxs = (char *) tp->params.allow_root_pfxs;
        var_nbdb_allow_user_pfxs = (char *) tp->params.allow_user_pfxs;
@@ -154,6 +157,8 @@ static int test_nbdb_process(const TEST_CASE *tp)
        setup_mock_stat(&tp->mock_stat_leg_idx);
        setup_mock_stat(&tp->mock_stat_parent);
        setup_mock_stat(&tp->mock_stat_new_idx);
+       setup_mock_stat(&tp->mock_stat_slash_path);
+       setup_mock_stat(&tp->mock_stat_slash);
        setup_mock_vstream_fopen_as(&tp->mock_open);
        setup_mock_spawn_command(&tp->mock_spawn);
     }
@@ -222,6 +227,16 @@ static const TEST_CASE test_cases[] = {
            .out_errno = 0,
            .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,},
        },
+       .mock_stat_slash_path = {
+           .want_path = "/path",
+           .out_errno = 0,
+           .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,},
+       },
+       .mock_stat_slash = {
+           .want_path = "/",
+           .out_errno = 0,
+           .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,},
+       },
        .mock_stat_new_idx = {
            .want_path = "/path/to/file.cdb",
            .out_errno = ENOENT,
@@ -286,6 +301,16 @@ static const TEST_CASE test_cases[] = {
            .out_errno = 0,
            .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,},
        },
+       .mock_stat_slash_path = {
+           .want_path = "/path",
+           .out_errno = 0,
+           .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,},
+       },
+       .mock_stat_slash = {
+           .want_path = "/",
+           .out_errno = 0,
+           .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,},
+       },
        .mock_stat_new_idx = {
            .want_path = "/path/to/file.cdb",
            .out_errno = ENOENT,
@@ -338,6 +363,16 @@ static const TEST_CASE test_cases[] = {
            .out_errno = 0,
            .out_st = {.st_mode = S_IWGRP,.st_uid = 0,.st_gid = 0,},
        },
+       .mock_stat_slash_path = {
+           .want_path = "/path",
+           .out_errno = 0,
+           .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,},
+       },
+       .mock_stat_slash = {
+           .want_path = "/",
+           .out_errno = 0,
+           .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,},
+       },
        .mock_stat_new_idx = {
            .want_path = "/path/to/file.cdb",
            .out_errno = ENOENT,
@@ -379,6 +414,16 @@ static const TEST_CASE test_cases[] = {
            .out_errno = 0,
            .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,},
        },
+       .mock_stat_slash_path = {
+           .want_path = "/path",
+           .out_errno = 0,
+           .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,},
+       },
+       .mock_stat_slash = {
+           .want_path = "/",
+           .out_errno = 0,
+           .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,},
+       },
        .mock_stat_new_idx = {
            .want_path = "/path/to/file.cdb",
            .out_errno = ENOENT,
@@ -430,6 +475,16 @@ static const TEST_CASE test_cases[] = {
                .st_mode = S_IRWXU,.st_uid = 1,.st_gid = 0,
            },
        },
+       .mock_stat_slash_path = {
+           .want_path = "/path",
+           .out_errno = 0,
+           .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,},
+       },
+       .mock_stat_slash = {
+           .want_path = "/",
+           .out_errno = 0,
+           .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,},
+       },
        .mock_stat_new_idx = {
            .want_path = "/path/to/file.cdb",
            .out_errno = ENOENT,
@@ -471,6 +526,16 @@ static const TEST_CASE test_cases[] = {
            .out_errno = 0,
            .out_st = {.st_mode = S_IRWXU,.st_uid = 1,.st_gid = 0,},
        },
+       .mock_stat_slash_path = {
+           .want_path = "/path",
+           .out_errno = 0,
+           .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,},
+       },
+       .mock_stat_slash = {
+           .want_path = "/",
+           .out_errno = 0,
+           .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,},
+       },
        .mock_stat_new_idx = {
            .want_path = "/path/to/file.cdb",
            .out_errno = ENOENT,
@@ -524,13 +589,23 @@ static const TEST_CASE test_cases[] = {
            .out_errno = 0,
            .out_st = {.st_mode = S_IRWXU,.st_uid = 1,.st_gid = 0,},
        },
+       .mock_stat_slash_path = {
+           .want_path = "/path",
+           .out_errno = 0,
+           .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,},
+       },
+       .mock_stat_slash = {
+           .want_path = "/",
+           .out_errno = 0,
+           .out_st = {.st_mode = S_IRWXU,.st_uid = 0,.st_gid = 0,},
+       },
        .mock_stat_new_idx = {
            .want_path = "/path/to/file.cdb",
            .out_errno = ENOENT,
        },
        .mock_open = {
            .want_path = "/path/to/file",
-           .want_uid = 0,
+           .want_uid = 1,
            .want_gid = 0,
            .out_errno = 0,
            .out_data = "foo bar\nfoo :bar",
index cf73e17429edec9070fbcdb366afbcf9c5b527f5..0eb065ac09753e69e8f30d970e08271b3ff62986 100644 (file)
 /*     uid_t   uid,
 /*     const struct stat *st)
 /*
+/*     bool    nbdb_safe_parent_for_uid(
+/*     uid_t   uid,
+/*     const char *parent_dir,
+/*     const struct stat *parent_dir_st)
+/*
 /*     bool    nbdb_safe_to_index_as_legacy_index_owner(
 /*     const char *source_path,
 /*     const struct stat *source_st,
 /*     the safety of other pathname components. It is a good idea to
 /*     trust only a limited number of pathname prefixes.
 /*
+/*     nbdb_safe_parent_for_uid() implements nbdb_safe_for_uid()
+/*     for the directories in the specified pathname. When a pathname
+/*     element contains a symlink, this function will examine the
+/*     symlink target but not the target's parent directories.
+/*
 /*     nbdb_safe_to_index_as_legacy_index_owner() determines
 /*     if a Berkeley DB source file and parent directory are 'safe'
 /*     for the uid of the Berkeley DB indexed file.
@@ -55,6 +65,8 @@
   */
 #include <sys_defs.h>
 #include <sys/stat.h>
+#include <string.h>
+#include <libgen.h>
 
  /*
   * Utility library.
@@ -67,6 +79,9 @@
 #include <allowed_prefix.h>
 #include <mail_conf.h>
 #include <mail_params.h>
+#include <msg.h>
+#include <mymalloc.h>
+#include <wrap_stat.h>
 
  /*
   * Application-specific.
 #include <nbdb_safe.h>
 #include <nbdb_reindexd.h>
 
+/* nbdb_safe_parent_for_uid - simple pathname safety */
+
+bool    nbdb_safe_parent_for_uid(uid_t uid, const char *path,
+                                      const struct stat * path_st)
+{
+    bool    ret;
+
+    /*
+     * See also the comment in nbdb_safe_for_uid().
+     */
+    if (!nbdb_safe_for_uid(uid, path_st)) {
+       ret = false;
+    } else if (strcmp(path, "/") == 0) {
+       ret = true;
+    } else {
+       char   *saved_path = mystrdup(path);
+       char   *parent = dirname(saved_path);
+       struct stat parent_st;
+
+       if (stat(parent, &parent_st) < 0) {
+           ret = false;
+           msg_warn("look up status for directory '%s': %m", parent);
+       } else {
+           ret = nbdb_safe_parent_for_uid(uid, parent, &parent_st);
+       }
+       myfree(saved_path);
+    }
+    return (ret);
+}
+
 /* nbdb_safe_for_uid - owned by the user or root, not group or other writable */
 
 bool    nbdb_safe_for_uid(uid_t uid, const struct stat * st)
@@ -147,7 +192,7 @@ bool    nbdb_safe_to_index_as_legacy_index_owner(
                        leg_idx_path, (int) runas_uid, source_path);
        return (false);
     }
-    if (!nbdb_safe_for_uid(runas_uid, parent_dir_st)) {
+    if (!nbdb_safe_parent_for_uid(runas_uid, parent_dir, parent_dir_st)) {
        vstring_sprintf(why, "legacy indexed file '%s' is owned by "
                        "uid '%d', but parent directory '%s' is "
                        "owned or writable by other user; to allow "
index 754d3e74db73cce61c2dfd0f288c0b196fd3f259..3ef55c59e6b1136433921ed21f0012e882aac4f7 100644 (file)
@@ -25,6 +25,7 @@
   * Internal API.
   */
 extern bool nbdb_safe_for_uid(uid_t, const struct stat *);
+extern bool nbdb_safe_parent_for_uid(uid_t, const char *, const struct stat *);
 extern bool nbdb_safe_to_index_as_legacy_index_owner(
                                          const char *, const struct stat *,
                                          const char *, const struct stat *,
index 91b7801119ba2930a253e921d53608f36577bcd3..10e81a146d02204c7b607a1fdfd72956435117ff 100644 (file)
@@ -21,6 +21,7 @@
   * System library.
   */
 #include <sys_defs.h>
+#include <sys/stat.h>
 #include <errno.h>
 #include <stdlib.h>
 #include <string.h>
 #include <mail_conf.h>
 #include <mail_params.h>
 
+ /*
+  * Test library.
+  */
+#include <mock_stat.h>
+
  /*
   * Application-specific.
   */
@@ -72,8 +78,11 @@ typedef struct TEST_CASE {
     struct stat source_st;
     const char *leg_idx_path;
     struct stat leg_idx_st;
-    const char *parent_dir;
+    const char *parent_dir;    /* "/etc/postfix" */
     struct stat parent_dir_st;
+    MOCK_STAT_REQ slash_etc_dir;       /* "/etc" */
+    MOCK_STAT_REQ slash_dir;   /* "/" */
+    struct stat slash_dir_st;
     const char *allow_root_pfxs;
     const char *allow_user_pfxs;
     const char *want_why;
@@ -118,6 +127,9 @@ static int test_nbdb_safe_to_index_as_legacy_index_owner(const TEST_CASE *tp)
     var_nbdb_allow_user_pfxs = (char *) tp->allow_user_pfxs;
     parsed_allow_root_pfxs = allowed_prefix_create(var_nbdb_allow_root_pfxs);
     parsed_allow_user_pfxs = allowed_prefix_create(var_nbdb_allow_user_pfxs);
+    teardown_mock_stat();
+    setup_mock_stat(&tp->slash_etc_dir);
+    setup_mock_stat(&tp->slash_dir);
     got_bool = nbdb_safe_to_index_as_legacy_index_owner(
                                            tp->source_path, &tp->source_st,
                                          tp->leg_idx_path, &tp->leg_idx_st,
@@ -231,6 +243,8 @@ static const TEST_CASE test_cases[] = {
        .leg_idx_st = {.st_uid = 0,},
        .parent_dir = "/etc/postfix",
        .parent_dir_st = {.st_uid = 0,.st_mode = S_IRWXU,},
+       .slash_etc_dir = {"/etc", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,},
+       .slash_dir = {"/", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,},
        .allow_root_pfxs = "/etc/postfix",
        .allow_user_pfxs = "",
        .want_bool = true,
@@ -244,6 +258,8 @@ static const TEST_CASE test_cases[] = {
        .leg_idx_st = {.st_uid = 0,},
        .parent_dir = "/etc/postfix",
        .parent_dir_st = {.st_uid = 0,.st_mode = S_IRWXU,},
+       .slash_etc_dir = {"/etc", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,},
+       .slash_dir = {"/", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,},
        .allow_root_pfxs = "/etc/posttfix",
        .allow_user_pfxs = "",
        .want_bool = false,
@@ -257,6 +273,8 @@ static const TEST_CASE test_cases[] = {
        .leg_idx_st = {.st_uid = 0,},
        .parent_dir = "/etc/postfix",
        .parent_dir_st = {.st_uid = 0,.st_mode = S_IRWXU,},
+       .slash_etc_dir = {"/etc", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,},
+       .slash_dir = {"/", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,},
        .allow_root_pfxs = "/etc/postfix",
        .allow_user_pfxs = "",
        .want_bool = false,
@@ -270,6 +288,8 @@ static const TEST_CASE test_cases[] = {
        .leg_idx_st = {.st_uid = 0,},
        .parent_dir = "/etc/postfix",
        .parent_dir_st = {.st_uid = 0,.st_mode = S_IRWXU,},
+       .slash_etc_dir = {"/etc", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,},
+       .slash_dir = {"/", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,},
        .allow_root_pfxs = "/etc/postfix",
        .allow_user_pfxs = "",
        .want_bool = false,
@@ -283,11 +303,43 @@ static const TEST_CASE test_cases[] = {
        .leg_idx_st = {.st_uid = 0,},
        .parent_dir = "/etc/postfix",
        .parent_dir_st = {.st_uid = 0,.st_mode = S_IRWXU,},
+       .slash_etc_dir = {"/etc", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,},
+       .slash_dir = {"/", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,},
        .allow_root_pfxs = "/etc/postfix",
        .allow_user_pfxs = "",
        .want_bool = false,
        .want_why = "'/etc/postfix/access' is owned or writable by other user",
     },
+    {.label = "safe_to_index_as_root:bad_root_owner",
+       .action = test_nbdb_safe_to_index_as_legacy_index_owner,
+       .source_path = "/etc/postfix/access",
+       .source_st = {.st_uid = 0,.st_mode = S_IRWXU,},
+       .leg_idx_path = "/etc/postfix/access.db",
+       .leg_idx_st = {.st_uid = 0,},
+       .parent_dir = "/etc/postfix",
+       .parent_dir_st = {.st_uid = 0,.st_mode = S_IRWXU,},
+       .slash_etc_dir = {"/etc", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,},
+       .slash_dir = {"/", {.st_uid = 1,.st_mode = S_IRWXU,}, 0,},
+       .allow_root_pfxs = "/etc/postfix",
+       .allow_user_pfxs = "",
+       .want_bool = false,
+       .want_why = "",
+    },
+    {.label = "safe_to_index_as_root:bad_root_perms",
+       .action = test_nbdb_safe_to_index_as_legacy_index_owner,
+       .source_path = "/etc/postfix/access",
+       .source_st = {.st_uid = 0,.st_mode = S_IRWXU,},
+       .leg_idx_path = "/etc/postfix/access.db",
+       .leg_idx_st = {.st_uid = 0,},
+       .parent_dir = "/etc/postfix",
+       .parent_dir_st = {.st_uid = 0,.st_mode = S_IRWXU,},
+       .slash_etc_dir = {"/etc", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,},
+       .slash_dir = {"/", {.st_uid = 0,.st_mode = S_IWOTH,}, 0,},
+       .allow_root_pfxs = "/etc/postfix",
+       .allow_user_pfxs = "",
+       .want_bool = false,
+       .want_why = "",
+    },
     {.label = "safe_to_index_as_root:bad_parent_owner",
        .action = test_nbdb_safe_to_index_as_legacy_index_owner,
        .source_path = "/etc/postfix/access",
@@ -296,6 +348,8 @@ static const TEST_CASE test_cases[] = {
        .leg_idx_st = {.st_uid = 0,},
        .parent_dir = "/etc/postfix",
        .parent_dir_st = {.st_uid = 1,.st_mode = S_IRWXU,},
+       .slash_etc_dir = {"/etc", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,},
+       .slash_dir = {"/", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,},
        .allow_root_pfxs = "/etc/postfix",
        .allow_user_pfxs = "",
        .want_bool = false,
@@ -309,6 +363,8 @@ static const TEST_CASE test_cases[] = {
        .leg_idx_st = {.st_uid = 0,},
        .parent_dir = "/etc/postfix",
        .parent_dir_st = {.st_uid = 0,.st_mode = S_IWGRP,},
+       .slash_etc_dir = {"/etc", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,},
+       .slash_dir = {"/", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,},
        .allow_root_pfxs = "/etc/postfix",
        .allow_user_pfxs = "",
        .want_bool = false,
@@ -322,6 +378,8 @@ static const TEST_CASE test_cases[] = {
        .leg_idx_st = {.st_uid = 0,},
        .parent_dir = "/etc/postfix",
        .parent_dir_st = {.st_uid = 0,.st_mode = S_IWOTH,},
+       .slash_etc_dir = {"/etc", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,},
+       .slash_dir = {"/", {.st_uid = 0,.st_mode = S_IRWXU,}, 0,},
        .allow_root_pfxs = "/etc/postfix",
        .allow_user_pfxs = "",
        .want_bool = false,
index a5ff2204d68f0a16693e0de9fd85280212adb9b5..80e0f04b74fd085eb23cfe5ae96713d87b936051 100644 (file)
@@ -765,12 +765,6 @@ static ARGV *expand_argv(const char *service, char **argv,
                 * Either anything to the left of the extension delimiter or,
                 * in absence of the latter, anything to the left of the
                 * rightmost @.
-                * 
-                * Beware: if the user name is blank (e.g. +user@host), the
-                * argument is suppressed. This is necessary to allow for
-                * cyrus bulletin-board (global mailbox) delivery. XXX But,
-                * skipping empty user parts will also prevent other
-                * expansions of this specific command-line argument.
                 */
                if (state.expand_flag & PIPE_FLAG_USER) {
                    morph_recipient(buf, rcpt_list->info[i].address,
@@ -780,8 +774,7 @@ static ARGV *expand_argv(const char *service, char **argv,
                                 rcpt_list->info[i].address);
                    if (*var_rcpt_delim)
                        split_addr(STR(buf), var_rcpt_delim);
-                   if (*STR(buf) == 0)
-                       continue;
+                   /* 202607 Qualys+Mythos: don't skip arg if $user is "".*/
                    dict_update(PIPE_DICT_TABLE, PIPE_DICT_USER, STR(buf));
                }
 
index c9b8e38c67710a0db37df6e598db284671bd2280..2d2b5499ea5ce11db7334b9203d45271fbe11a4a 100644 (file)
@@ -1053,6 +1053,8 @@ static void super(const char **queues, int action)
             * number.
             */
            if ((action & ACTION_STRUCT)
+           /* 202607 Qualys+Mythos: skip short paths. */
+               && strlen(path) > SUFFIX_LEN
                && strcmp(path + (strlen(path) - SUFFIX_LEN), SUFFIX) == 0) {
                path[strlen(path) - SUFFIX_LEN] = 0;    /* XXX */
                if (!mail_queue_id_ok(path)) {
index a3b1d372fbee860dda02c76db6858b7f74e4a2ae..f82b2b0a00ee18040b2a11230d8e6f5427e5f643 100644 (file)
 /*     (these don't support \fBdane\fR or \fBdane-only\fR as no DNSSEC
 /*     validation information is available via \fBnative\fR lookups).
 /* .IP "\fBunix:\fIpathname\fR"
-/*     Connect to the UNIX-domain socket at \fIpathname\fR. LMTP only.
+/*     Connect to the UNIX-domain socket at \fIpathname\fR. This feature
+/*     requires "\fB-S\fR".
 /* .IP "\fBmatch ...\fR"
 /*     With no match arguments specified, certificate peername matching uses
 /*     the compiled-in default strategies for each security level.  If you
@@ -1715,11 +1716,15 @@ static int connect_dest(STATE *state)
      */
     if (state->smtp == 0) {
        if (strncmp(dest, "unix:", 5) == 0) {
+           /* 202607 Qualys+Mythos: handle unix-domain success. */
            state->stream = connect_unix(state, dest + 5);
-           if (!state->stream)
+           if (state->stream) {
+               return (0);
+           } else {
                msg_info("Failed to establish session to %s: %s",
                         dest, vstring_str(state->why->reason));
-           return (1);
+               return (1);
+           }
        }
        if (strncmp(dest, "inet:", 5) == 0)
            dest += 5;
index 3a6a70a854f3e09be7578483bf48c599686510f5..eb20406067bce271f2b988a21a3ecefb8e967843 100644 (file)
@@ -81,13 +81,13 @@ VSTREAM *vstream_fopen_as(const char *path, int flags, mode_t mode,
 
     /* TODO(wietse) replace with match_str() and match_decimal(). */
     if (strcmp(path, mock_req.want_path) != 0)
-       msg_fatal("vstream_fopen: got path '%s', want '%s'",
+       msg_fatal("vstream_fopen_as: got path '%s', want '%s'",
                  path, mock_req.want_path);
     if (uid != mock_req.want_uid)
-       msg_fatal("vstream_fopen: got uid '%d', want '%d'",
+       msg_fatal("vstream_fopen_as: got uid '%d', want '%d'",
                  (int) uid, (int) mock_req.want_uid);
     if (gid != mock_req.want_gid)
-       msg_fatal("vstream_fopen: got gid '%d', want '%d'",
+       msg_fatal("vstream_fopen_as: got gid '%d', want '%d'",
                  (int) gid, (int) mock_req.want_gid);
 
     if (mock_req.out_errno != 0) {
index 9a8b8a53e149af7e952f6a73b264ee55c27efa8d..942996d574195c30079ec423e68285112bdc4a44 100644 (file)
@@ -415,7 +415,7 @@ DICT   *dict_dbm_open(const char *path, int open_flags, int dict_flags)
     struct stat st;
     DBM    *dbm;
     char   *dbm_path = 0;
-    int     lock_fd;
+    int     lock_fd = -1;
 
     /*
      * Let the optimizer worry about eliminating redundant code.
@@ -424,6 +424,8 @@ DICT   *dict_dbm_open(const char *path, int open_flags, int dict_flags)
        DICT *__d = (d); \
        if (dbm_path != 0) \
            myfree(dbm_path); \
+       if (lock_fd >= 0) \
+           (void) close(lock_fd); \
        return (__d); \
     } while (0)
 
@@ -459,6 +461,7 @@ DICT   *dict_dbm_open(const char *path, int open_flags, int dict_flags)
            msg_fatal("unlock database %s for open: %m", dbm_path);
        if (close(lock_fd) < 0)
            msg_fatal("close database %s: %m", dbm_path);
+       lock_fd = -1;
     }
     dict_dbm = (DICT_DBM *) dict_alloc(DICT_TYPE_DBM, path, sizeof(*dict_dbm));
     dict_dbm->dict.lookup = dict_dbm_lookup;