aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGilles Chehade <gilles@poolp.org>2013-09-12 18:20:06 +0200
committerGilles Chehade <gilles@poolp.org>2013-09-12 18:20:06 +0200
commitaec4d5fa58b4d713b4c0500e2923d494be475908 (patch)
tree91e34c65dfbf010e96c262a9e2f36f9ebb572265
parenta couple bzero() added just to be safe (diff)
downloadOpenSMTPD-aec4d5fa58b4d713b4c0500e2923d494be475908.tar.xz
OpenSMTPD-aec4d5fa58b4d713b4c0500e2923d494be475908.zip
negative rules + doc
-rw-r--r--smtpd/parse.y49
-rw-r--r--smtpd/ruleset.c31
-rw-r--r--smtpd/smtpd.conf.590
-rw-r--r--smtpd/smtpd.h6
-rw-r--r--smtpd/to.c17
5 files changed, 142 insertions, 51 deletions
diff --git a/smtpd/parse.y b/smtpd/parse.y
index 5bd2c1c9..ba16f323 100644
--- a/smtpd/parse.y
+++ b/smtpd/parse.y
@@ -134,7 +134,7 @@ typedef struct {
%token <v.string> STRING
%token <v.number> NUMBER
%type <v.table> table
-%type <v.number> port auth ssl size expire address_family mask_source
+%type <v.number> port auth ssl size expire address_family mask_source negation
%type <v.table> tables tablenew tableref destination alias virtual usermapping userbase from sender
%type <v.string> pkiname tag tagged listen_helo
%%
@@ -277,13 +277,14 @@ tag : TAG STRING {
| /* empty */ { $$ = NULL; }
;
-tagged : TAGGED STRING {
- if (($$ = strdup($2)) == NULL) {
+tagged : TAGGED negation STRING {
+ if (($$ = strdup($3)) == NULL) {
yyerror("strdup");
- free($2);
+ free($3);
YYERROR;
}
- free($2);
+ free($3);
+ rule->r_nottag = $2;
}
| /* empty */ { $$ = NULL; }
;
@@ -858,19 +859,25 @@ userbase : USERBASE tables {
-destination : DOMAIN tables {
- struct table *t = $2;
+destination : negation DOMAIN tables {
+ struct table *t = $3;
if (! table_check_use(t, T_DYNAMIC|T_LIST, K_DOMAIN)) {
yyerror("invalid use of table \"%s\" as DOMAIN parameter",
t->t_name);
YYERROR;
}
-
+ rule->r_notdestination = $1;
$$ = t;
}
- | LOCAL { $$ = table_find("<localnames>", NULL); }
- | ANY { $$ = 0; }
+ | negation LOCAL {
+ rule->r_notdestination = $1;
+ $$ = table_find("<localnames>", NULL);
+ }
+ | negation ANY {
+ rule->r_notdestination = $1;
+ $$ = 0;
+ }
;
@@ -942,37 +949,43 @@ action : userbase DELIVER TO MAILDIR {
}
;
-from : FROM tables {
- struct table *t = $2;
+negation : '!' { $$ = 1; }
+ | /* empty */ { $$ = 0; }
+ ;
+
+from : FROM negation tables {
+ struct table *t = $3;
if (! table_check_use(t, T_DYNAMIC|T_LIST, K_NETADDR)) {
yyerror("invalid use of table \"%s\" as FROM parameter",
t->t_name);
YYERROR;
}
-
+ rule->r_notsources = $2;
$$ = t;
}
- | FROM ANY {
+ | FROM negation ANY {
$$ = table_find("<anyhost>", NULL);
+ rule->r_notsources = $2;
}
- | FROM LOCAL {
+ | FROM negation LOCAL {
$$ = table_find("<localhost>", NULL);
+ rule->r_notsources = $2;
}
| /* empty */ {
$$ = table_find("<localhost>", NULL);
}
;
-sender : SENDER tables {
- struct table *t = $2;
+sender : SENDER negation tables {
+ struct table *t = $3;
if (! table_check_use(t, T_DYNAMIC|T_LIST, K_MAILADDR)) {
yyerror("invalid use of table \"%s\" as SENDER parameter",
t->t_name);
YYERROR;
}
-
+ rule->r_notsenders = $2;
$$ = t;
}
| /* empty */ { $$ = NULL; }
diff --git a/smtpd/ruleset.c b/smtpd/ruleset.c
index b6589edb..cac817d9 100644
--- a/smtpd/ruleset.c
+++ b/smtpd/ruleset.c
@@ -47,15 +47,20 @@ ruleset_match(const struct envelope *evp)
TAILQ_FOREACH(r, env->sc_rules, r_entry) {
- if (r->r_tag[0] != '\0' && strcmp(r->r_tag, evp->tag) != 0)
- continue;
+ if (r->r_tag[0] != '\0') {
+ ret = strcmp(r->r_tag, evp->tag);
+ if (ret != 0 && !r->r_nottag)
+ continue;
+ if (ret == 0 && r->r_nottag)
+ continue;
+ }
ret = ruleset_check_source(r->r_sources, ss, evp->flags);
if (ret == -1) {
errno = EAGAIN;
return (NULL);
}
- if (ret == 0)
+ if ((ret == 0 && !r->r_notsources) || (ret != 0 && r->r_notsources))
continue;
if (r->r_senders) {
@@ -64,7 +69,7 @@ ruleset_match(const struct envelope *evp)
errno = EAGAIN;
return (NULL);
}
- if (ret == 0)
+ if ((ret == 0 && !r->r_notsenders) || (ret != 0 && r->r_notsenders))
continue;
}
@@ -75,19 +80,21 @@ ruleset_match(const struct envelope *evp)
errno = EAGAIN;
return NULL;
}
- if (ret) {
- if (r->r_desttype == DEST_VDOM &&
- (r->r_action == A_RELAY || r->r_action == A_RELAYVIA)) {
- if (! aliases_virtual_check(r->r_mapping,
- &evp->rcpt)) {
- return NULL;
- }
+ if ((ret == 0 && !r->r_notdestination) || (ret != 0 && r->r_notdestination))
+ continue;
+
+ if (r->r_desttype == DEST_VDOM &&
+ (r->r_action == A_RELAY || r->r_action == A_RELAYVIA)) {
+ if (! aliases_virtual_check(r->r_mapping,
+ &evp->rcpt)) {
+ return NULL;
}
- goto matched;
}
+ goto matched;
}
errno = 0;
+ log_trace(TRACE_RULES, "no rule matched");
return (NULL);
matched:
diff --git a/smtpd/smtpd.conf.5 b/smtpd/smtpd.conf.5
index d2ee5a2e..777012ec 100644
--- a/smtpd/smtpd.conf.5
+++ b/smtpd/smtpd.conf.5
@@ -80,33 +80,57 @@ The first matching rule decides what action is taken.
If no rule matches the message,
the default action is to reject the message.
.Pp
+Whenever \! is used, it will performe a reverse match.
+.Pp
Following the accept/reject
-decision comes the client's IP address filter:
+decision comes the optional tag matching:
+.Bl -tag -width Ds
+.It Xo
+.Ic tagged
+.Op Ic \!
+.Ic tag
+.Xc
+If specified, the rule will only be matched if the client session was tagged
+.Ar tag .
+.El
+.Pp
+Then, comes the client's IP address filter:
.Bl -tag -width Ds
.It Ic from any
Make the rule match regardless of the IP of connecting client.
-.It Ic from local
+.It Xo
+.Ic from
+.Op Ic \!
+.Ic local
+.Xc
The rule matches only locally originating connections.
This is the default,
and may be omitted.
-.It Ic from Ar network
+.It Xo
+.Ic from
+.Op Ic \!
+.Ic network
+.Xc
The rule matches if the connection is made from the specified
.Ar network ,
specified in CIDR notation.
-.It Ic from Aq Ar table
+.It Xo
+.Ic from
+.Op Ic \!
+.Ic table
+.Xc
The rule matches if the connection is made from a client whose address
is declared in the table
.Ar table .
-.It Ic tagged Ar tag
-If specified, the rule will only be matched if the client session was tagged
-.Ar tag .
-Mails enqueued via the local socket are tagged
-.Qq local .
.El
.Pp
In addition, finer filtering may be achieved on the sender if desired:
.Bl -tag -width Ds
-.It Ic sender Ar senders
+.It Xo
+.Ic sender
+.Op Ic \!
+.Ic senders
+.Xc
If specified, the rule will only be matched if the sender email address
is found in the table
.Ar senders .
@@ -126,7 +150,13 @@ Make the rule match regardless of the domain it is sent to.
The
.Ar vmap
table will be used as the virtual domain mapping.
-.It Ic for domain Ar domain Op Ic alias Aq Ar aliases
+.It Xo
+.Ic for
+.Op Ic \!
+.Ic domain
+.Ar domain
+.Op Ic alias Aq Ar aliases
+.Xc
This rule applies to mail destined for the specified
.Ar domain .
This parameter supports the
@@ -141,7 +171,13 @@ If specified, the table
.Ar aliases
is used for looking up alternative destinations for addresses in this
.Ar domain .
-.It Ic for domain Aq Ar domains Op Ic alias Aq Ar aliases
+.It Xo
+.Ic for
+.Op Ic \!
+.Ic domain
+.Aq Ar domains
+.Op Ic alias Aq Ar aliases
+.Xc
This rule applies to mail destined to domains which are part of the table
.Ar domains .
.Pp
@@ -149,7 +185,13 @@ If specified, the table
.Ar aliases
is used for looking up alternative destinations for addresses in these
.Ar domains .
-.It Ic for domain Ar domain Ic virtual Aq Ar users
+.It Xo
+.Ic for
+.Op Ic \!
+.Ic domain
+.Ar domain
+.Ic virtual Aq Ar users
+.Xc
This rule applies to mail destined for the specified virtual
.Ar domain .
This parameter supports the
@@ -168,7 +210,13 @@ For an example of how to configure the
.Ar users
table, see
.Xr makemap 8 .
-.It Ic for domain Ao Ar domains Ac Ic virtual Aq Ar users
+.It Xo
+.Ic for
+.Op Ic \!
+.Ic domain
+.Ao Ar domains
+.Ac Ic virtual Aq Ar users
+.Xc
This rule applies to mail destined for the virtual domains specified
in the table
.Ar domains .
@@ -180,7 +228,12 @@ For an example of how to configure the
.Ar users
table, see
.Xr makemap 8 .
-.It Ic for local Op Ic alias Aq Ar aliases
+.It Xo
+.Ic for
+.Op Ic \!
+.Ic local
+.Op Ic alias Aq Ar aliases
+.Xc
This rule applies to mail destined to
.Dq localhost
and to the default server name.
@@ -189,7 +242,12 @@ See the
entry for
.Pa /etc/mail/mailname
below for details of how the server name is determined.
-.It Ic for local virtual Aq Ar vmap
+.It Xo
+.Ic for
+.Op Ic \!
+.Ic local
+.Ic virtual Aq Ar vmap
+.Xc
This rule applies to mail destined to
.Dq localhost
and to the default server name.
diff --git a/smtpd/smtpd.h b/smtpd/smtpd.h
index af10cf26..22568749 100644
--- a/smtpd/smtpd.h
+++ b/smtpd/smtpd.h
@@ -336,10 +336,16 @@ enum decision {
struct rule {
TAILQ_ENTRY(rule) r_entry;
enum decision r_decision;
+ uint8_t r_nottag;
char r_tag[MAX_TAG_SIZE];
+
+ uint8_t r_notsources;
struct table *r_sources;
+
+ uint8_t r_notsenders;
struct table *r_senders;
+ uint8_t r_notdestination;
enum dest_type r_desttype;
struct table *r_destination;
diff --git a/smtpd/to.c b/smtpd/to.c
index 5aeb0aa0..2c2b37e4 100644
--- a/smtpd/to.c
+++ b/smtpd/to.c
@@ -511,19 +511,26 @@ rule_to_text(struct rule *r)
bzero(buf, sizeof buf);
strlcpy(buf, r->r_decision == R_ACCEPT ? "accept" : "reject", sizeof buf);
if (r->r_tag[0]) {
- strlcat(buf, " on ", sizeof buf);
+ strlcat(buf, " tagged ", sizeof buf);
+ if (r->r_nottag)
+ strlcat(buf, "! ", sizeof buf);
strlcat(buf, r->r_tag, sizeof buf);
}
strlcat(buf, " from ", sizeof buf);
+ if (r->r_notsources)
+ strlcat(buf, "! ", sizeof buf);
strlcat(buf, r->r_sources->t_name, sizeof buf);
+ strlcat(buf, " for ", sizeof buf);
+ if (r->r_notdestination)
+ strlcat(buf, "! ", sizeof buf);
switch (r->r_desttype) {
case DEST_DOM:
if (r->r_destination == NULL) {
- strlcat(buf, " for any", sizeof buf);
+ strlcat(buf, " any", sizeof buf);
break;
}
- strlcat(buf, " for domain ", sizeof buf);
+ strlcat(buf, " domain ", sizeof buf);
strlcat(buf, r->r_destination->t_name, sizeof buf);
if (r->r_mapping) {
strlcat(buf, " alias ", sizeof buf);
@@ -532,11 +539,11 @@ rule_to_text(struct rule *r)
break;
case DEST_VDOM:
if (r->r_destination == NULL) {
- strlcat(buf, " for any virtual ", sizeof buf);
+ strlcat(buf, " any virtual ", sizeof buf);
strlcat(buf, r->r_mapping->t_name, sizeof buf);
break;
}
- strlcat(buf, " for domain ", sizeof buf);
+ strlcat(buf, " domain ", sizeof buf);
strlcat(buf, r->r_destination->t_name, sizeof buf);
strlcat(buf, " virtual ", sizeof buf);
strlcat(buf, r->r_mapping->t_name, sizeof buf);