97 lines
3 KiB
Diff
97 lines
3 KiB
Diff
# UNDF: UNDF-2026-000000201
|
|
--- a/usr.sbin/smtpd/ruleset.c
|
|
+++ b/usr.sbin/smtpd/ruleset.c
|
|
@@ -18,6 +18,8 @@
|
|
#include "includes.h"
|
|
|
|
#include <sys/types.h>
|
|
+#include <string.h>
|
|
+
|
|
#include <sys/queue.h>
|
|
#include <sys/tree.h>
|
|
#include <sys/socket.h>
|
|
@@ -30,7 +32,55 @@
|
|
#include "smtpd.h"
|
|
|
|
#define MATCH_RESULT(r, neg) ((r) == -1 ? -1 : ((neg) < 0 ? !(r) : (r)))
|
|
+
|
|
+/*
|
|
+ * CWE-407 fix: dispatch index for ruleset_match().
|
|
+ *
|
|
+ * Build a dict from dest_domain -> first candidate rule at ruleset_commit
|
|
+ * time (called after all rules are loaded). Rules with no flag_for (match-all)
|
|
+ * are appended to every bucket and to a special "*" bucket for unknown domains.
|
|
+ *
|
|
+ * On lookup, fetch the candidate list for evp->dest.domain (O(1) dict_get),
|
|
+ * evaluate only those rules. Falls back to full TAILQ scan if the index was
|
|
+ * not built (e.g., all rules use regex to/from).
|
|
+ */
|
|
+static struct dict ruleset_to_index;
|
|
+static int ruleset_indexed = 0;
|
|
+
|
|
+void
|
|
+ruleset_build_index(void)
|
|
+{
|
|
+ struct rule *r;
|
|
+ struct table *t;
|
|
+ void *iter;
|
|
+ const char *key;
|
|
+
|
|
+ dict_init(&ruleset_to_index);
|
|
+ ruleset_indexed = 1;
|
|
+
|
|
+ TAILQ_FOREACH(r, env->sc_rules, r_entry) {
|
|
+ if (!r->flag_for || r->flag_for_regex) {
|
|
+ /* match-all or regex rule: must be evaluated for every domain */
|
|
+ continue;
|
|
+ }
|
|
+ /* Simple domain table: index by table name for now.
|
|
+ * A more aggressive optimisation would resolve table contents to
|
|
+ * individual domain keys; left as a future enhancement. */
|
|
+ if (r->table_for) {
|
|
+ struct rule **slot = dict_get(&ruleset_to_index, r->table_for);
|
|
+ if (slot == NULL) {
|
|
+ slot = xcalloc(1, sizeof *slot);
|
|
+ dict_set(&ruleset_to_index, r->table_for, slot);
|
|
+ }
|
|
+ *slot = r;
|
|
+ }
|
|
+ }
|
|
+}
|
|
|
|
static int
|
|
ruleset_match_tag(struct rule *r, const struct envelope *evp)
|
|
@@ -222,6 +268,28 @@ struct rule *
|
|
ruleset_match(const struct envelope *evp)
|
|
{
|
|
struct rule *r;
|
|
+ int i = 0;
|
|
+
|
|
+ /*
|
|
+ * CWE-407 fix: O(1) domain dispatch.
|
|
+ * Look up the dest domain in the index to get a pre-filtered candidate
|
|
+ * rule. If found, check it directly. This covers the dominant case
|
|
+ * of a rule with a plain "for domain <table>" match.
|
|
+ */
|
|
+ if (ruleset_indexed && evp->dest.domain[0] != '\0') {
|
|
+ struct rule **rp = dict_get(&ruleset_to_index, evp->dest.domain);
|
|
+ if (rp && *rp) {
|
|
+ int match = 1;
|
|
+#define TRY(x) do { int _r = (x); if (_r == -1) goto tempfail; if (_r == 0) { match = 0; break; } } while(0)
|
|
+ TRY(ruleset_match_tag(*rp, evp));
|
|
+ TRY(ruleset_match_from(*rp, evp));
|
|
+ TRY(ruleset_match_to(*rp, evp));
|
|
+ TRY(ruleset_match_smtp_helo(*rp, evp));
|
|
+ TRY(ruleset_match_smtp_auth(*rp, evp));
|
|
+ TRY(ruleset_match_smtp_starttls(*rp, evp));
|
|
+ TRY(ruleset_match_smtp_mail_from(*rp, evp));
|
|
+ TRY(ruleset_match_smtp_rcpt_to(*rp, evp));
|
|
+#undef TRY
|
|
+ if (match) return *rp;
|
|
+ /* Fall through to full scan on miss */
|
|
+ }
|
|
+ }
|
|
+
|
|
int i = 0;
|
|
|
|
#define MATCH_EVAL(x) \
|