Add Match action and connect|content|pcap|mirror log actions in filtering rules

- Match action is added to be used with log actions only, the other
filter actions can specify log actions too
- Log actions do not configure any loggers. Global loggers for
respective log actions should have been configured for those log actions
to have any effect.
- If no filter rules are defined for a proxyspec, all log actions are
enabled. Otherwise, all log actions are disabled, and filtering rules
should enable them specifically.
- Fix max number of tokens in proxyspec and filter parsers
- Fix issues with rejecting unknown args in filter rule parser
- Do not use filter_rules field of proxyspec after config finished, it
is used for filter configuration and freed afterwards
pull/48/head
Soner Tari 3 years ago
parent 0787e74bd1
commit f0c2ca6819

@ -1,4 +1,26 @@
### SSLproxy 0.8.7
- Add filtering rules:
(Divert|Split|Pass|Block|Match)
([from (
user (username|*) [desc keyword]|
ip (clientaddr|*)|
*)]
[to (
sni (servername[*]|*)|
cn (commonname[*]|*)|
host (host[*]|*)|
uri (uri[*]|*)|
ip (serveraddr|*)|
*)]
[log ([connect] [content] [pcap] [mirror]|*)]
|*)
- Add -Q test config option.
### SSLproxy 0.8.4 2021-08-29 ### SSLproxy 0.8.4 2021-08-29
- Add split mode of operation similar to SSLsplit. In split mode, packets - Add split mode of operation similar to SSLsplit. In split mode, packets

@ -296,8 +296,8 @@ These user control lists can be defined globally or per-proxyspec.
### Filtering rules ### Filtering rules
SSLproxy can divert, split, pass, or block connections based on filtering SSLproxy can divert, split, pass, block, or match connections based on
rules. Filtering rules can be defined globally or per-proxyspec. filtering rules. Filtering rules can be defined globally or per-proxyspec.
- Divert action diverts packets to listening program, allowing SSL inspection - Divert action diverts packets to listening program, allowing SSL inspection
by listening program and content logging of packets by listening program and content logging of packets
@ -307,10 +307,12 @@ allowing content logging of packets
- Pass action passes the connection through by engaging passthrough mode, - Pass action passes the connection through by engaging passthrough mode,
effectively disabling SSL inspection and content logging of packets effectively disabling SSL inspection and content logging of packets
- Block action terminates the connection - Block action terminates the connection
- Match action is used to specify log actions for matching connections without
changing their filter actions
The syntax of filtering rules is as follows: The syntax of filtering rules is as follows:
(Divert|Split|Pass|Block) (Divert|Split|Pass|Block|Match)
([from ( ([from (
user (username|*) [desc keyword]| user (username|*) [desc keyword]|
ip (clientaddr|*)| ip (clientaddr|*)|
@ -322,6 +324,7 @@ The syntax of filtering rules is as follows:
uri (uri[*]|*)| uri (uri[*]|*)|
ip (serveraddr|*)| ip (serveraddr|*)|
*)] *)]
[log ([connect] [content] [pcap] [mirror]|*)]
|*) |*)
The definition of which connections the rule action will be applied to is The definition of which connections the rule action will be applied to is
@ -337,18 +340,40 @@ Common Names of SSL connections, Host or URI fields in HTTP Request headers, or
- The proxyspec handling the connection defines the protocol filter for the - The proxyspec handling the connection defines the protocol filter for the
connection. connection.
If and how a connection should be logged is specified using the `log` part of
filtering rules. `connect` enables logging connection information to connect
log file, `content` enables logging packet contents to content log file,
`pcap` enables writing packets to pcap files, and `mirror` enables mirroring
packets to mirror interfaces or targets.
For example, if the following rules are defined in a structured HTTPS proxyspec, For example, if the following rules are defined in a structured HTTPS proxyspec,
Split from user soner desc notebook to sni example.com Split from user soner desc notebook to sni example.com log content
Pass from user soner desc android to cn .fbcdn.net* Pass from user soner desc android to cn .fbcdn.net*
The first filtering rule above splits but does not divert HTTPS connections The first filtering rule above splits but does not divert HTTPS connections
from the user soner who has logged in with a description containing the keyword from the user soner who has logged in with a description containing the keyword
notebook to SSL sites with the SNI of example.com. The second rule passes notebook to SSL sites with the SNI of example.com. Also, the rule specifies
through HTTPS connections from the user soner who has logged in with a that the packet contents of the matching connection be written to content log
description containing the keyword android to SSL sites with the Common Names file configured globally.
containing the substring .fbcdn.net anywhere in it (notice the asterisk at the
end). Note that the second example is a filtering rule you can use to resolve The second rule passes through HTTPS connections from the user soner who has
logged in with a description containing the keyword android to SSL sites with
the Common Names containing the substring .fbcdn.net anywhere in it (notice
the asterisk at the end). Since connection contents cannot be written to log
files in passthrough mode, the rule does not specify any log action.
The default filter action is Divert. So, if those are the only filtering rules
in that proxyspec, the other connections are diverted to the listening program
specified in that proxyspec, without writing any logs.
If you want to enable, say, connect logging for the other connections handled
by that proxyspec, without changing their default Divert filter action, you
can add a third filtering rule to that proxyspec:
Match * log connect
Note that the second example above is a filtering rule you can use to resolve
one of the certificate issues preventing the Facebook application on Android one of the certificate issues preventing the Facebook application on Android
smartphones to connect to the Internet behind sslproxy. smartphones to connect to the Internet behind sslproxy.
@ -373,10 +398,21 @@ In terms of possible filter actions,
- HTTP filter rules can take the block action, but not divert, split, or pass - HTTP filter rules can take the block action, but not divert, split, or pass
actions. actions.
Log actions do not configure any loggers. Global content loggers for
respective log actions should have been configured for those log actions to
have any effect.
If no filter rules are defined for a proxyspec, all logging actions for that
proxyspec are enabled. Otherwise, all log actions are disabled, and filtering
rules should enable them specifically. Note that if logging is disabled by
filtering rules, the loggers create the log files, but they remain empty.
You can append an asterisk `*` to site field of filtering rules for substring You can append an asterisk `*` to site field of filtering rules for substring
matching. Otherwise, the filter searches for an exact match with the site field matching. Otherwise, the filter searches for an exact match with the site field
in the rule. in the rule.
The order of `from`, `to`, and `log` parts is not important.
If the UserAuth option is disabled, only client IP addresses can be used in If the UserAuth option is disabled, only client IP addresses can be used in
the from part of filtering rules. the from part of filtering rules.
@ -403,7 +439,7 @@ on each line. PassSite rules can search for exact or substring matches.
Logging options include traditional SSLproxy connect and content log files as Logging options include traditional SSLproxy connect and content log files as
well as PCAP files and mirroring decrypted traffic to a network interface. well as PCAP files and mirroring decrypted traffic to a network interface.
Additionally, certificates, master secrets and local process information can be Additionally, certificates, master secrets and local process information can be
logged. logged. Filtering rules can selectively modify connection logging.
See the manual pages sslproxy(1) and sslproxy.conf(5) for details on using See the manual pages sslproxy(1) and sslproxy.conf(5) for details on using
SSLproxy, setting up the various NAT engines, and for examples. SSLproxy, setting up the various NAT engines, and for examples.

@ -896,7 +896,11 @@ errout:
* On failure, lb is not freed. * On failure, lb is not freed.
*/ */
int int
log_content_submit(log_content_ctx_t *ctx, logbuf_t *lb, int is_request) log_content_submit(log_content_ctx_t *ctx, logbuf_t *lb, int is_request, int log_content, int log_pcap
#ifndef WITHOUT_MIRROR
, int log_mirror
#endif /* !WITHOUT_MIRROR */
)
{ {
unsigned long prepflags = 0; unsigned long prepflags = 0;
logbuf_t *lbpcap, *lbmirror; logbuf_t *lbpcap, *lbmirror;
@ -909,26 +913,26 @@ log_content_submit(log_content_ctx_t *ctx, logbuf_t *lb, int is_request)
return -1; return -1;
lbpcap = lbmirror = lb; lbpcap = lbmirror = lb;
if (content_file_log) { if (log_content && content_file_log) {
if (content_pcap_log) { if (log_pcap && content_pcap_log) {
lbpcap = logbuf_new_deepcopy(lb, 1); lbpcap = logbuf_new_deepcopy(lb, 1);
if (!lbpcap) if (!lbpcap)
goto errout; goto errout;
} }
#ifndef WITHOUT_MIRROR #ifndef WITHOUT_MIRROR
if (content_mirror_log) { if (log_mirror && content_mirror_log) {
lbmirror = logbuf_new_deepcopy(lb, 1); lbmirror = logbuf_new_deepcopy(lb, 1);
if (!lbmirror) if (!lbmirror)
goto errout; goto errout;
} }
} else if (content_pcap_log && content_mirror_log) { } else if (log_pcap && content_pcap_log && log_mirror && content_mirror_log) {
lbmirror = logbuf_new_deepcopy(lb, 1); lbmirror = logbuf_new_deepcopy(lb, 1);
if (!lbmirror) if (!lbmirror)
goto errout; goto errout;
#endif /* !WITHOUT_MIRROR */ #endif /* !WITHOUT_MIRROR */
} }
if (content_pcap_log) { if (log_pcap && content_pcap_log) {
if (logger_submit(content_pcap_log, ctx->pcap, if (logger_submit(content_pcap_log, ctx->pcap,
prepflags, lbpcap) == -1) { prepflags, lbpcap) == -1) {
goto errout; goto errout;
@ -936,7 +940,7 @@ log_content_submit(log_content_ctx_t *ctx, logbuf_t *lb, int is_request)
lbpcap = NULL; lbpcap = NULL;
} }
#ifndef WITHOUT_MIRROR #ifndef WITHOUT_MIRROR
if (content_mirror_log) { if (log_mirror && content_mirror_log) {
if (logger_submit(content_mirror_log, ctx->mirror, if (logger_submit(content_mirror_log, ctx->mirror,
prepflags, lbmirror) == -1) { prepflags, lbmirror) == -1) {
goto errout; goto errout;
@ -944,7 +948,7 @@ log_content_submit(log_content_ctx_t *ctx, logbuf_t *lb, int is_request)
lbmirror = NULL; lbmirror = NULL;
} }
#endif /* !WITHOUT_MIRROR */ #endif /* !WITHOUT_MIRROR */
if (content_file_log) { if (log_content && content_file_log) {
if (logger_submit(content_file_log, ctx->file, if (logger_submit(content_file_log, ctx->file,
prepflags, lb) == -1) { prepflags, lb) == -1) {
return -1; return -1;
@ -960,7 +964,12 @@ errout:
} }
int int
log_content_close(log_content_ctx_t *ctx, int by_requestor) log_content_close(log_content_ctx_t *ctx, int by_requestor, int log_content, int log_pcap
#ifndef WITHOUT_MIRROR
, int log_mirror
#endif /* !WITHOUT_MIRROR */
)
{ {
unsigned long prepflags = PREPFLAG_EOF; unsigned long prepflags = PREPFLAG_EOF;
unsigned long ctl; unsigned long ctl;
@ -977,7 +986,7 @@ log_content_close(log_content_ctx_t *ctx, int by_requestor)
* closing the file. The logger_close() call will actually close the * closing the file. The logger_close() call will actually close the
* log. Some logs prefer to use the close callback for logging the * log. Some logs prefer to use the close callback for logging the
* close event to the log. */ * close event to the log. */
if (content_file_log && ctx->file) { if (log_content && content_file_log && ctx->file) {
if (logger_submit(content_file_log, ctx->file, if (logger_submit(content_file_log, ctx->file,
prepflags, NULL) == -1) { prepflags, NULL) == -1) {
return -1; return -1;
@ -987,7 +996,7 @@ log_content_close(log_content_ctx_t *ctx, int by_requestor)
} }
ctx->file = NULL; ctx->file = NULL;
} }
if (content_pcap_log && ctx->pcap) { if (log_pcap && content_pcap_log && ctx->pcap) {
if (logger_submit(content_pcap_log, ctx->pcap, if (logger_submit(content_pcap_log, ctx->pcap,
prepflags, NULL) == -1) { prepflags, NULL) == -1) {
return -1; return -1;
@ -998,7 +1007,7 @@ log_content_close(log_content_ctx_t *ctx, int by_requestor)
ctx->pcap = NULL; ctx->pcap = NULL;
} }
#ifndef WITHOUT_MIRROR #ifndef WITHOUT_MIRROR
if (content_mirror_log && ctx->mirror) { if (log_mirror && content_mirror_log && ctx->mirror) {
if (logger_submit(content_mirror_log, ctx->mirror, if (logger_submit(content_mirror_log, ctx->mirror,
prepflags, NULL) == -1) { prepflags, NULL) == -1) {
return -1; return -1;

@ -147,9 +147,16 @@ int log_content_open(log_content_ctx_t *, global_t *,
const struct sockaddr *, socklen_t, const struct sockaddr *, socklen_t,
char *, char *, char *, char *, char *, char *, char *, char *,
char *, char *, char *) NONNULL(1,2,3) WUNRES; char *, char *, char *) NONNULL(1,2,3) WUNRES;
int log_content_submit(log_content_ctx_t *, logbuf_t *, int) int log_content_submit(log_content_ctx_t *, logbuf_t *, int, int, int
NONNULL(1,2) WUNRES; #ifndef WITHOUT_MIRROR
int log_content_close(log_content_ctx_t *, int) NONNULL(1) WUNRES; , int
#endif /* !WITHOUT_MIRROR */
) NONNULL(1,2) WUNRES;
int log_content_close(log_content_ctx_t *, int, int, int
#ifndef WITHOUT_MIRROR
, int
#endif /* !WITHOUT_MIRROR */
) NONNULL(1) WUNRES;
int log_content_split_pathspec(const char *, char **, int log_content_split_pathspec(const char *, char **,
char **) NONNULL(1,2,3) WUNRES; char **) NONNULL(1,2,3) WUNRES;

@ -783,6 +783,14 @@ clone_global_opts(global_t *global, const char *argv0, tmp_global_opts_t *tmp_gl
fr->split = rule->split; fr->split = rule->split;
fr->pass = rule->pass; fr->pass = rule->pass;
fr->block = rule->block; fr->block = rule->block;
fr->match = rule->match;
fr->log_connect = rule->log_connect;
fr->log_content = rule->log_content;
fr->log_pcap = rule->log_pcap;
#ifndef WITHOUT_MIRROR
fr->log_mirror = rule->log_mirror;
#endif /* !WITHOUT_MIRROR */
fr->dstip = rule->dstip; fr->dstip = rule->dstip;
fr->sni = rule->sni; fr->sni = rule->sni;
@ -1129,7 +1137,11 @@ filter_rule_str(filter_rule_t *rule)
#ifndef WITHOUT_USERAUTH #ifndef WITHOUT_USERAUTH
"|%s" "|%s"
#endif /* !WITHOUT_USERAUTH */ #endif /* !WITHOUT_USERAUTH */
"|%s, action=%s|%s|%s|%s, apply to=%s|%s|%s|%s|%s", "|%s, action=%s|%s|%s|%s|%s, log=%s|%s|%s"
#ifndef WITHOUT_MIRROR
"|%s"
#endif /* !WITHOUT_MIRROR */
", apply to=%s|%s|%s|%s|%s",
rule->site, rule->exact ? "exact" : "substring", STRORNONE(rule->ip), rule->site, rule->exact ? "exact" : "substring", STRORNONE(rule->ip),
#ifndef WITHOUT_USERAUTH #ifndef WITHOUT_USERAUTH
STRORNONE(rule->user), STRORNONE(rule->keyword), STRORNONE(rule->user), STRORNONE(rule->keyword),
@ -1139,7 +1151,11 @@ filter_rule_str(filter_rule_t *rule)
rule->all_users ? "users" : "", rule->all_users ? "users" : "",
#endif /* !WITHOUT_USERAUTH */ #endif /* !WITHOUT_USERAUTH */
rule->all_sites ? "sites" : "", rule->all_sites ? "sites" : "",
rule->divert ? "divert" : "", rule->split ? "split" : "", rule->pass ? "pass" : "", rule->block ? "block" : "", rule->divert ? "divert" : "", rule->split ? "split" : "", rule->pass ? "pass" : "", rule->block ? "block" : "", rule->match ? "match" : "",
rule->log_connect ? "connect" : "", rule->log_content ? "content" : "", rule->log_pcap ? "pcap" : "",
#ifndef WITHOUT_MIRROR
rule->log_mirror ? "mirror" : "",
#endif /* !WITHOUT_MIRROR */
rule->dstip ? "dstip" : "", rule->sni ? "sni" : "", rule->cn ? "cn" : "", rule->host ? "host" : "", rule->uri ? "uri" : "" rule->dstip ? "dstip" : "", rule->sni ? "sni" : "", rule->cn ? "cn" : "", rule->host ? "host" : "", rule->uri ? "uri" : ""
) < 0) { ) < 0) {
goto err; goto err;
@ -1175,9 +1191,17 @@ filter_sites_str(filter_site_t *site)
int count = 0; int count = 0;
while (site) { while (site) {
char *p; char *p;
if (asprintf(&p, "%s\n %d: %s (%s%s, action=%s|%s|%s|%s)", STRORNONE(s), count, if (asprintf(&p, "%s\n %d: %s (%s%s, action=%s|%s|%s|%s|%s, log=%s|%s|%s"
#ifndef WITHOUT_MIRROR
"|%s"
#endif /* !WITHOUT_MIRROR */
")", STRORNONE(s), count,
site->site, site->all_sites ? "all_sites, " : "", site->exact ? "exact" : "substring", site->site, site->all_sites ? "all_sites, " : "", site->exact ? "exact" : "substring",
site->divert ? "divert" : "", site->split ? "split" : "", site->pass ? "pass" : "", site->block ? "block" : "" site->divert ? "divert" : "", site->split ? "split" : "", site->pass ? "pass" : "", site->block ? "block" : "", site->match ? "match" : "",
site->log_connect ? "connect" : "", site->log_content ? "content" : "", site->log_pcap ? "pcap" : ""
#ifndef WITHOUT_MIRROR
, site->log_mirror ? "mirror" : ""
#endif /* !WITHOUT_MIRROR */
) < 0) { ) < 0) {
goto err; goto err;
} }
@ -2301,7 +2325,7 @@ opts_set_passsite(opts_t *opts, char *value, int line_num)
{ {
#define MAX_PASSSITE_TOKENS 3 #define MAX_PASSSITE_TOKENS 3
// site[*] [(clientaddr|user|*) [description keyword]] // site[*] [(clientaddr|user|*) [keyword]]
char *argv[sizeof(char *) * MAX_PASSSITE_TOKENS]; char *argv[sizeof(char *) * MAX_PASSSITE_TOKENS];
int argc = 0; int argc = 0;
char *p, *last = NULL; char *p, *last = NULL;
@ -2312,7 +2336,8 @@ opts_set_passsite(opts_t *opts, char *value, int line_num)
if (argc < MAX_PASSSITE_TOKENS) { if (argc < MAX_PASSSITE_TOKENS) {
argv[argc++] = p; argv[argc++] = p;
} else { } else {
break; fprintf(stderr, "Too many arguments in passsite option on line %d\n", line_num);
exit(EXIT_FAILURE);
} }
} }
@ -2409,7 +2434,11 @@ opts_set_passsite(opts_t *opts, char *value, int line_num)
#ifndef WITHOUT_USERAUTH #ifndef WITHOUT_USERAUTH
"%s|" "%s|"
#endif /* !WITHOUT_USERAUTH */ #endif /* !WITHOUT_USERAUTH */
"%s, action=%s|%s|%s|%s, , apply to=%s|%s|%s|%s|%s\n", "%s, action=%s|%s|%s|%s|%s, log=%s|%s|%s"
#ifndef WITHOUT_MIRROR
"|%s"
#endif /* !WITHOUT_MIRROR */
", apply to=%s|%s|%s|%s|%s\n",
rule->site, rule->exact ? "exact" : "substring", STRORNONE(rule->ip), rule->site, rule->exact ? "exact" : "substring", STRORNONE(rule->ip),
#ifndef WITHOUT_USERAUTH #ifndef WITHOUT_USERAUTH
STRORNONE(rule->user), STRORNONE(rule->keyword), STRORNONE(rule->user), STRORNONE(rule->keyword),
@ -2419,7 +2448,11 @@ opts_set_passsite(opts_t *opts, char *value, int line_num)
rule->all_users ? "users" : "", rule->all_users ? "users" : "",
#endif /* !WITHOUT_USERAUTH */ #endif /* !WITHOUT_USERAUTH */
rule->all_sites ? "sites" : "", rule->all_sites ? "sites" : "",
rule->divert ? "divert" : "", rule->split ? "split" : "", rule->pass ? "pass" : "", rule->block ? "block" : "", rule->divert ? "divert" : "", rule->split ? "split" : "", rule->pass ? "pass" : "", rule->block ? "block" : "", rule->match ? "match" : "",
rule->log_connect ? "connect" : "", rule->log_content ? "content" : "", rule->log_pcap ? "pcap" : "",
#ifndef WITHOUT_MIRROR
rule->log_mirror ? "mirror" : "",
#endif /* !WITHOUT_MIRROR */
rule->dstip ? "dstip" : "", rule->sni ? "sni" : "", rule->cn ? "cn" : "", rule->host ? "host" : "", rule->uri ? "uri" : ""); rule->dstip ? "dstip" : "", rule->sni ? "sni" : "", rule->cn ? "cn" : "", rule->host ? "host" : "", rule->uri ? "uri" : "");
#endif /* DEBUG_OPTS */ #endif /* DEBUG_OPTS */
} }
@ -2464,12 +2497,12 @@ opts_inc_arg_index(int i, int argc, char *last, int line_num)
} }
} }
void static void
opts_set_filter_rule(opts_t *opts, const char *name, char *value, int line_num) filter_rule_parse(opts_t *opts, const char *name, char *value, int line_num)
{ {
#define MAX_FILTER_RULE_TOKENS 10 #define MAX_FILTER_RULE_TOKENS 13
//(Divert|Split|Pass|Block) //(Divert|Split|Pass|Block|Match)
// ([from ( // ([from (
// user (username|*) [desc keyword]| // user (username|*) [desc keyword]|
// ip (clientaddr|*)| // ip (clientaddr|*)|
@ -2481,8 +2514,8 @@ opts_set_filter_rule(opts_t *opts, const char *name, char *value, int line_num)
// uri (uri[*]|*)| // uri (uri[*]|*)|
// ip (serveraddr|*)| // ip (serveraddr|*)|
// *)] // *)]
// [log ([connect] [content] [pcap] [mirror]|*)]
// |*) // |*)
// [(log (connect|content|separate|dir))*]
char *argv[sizeof(char *) * MAX_FILTER_RULE_TOKENS]; char *argv[sizeof(char *) * MAX_FILTER_RULE_TOKENS];
int argc = 0; int argc = 0;
@ -2494,7 +2527,8 @@ opts_set_filter_rule(opts_t *opts, const char *name, char *value, int line_num)
if (argc < MAX_FILTER_RULE_TOKENS) { if (argc < MAX_FILTER_RULE_TOKENS) {
argv[argc++] = p; argv[argc++] = p;
} else { } else {
break; fprintf(stderr, "Too many arguments in filter rule on line %d\n", line_num);
exit(EXIT_FAILURE);
} }
} }
@ -2509,10 +2543,13 @@ opts_set_filter_rule(opts_t *opts, const char *name, char *value, int line_num)
rule->pass = 1; rule->pass = 1;
else if (equal(name, "Block")) else if (equal(name, "Block"))
rule->block = 1; rule->block = 1;
else if (equal(name, "Match"))
rule->match = 1;
int done_all = 0;
int done_from = 0; int done_from = 0;
int done_to = 0; int done_to = 0;
int done_all = 0; int done_log = 0;
int i = 0; int i = 0;
while (i < argc) { while (i < argc) {
if (equal(argv[i], "*")) { if (equal(argv[i], "*")) {
@ -2596,11 +2633,16 @@ opts_set_filter_rule(opts_t *opts, const char *name, char *value, int line_num)
i = opts_inc_arg_index(i, argc, argv[i], line_num); i = opts_inc_arg_index(i, argc, argv[i], line_num);
if (equal(argv[i], "sni") || equal(argv[i], "cn") || equal(argv[i], "host") || equal(argv[i], "uri") || equal(argv[i], "ip")) { if (equal(argv[i], "sni") || equal(argv[i], "cn") || equal(argv[i], "host") || equal(argv[i], "uri") || equal(argv[i], "ip")) {
rule->sni = equal(argv[i], "sni") ? 1 : 0; if (equal(argv[i], "sni"))
rule->cn = equal(argv[i], "cn") ? 1 : 0; rule->sni = 1;
rule->host = equal(argv[i], "host") ? 1 : 0; else if (equal(argv[i], "cn"))
rule->uri = equal(argv[i], "uri") ? 1 : 0; rule->cn = 1;
rule->dstip = equal(argv[i], "ip") ? 1 : 0; else if (equal(argv[i], "host"))
rule->host = 1;
else if (equal(argv[i], "uri"))
rule->uri = 1;
else if (equal(argv[i], "ip"))
rule->dstip = 1;
i = opts_inc_arg_index(i, argc, argv[i], line_num); i = opts_inc_arg_index(i, argc, argv[i], line_num);
opts_set_site(rule, argv[i++], line_num); opts_set_site(rule, argv[i++], line_num);
@ -2611,13 +2653,59 @@ opts_set_filter_rule(opts_t *opts, const char *name, char *value, int line_num)
i++; i++;
} }
else { else {
fprintf(stderr, "Not enough arguments in filter rule after '%s' on line %d\n", argv[i], line_num); fprintf(stderr, "Unknown argument in filter rule at '%s' on line %d\n", argv[i], line_num);
exit(EXIT_FAILURE);
}
}
else if (equal(argv[i], "log")) {
if (done_log) {
fprintf(stderr, "Only one 'log' statement allowed on line %d\n", line_num);
exit(EXIT_FAILURE);
}
i = opts_inc_arg_index(i, argc, argv[i], line_num);
if (equal(argv[i], "connect") || equal(argv[i], "content") || equal(argv[i], "pcap")
#ifndef WITHOUT_MIRROR
|| equal(argv[i], "mirror")
#endif /* !WITHOUT_MIRROR */
) {
do {
if (equal(argv[i], "connect"))
rule->log_connect = 1;
else if (equal(argv[i], "content"))
rule->log_content = 1;
else if (equal(argv[i], "pcap"))
rule->log_pcap = 1;
#ifndef WITHOUT_MIRROR
else if (equal(argv[i], "mirror"))
rule->log_mirror = 1;
#endif /* !WITHOUT_MIRROR */
if (++i == argc)
break;
} while (equal(argv[i], "connect") || equal(argv[i], "content") || equal(argv[i], "pcap")
#ifndef WITHOUT_MIRROR
|| equal(argv[i], "mirror")
#endif /* !WITHOUT_MIRROR */
);
done_log = 1;
}
else if (equal(argv[i], "*")) {
rule->log_connect = 1;
rule->log_content = 1;
rule->log_pcap = 1;
#ifndef WITHOUT_MIRROR
rule->log_mirror = 1;
#endif /* !WITHOUT_MIRROR */
i++;
done_log = 1;
}
else {
fprintf(stderr, "Unknown argument in filter rule at '%s' on line %d\n", argv[i], line_num);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
} }
// Not implemented yet
//else if (equal(argv[i], "log")) {
//}
else { else {
fprintf(stderr, "Unknown argument in filter rule at '%s' on line %d\n", argv[i], line_num); fprintf(stderr, "Unknown argument in filter rule at '%s' on line %d\n", argv[i], line_num);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
@ -2648,7 +2736,11 @@ opts_set_filter_rule(opts_t *opts, const char *name, char *value, int line_num)
#ifndef WITHOUT_USERAUTH #ifndef WITHOUT_USERAUTH
"%s|" "%s|"
#endif /* !WITHOUT_USERAUTH */ #endif /* !WITHOUT_USERAUTH */
"%s, action=%s|%s|%s|%s, , apply to=%s|%s|%s|%s|%s\n", "%s, action=%s|%s|%s|%s|%s, log=%s|%s|%s"
#ifndef WITHOUT_MIRROR
"|%s"
#endif /* !WITHOUT_MIRROR */
", apply to=%s|%s|%s|%s|%s\n",
rule->site, rule->exact ? "exact" : "substring", STRORNONE(rule->ip), rule->site, rule->exact ? "exact" : "substring", STRORNONE(rule->ip),
#ifndef WITHOUT_USERAUTH #ifndef WITHOUT_USERAUTH
STRORNONE(rule->user), STRORNONE(rule->keyword), STRORNONE(rule->user), STRORNONE(rule->keyword),
@ -2658,7 +2750,11 @@ opts_set_filter_rule(opts_t *opts, const char *name, char *value, int line_num)
rule->all_users ? "users" : "", rule->all_users ? "users" : "",
#endif /* !WITHOUT_USERAUTH */ #endif /* !WITHOUT_USERAUTH */
rule->all_sites ? "sites" : "", rule->all_sites ? "sites" : "",
rule->divert ? "divert" : "", rule->split ? "split" : "", rule->pass ? "pass" : "", rule->block ? "block" : "", rule->divert ? "divert" : "", rule->split ? "split" : "", rule->pass ? "pass" : "", rule->block ? "block" : "", rule->match ? "match" : "",
rule->log_connect ? "connect" : "", rule->log_content ? "content" : "", rule->log_pcap ? "pcap" : "",
#ifndef WITHOUT_MIRROR
rule->log_mirror ? "mirror" : "",
#endif /* !WITHOUT_MIRROR */
rule->dstip ? "dstip" : "", rule->sni ? "sni" : "", rule->cn ? "cn" : "", rule->host ? "host" : "", rule->uri ? "uri" : ""); rule->dstip ? "dstip" : "", rule->sni ? "sni" : "", rule->cn ? "cn" : "", rule->host ? "host" : "", rule->uri ? "uri" : "");
#endif /* DEBUG_OPTS */ #endif /* DEBUG_OPTS */
} }
@ -2709,15 +2805,20 @@ opts_add_site(filter_site_t *site, filter_rule_t *rule)
s->all_sites = rule->all_sites; s->all_sites = rule->all_sites;
s->exact = rule->exact; s->exact = rule->exact;
// Multiple rules can set the action for the same site, hence no 'else' // Multiple rules can set an action for the same site, hence bit-wise OR
if (rule->divert) s->divert |= rule->divert;
s->divert = 1; s->split |= rule->split;
if (rule->split) s->pass |= rule->pass;
s->split = 1; s->block |= rule->block;
if (rule->pass) s->match |= rule->match;
s->pass = 1;
if (rule->block) // Multiple log actions can be set for the same site, hence bit-wise OR
s->block = 1; s->log_connect |= rule->log_connect;
s->log_content |= rule->log_content;
s->log_pcap |= rule->log_pcap;
#ifndef WITHOUT_MIRROR
s->log_mirror |= rule->log_mirror;
#endif /* !WITHOUT_MIRROR */
return prepend ? s : site; return prepend ? s : site;
} }
@ -2942,7 +3043,8 @@ opts_set_userlist(char *value, int line_num, userlist_t **list, const char *list
if (argc < MAX_USERS) { if (argc < MAX_USERS) {
argv[argc++] = p; argv[argc++] = p;
} else { } else {
break; fprintf(stderr, "Too many arguments in user list, max users allowed %d, on line %d\n", MAX_USERS, line_num);
exit(EXIT_FAILURE);
} }
} }
@ -3666,12 +3768,12 @@ set_option(opts_t *opts, const char *argv0,
#endif /* DEBUG_OPTS */ #endif /* DEBUG_OPTS */
} else if (equal(name, "PassSite")) { } else if (equal(name, "PassSite")) {
opts_set_passsite(opts, value, line_num); opts_set_passsite(opts, value, line_num);
} else if (equal(name, "Split") || equal(name, "Pass") || equal(name, "Block")) { } else if (equal(name, "Split") || equal(name, "Pass") || equal(name, "Block") || equal(name, "Match")) {
opts_set_filter_rule(opts, name, value, line_num); filter_rule_parse(opts, name, value, line_num);
} else if (equal(name, "Divert")) { } else if (equal(name, "Divert")) {
yes = is_yesno(value); yes = is_yesno(value);
if (yes == -1) { if (yes == -1) {
opts_set_filter_rule(opts, name, value, line_num); filter_rule_parse(opts, name, value, line_num);
} else { } else {
yes ? opts_set_divert(opts) : opts_unset_divert(opts); yes ? opts_set_divert(opts) : opts_unset_divert(opts);
} }
@ -3797,10 +3899,10 @@ get_name_value(char **name, char **value, const char sep, int line_num)
return 0; return 0;
} }
#define MAX_TOKENS 10 #define MAX_TOKENS 8
static void static void
load_proxyspec_line(global_t *global, const char *argv0, char *value, char **natengine, tmp_global_opts_t *tmp_global_opts) load_proxyspec_line(global_t *global, const char *argv0, char *value, char **natengine, int line_num, tmp_global_opts_t *tmp_global_opts)
{ {
/* Use MAX_TOKENS instead of computing the actual number of tokens in value */ /* Use MAX_TOKENS instead of computing the actual number of tokens in value */
char **argv = malloc(sizeof(char *) * MAX_TOKENS); char **argv = malloc(sizeof(char *) * MAX_TOKENS);
@ -3815,7 +3917,8 @@ load_proxyspec_line(global_t *global, const char *argv0, char *value, char **nat
if (argc < MAX_TOKENS) { if (argc < MAX_TOKENS) {
argv[argc++] = p; argv[argc++] = p;
} else { } else {
break; fprintf(stderr, "Too many arguments in proxyspec on line %d\n", line_num);
exit(EXIT_FAILURE);
} }
} }
@ -4003,7 +4106,7 @@ set_global_option(global_t *global, const char *argv0,
goto leave; goto leave;
} }
} else { } else {
load_proxyspec_line(global, argv0, value, natengine, tmp_global_opts); load_proxyspec_line(global, argv0, value, natengine, *line_num, tmp_global_opts);
} }
} else if (equal(name, "ConnIdleTimeout")) { } else if (equal(name, "ConnIdleTimeout")) {
unsigned int i = atoi(value); unsigned int i = atoi(value);

@ -48,14 +48,12 @@
#define STRORDASH(x) (((x)&&*(x))?(x):"-") #define STRORDASH(x) (((x)&&*(x))?(x):"-")
#define STRORNONE(x) (((x)&&*(x))?(x):"") #define STRORNONE(x) (((x)&&*(x))?(x):"")
enum filter_action { #define FILTER_ACTION_NONE 0x0
FILTER_ACTION_MATCH = -1, #define FILTER_ACTION_MATCH 0x2
FILTER_ACTION_NONE = 0, #define FILTER_ACTION_DIVERT 0x4
FILTER_ACTION_DIVERT, #define FILTER_ACTION_SPLIT 0x8
FILTER_ACTION_SPLIT, #define FILTER_ACTION_PASS 0x10
FILTER_ACTION_PASS, #define FILTER_ACTION_BLOCK 0x20
FILTER_ACTION_BLOCK,
};
#ifndef WITHOUT_USERAUTH #ifndef WITHOUT_USERAUTH
typedef struct userlist { typedef struct userlist {
@ -177,6 +175,15 @@ typedef struct filter_rule {
unsigned int split : 1; unsigned int split : 1;
unsigned int pass : 1; unsigned int pass : 1;
unsigned int block : 1; unsigned int block : 1;
unsigned int match : 1;
// Log action
unsigned int log_connect : 1;
unsigned int log_content : 1;
unsigned int log_pcap : 1;
#ifndef WITHOUT_MIRROR
unsigned int log_mirror : 1;
#endif /* !WITHOUT_MIRROR */
// Conn field to apply filter to // Conn field to apply filter to
unsigned int dstip : 1; /* 1 to apply to dst ip */ unsigned int dstip : 1; /* 1 to apply to dst ip */
@ -200,6 +207,13 @@ typedef struct filter_site {
unsigned int split : 1; unsigned int split : 1;
unsigned int pass : 1; unsigned int pass : 1;
unsigned int block : 1; unsigned int block : 1;
unsigned int match : 1;
unsigned int log_connect : 1;
unsigned int log_content : 1;
unsigned int log_pcap : 1;
#ifndef WITHOUT_MIRROR
unsigned int log_mirror : 1;
#endif /* !WITHOUT_MIRROR */
struct filter_site *next; struct filter_site *next;
} filter_site_t; } filter_site_t;

@ -42,6 +42,9 @@
static void NONNULL(1) static void NONNULL(1)
protohttp_log_connect(pxy_conn_ctx_t *ctx) protohttp_log_connect(pxy_conn_ctx_t *ctx)
{ {
if (!ctx->log_connect)
return;
protohttp_ctx_t *http_ctx = ctx->protoctx->arg; protohttp_ctx_t *http_ctx = ctx->protoctx->arg;
char *msg; char *msg;
@ -429,8 +432,7 @@ protossl_match_uri(pxy_conn_ctx_t *ctx, filter_site_t *site)
return 0; return 0;
} }
static enum filter_action protohttp_filter(pxy_conn_ctx_t *, filter_list_t *) NONNULL(1,2); static unsigned char NONNULL(1,2)
static enum filter_action
protohttp_filter(pxy_conn_ctx_t *ctx, filter_list_t *list) protohttp_filter(pxy_conn_ctx_t *ctx, filter_list_t *list)
{ {
protohttp_ctx_t *http_ctx = ctx->protoctx->arg; protohttp_ctx_t *http_ctx = ctx->protoctx->arg;
@ -500,15 +502,16 @@ protohttp_filter(pxy_conn_ctx_t *ctx, filter_list_t *list)
static int static int
protohttp_apply_filter(pxy_conn_ctx_t *ctx) protohttp_apply_filter(pxy_conn_ctx_t *ctx)
{ {
enum filter_action action; unsigned char action;
if ((action = pxyconn_filter(ctx, protohttp_filter))) { if ((action = pxyconn_filter(ctx, protohttp_filter))) {
if (action == FILTER_ACTION_BLOCK) { if (action & FILTER_ACTION_BLOCK) {
pxy_conn_term(ctx, 1); pxy_conn_term(ctx, 1);
return 1; return 1;
} }
else if (action == FILTER_ACTION_DIVERT || action == FILTER_ACTION_SPLIT || action == FILTER_ACTION_PASS) { else if (action & (FILTER_ACTION_DIVERT | FILTER_ACTION_SPLIT | FILTER_ACTION_PASS)) {
log_err_level_printf(LOG_WARNING, "HTTP filter cannot take divert, split, or pass action\n"); log_err_level_printf(LOG_WARNING, "HTTP filter cannot take divert, split, or pass action\n");
} }
//else { /* FILTER_ACTION_MATCH */ }
} }
return 0; return 0;
} }

@ -96,7 +96,7 @@ protossl_log_ssl_error(struct bufferevent *bev, pxy_conn_ctx_t *ctx)
ERR_GET_FUNC(sslerr), STRORDASH(ERR_func_error_string(sslerr))); ERR_GET_FUNC(sslerr), STRORDASH(ERR_func_error_string(sslerr)));
} }
} }
if (ctx->spec->opts->filter_rules && !ctx->pass) { if (ctx->spec->opts->filter && !ctx->pass) {
log_err_level_printf(LOG_WARNING, "Closing on ssl error without filter match: %s:%s, %s:%s, " log_err_level_printf(LOG_WARNING, "Closing on ssl error without filter match: %s:%s, %s:%s, "
#ifndef WITHOUT_USERAUTH #ifndef WITHOUT_USERAUTH
"%s, %s, " "%s, %s, "
@ -676,8 +676,7 @@ protossl_match_cn(pxy_conn_ctx_t *ctx, filter_site_t *site)
return 0; return 0;
} }
static enum filter_action protossl_filter(pxy_conn_ctx_t *, filter_list_t *) NONNULL(1,2); static unsigned char NONNULL(1,2)
static enum filter_action
protossl_filter(pxy_conn_ctx_t *ctx, filter_list_t *list) protossl_filter(pxy_conn_ctx_t *ctx, filter_list_t *list)
{ {
if (ctx->sslctx->sni) { if (ctx->sslctx->sni) {
@ -745,22 +744,23 @@ protossl_filter(pxy_conn_ctx_t *ctx, filter_list_t *list)
static int static int
protossl_apply_filter(pxy_conn_ctx_t *ctx) protossl_apply_filter(pxy_conn_ctx_t *ctx)
{ {
enum filter_action action; unsigned char action;
if ((action = pxyconn_filter(ctx, protossl_filter))) { if ((action = pxyconn_filter(ctx, protossl_filter))) {
if (action == FILTER_ACTION_DIVERT) { if (action & FILTER_ACTION_DIVERT) {
ctx->divert = 1; ctx->divert = 1;
} }
else if (action == FILTER_ACTION_SPLIT) { else if (action & FILTER_ACTION_SPLIT) {
ctx->divert = 0; ctx->divert = 0;
} }
else if (action == FILTER_ACTION_PASS) { else if (action & FILTER_ACTION_PASS) {
ctx->pass = 1; ctx->pass = 1;
return 1; return 1;
} }
else if (action == FILTER_ACTION_BLOCK) { else if (action & FILTER_ACTION_BLOCK) {
pxy_conn_term(ctx, 1); pxy_conn_term(ctx, 1);
return 1; return 1;
} }
//else { /* FILTER_ACTION_MATCH */ }
} }
return 0; return 0;
} }
@ -800,7 +800,7 @@ protossl_srcssl_create(pxy_conn_ctx_t *ctx, SSL *origssl)
protossl_debug_crt(cert->crt); protossl_debug_crt(cert->crt);
} }
if (WANT_CONNECT_LOG(ctx) || ctx->spec->opts->filter_rules) { if (WANT_CONNECT_LOG(ctx) || ctx->spec->opts->filter) {
ctx->sslctx->ssl_names = ssl_x509_names_to_str(ctx->sslctx->origcrt ? ctx->sslctx->ssl_names = ssl_x509_names_to_str(ctx->sslctx->origcrt ?
ctx->sslctx->origcrt : ctx->sslctx->origcrt :
cert->crt); cert->crt);
@ -906,7 +906,7 @@ protossl_ossl_servername_cb(SSL *ssl, UNUSED int *al, void *arg)
"certificate:\n"); "certificate:\n");
protossl_debug_crt(newcrt); protossl_debug_crt(newcrt);
} }
if (WANT_CONNECT_LOG(ctx) || ctx->spec->opts->filter_rules) { if (WANT_CONNECT_LOG(ctx) || ctx->spec->opts->filter) {
if (ctx->sslctx->ssl_names) { if (ctx->sslctx->ssl_names) {
free(ctx->sslctx->ssl_names); free(ctx->sslctx->ssl_names);
} }

@ -526,9 +526,7 @@ prototcp_filter_match(pxy_conn_ctx_t *ctx, filter_site_t *site)
return 0; return 0;
} }
// The editor complains without this forward declaration for the NONNULL attribute static unsigned char NONNULL(1,2)
static enum filter_action prototcp_dsthost_filter(pxy_conn_ctx_t *, filter_list_t *) NONNULL(1,2);
static enum filter_action
prototcp_dsthost_filter(pxy_conn_ctx_t *ctx, filter_list_t *list) prototcp_dsthost_filter(pxy_conn_ctx_t *ctx, filter_list_t *list)
{ {
if (ctx->dsthost_str) { if (ctx->dsthost_str) {
@ -550,23 +548,24 @@ prototcp_dsthost_filter(pxy_conn_ctx_t *ctx, filter_list_t *list)
int int
prototcp_apply_filter(pxy_conn_ctx_t *ctx) prototcp_apply_filter(pxy_conn_ctx_t *ctx)
{ {
enum filter_action action; unsigned char action;
if ((action = pxyconn_filter(ctx, prototcp_dsthost_filter))) { if ((action = pxyconn_filter(ctx, prototcp_dsthost_filter))) {
if (action == FILTER_ACTION_DIVERT) { if (action & FILTER_ACTION_DIVERT) {
ctx->divert = 1; ctx->divert = 1;
} }
else if (action == FILTER_ACTION_SPLIT) { else if (action & FILTER_ACTION_SPLIT) {
ctx->divert = 0; ctx->divert = 0;
} }
else if (action == FILTER_ACTION_PASS) { else if (action & FILTER_ACTION_PASS) {
protopassthrough_engage(ctx); protopassthrough_engage(ctx);
ctx->pass = 1; ctx->pass = 1;
return 1; return 1;
} }
else if (action == FILTER_ACTION_BLOCK) { else if (action & FILTER_ACTION_BLOCK) {
pxy_conn_term(ctx, 1); pxy_conn_term(ctx, 1);
return 1; return 1;
} }
//else { /* FILTER_ACTION_MATCH */ }
} }
return 0; return 0;
} }

@ -172,6 +172,16 @@ proxy_conn_ctx_new(evutil_socket_t fd,
ctx->spec = spec; ctx->spec = spec;
ctx->divert = spec->opts->divert; ctx->divert = spec->opts->divert;
// Enable all logging for conn if proxyspec does not have any filter
if (!spec->opts->filter) {
ctx->log_connect = 1;
ctx->log_content = 1;
ctx->log_pcap = 1;
#ifndef WITHOUT_MIRROR
ctx->log_mirror = 1;
#endif /* !WITHOUT_MIRROR */
}
ctx->proto = proxy_setup_proto(ctx); ctx->proto = proxy_setup_proto(ctx);
if (ctx->proto == PROTO_ERROR) { if (ctx->proto == PROTO_ERROR) {
free(ctx); free(ctx);

@ -319,7 +319,11 @@ pxy_conn_ctx_free(pxy_conn_ctx_t *ctx, int by_requestor)
log_finest("ENTER"); log_finest("ENTER");
if (WANT_CONTENT_LOG(ctx)) { if (WANT_CONTENT_LOG(ctx)) {
if (log_content_close(&ctx->logctx, by_requestor) == -1) { if (log_content_close(&ctx->logctx, by_requestor, ctx->log_content, ctx->log_pcap
#ifndef WITHOUT_MIRROR
, ctx->log_mirror
#endif /* !WITHOUT_MIRROR */
) == -1) {
log_err_level_printf(LOG_WARNING, "Content log close failed\n"); log_err_level_printf(LOG_WARNING, "Content log close failed\n");
} }
} }
@ -463,6 +467,9 @@ pxy_conn_term(pxy_conn_ctx_t *ctx, int by_requestor)
void void
pxy_log_connect_nonhttp(pxy_conn_ctx_t *ctx) pxy_log_connect_nonhttp(pxy_conn_ctx_t *ctx)
{ {
if (!ctx->log_connect)
return;
char *msg; char *msg;
#ifdef HAVE_LOCAL_PROCINFO #ifdef HAVE_LOCAL_PROCINFO
char *lpi = NULL; char *lpi = NULL;
@ -573,9 +580,17 @@ out:
return; return;
} }
int static int NONNULL(1)
pxy_log_content_inbuf(pxy_conn_ctx_t *ctx, struct evbuffer *inbuf, int req) pxy_log_content_inbuf(pxy_conn_ctx_t *ctx, struct evbuffer *inbuf, int req)
{ {
if (!ctx->log_content && !ctx->log_pcap
#ifndef WITHOUT_MIRROR
&& !ctx->log_mirror
#endif /* !WITHOUT_MIRROR */
) {
return 0;
}
size_t sz = evbuffer_get_length(inbuf); size_t sz = evbuffer_get_length(inbuf);
unsigned char *buf = malloc(sz); unsigned char *buf = malloc(sz);
if (!buf) { if (!buf) {
@ -594,7 +609,11 @@ pxy_log_content_inbuf(pxy_conn_ctx_t *ctx, struct evbuffer *inbuf, int req)
} }
memcpy(lb->buf, buf, lb->sz); memcpy(lb->buf, buf, lb->sz);
free(buf); free(buf);
if (log_content_submit(&ctx->logctx, lb, req) == -1) { if (log_content_submit(&ctx->logctx, lb, req, ctx->log_content, ctx->log_pcap
#ifndef WITHOUT_MIRROR
, ctx->log_mirror
#endif /* !WITHOUT_MIRROR */
) == -1) {
logbuf_free(lb); logbuf_free(lb);
log_err_level_printf(LOG_WARNING, "Content log submission failed\n"); log_err_level_printf(LOG_WARNING, "Content log submission failed\n");
return -1; return -1;
@ -1511,7 +1530,7 @@ pxy_bev_writecb_child(struct bufferevent *bev, void *arg)
} }
} }
int static int NONNULL(1,3)
pxy_bev_eventcb_postexec_logging_and_stats(struct bufferevent *bev, short events, pxy_conn_ctx_t *ctx) pxy_bev_eventcb_postexec_logging_and_stats(struct bufferevent *bev, short events, pxy_conn_ctx_t *ctx)
{ {
if (ctx->term || ctx->enomem) { if (ctx->term || ctx->enomem) {
@ -1973,35 +1992,61 @@ pxy_userauth(pxy_conn_ctx_t *ctx)
} }
#endif /* !WITHOUT_USERAUTH */ #endif /* !WITHOUT_USERAUTH */
enum filter_action unsigned char
pxyconn_set_filter_action(pxy_conn_ctx_t *ctx, filter_site_t *site) pxyconn_set_filter_action(pxy_conn_ctx_t *ctx, filter_site_t *site)
{ {
unsigned char action = FILTER_ACTION_NONE;
if (site->divert) { if (site->divert) {
log_err_level_printf(LOG_INFO, "Site filter divert action for %s\n", site->site); log_err_level_printf(LOG_INFO, "Site filter divert action for %s\n", site->site);
return FILTER_ACTION_DIVERT; action = FILTER_ACTION_DIVERT;
} }
else if (site->split) { else if (site->split) {
log_err_level_printf(LOG_INFO, "Site filter split action for %s\n", site->site); log_err_level_printf(LOG_INFO, "Site filter split action for %s\n", site->site);
return FILTER_ACTION_SPLIT; action = FILTER_ACTION_SPLIT;
} }
else if (site->pass) { else if (site->pass) {
// Ignore pass action if already in passthrough mode // Ignore pass action if already in passthrough mode
if (!ctx->pass) { if (!ctx->pass) {
log_err_level_printf(LOG_INFO, "Site filter pass action for %s\n", site->site); log_err_level_printf(LOG_INFO, "Site filter pass action for %s\n", site->site);
return FILTER_ACTION_PASS; action = FILTER_ACTION_PASS;
} }
} }
else if (site->block) { else if (site->block) {
log_err_level_printf(LOG_INFO, "Site filter block action for %s\n", site->site); log_err_level_printf(LOG_INFO, "Site filter block action for %s\n", site->site);
return FILTER_ACTION_BLOCK; action = FILTER_ACTION_BLOCK;
}
else if (site->match) {
log_err_level_printf(LOG_INFO, "Site filter match action for %s\n", site->site);
action = FILTER_ACTION_MATCH;
}
// Multiple log actions can be defined, hence no 'else'
// Log actions can only enable logging not disable, hence set to 1
if (site->log_connect) {
log_err_level_printf(LOG_INFO, "Site filter connect log for %s\n", site->site);
ctx->log_connect = 1;
} }
return FILTER_ACTION_MATCH; if (site->log_content) {
log_err_level_printf(LOG_INFO, "Site filter content log for %s\n", site->site);
ctx->log_content = 1;
}
if (site->log_pcap) {
log_err_level_printf(LOG_INFO, "Site filter pcap log for %s\n", site->site);
ctx->log_pcap = 1;
}
#ifndef WITHOUT_MIRROR
if (site->log_mirror) {
log_err_level_printf(LOG_INFO, "Site filter mirror log for %s\n", site->site);
ctx->log_mirror = 1;
}
#endif /* !WITHOUT_MIRROR */
return action;
} }
enum filter_action unsigned char
pxyconn_filter(pxy_conn_ctx_t *ctx, proto_filter_func_t filtercb) pxyconn_filter(pxy_conn_ctx_t *ctx, proto_filter_func_t filtercb)
{ {
enum filter_action action = FILTER_ACTION_NONE; unsigned char action = FILTER_ACTION_NONE;
filter_t *filter = ctx->spec->opts->filter; filter_t *filter = ctx->spec->opts->filter;
if (filter) { if (filter) {

@ -78,7 +78,7 @@ typedef void (*proto_classify_user_func_t)(pxy_conn_ctx_t *);
typedef void (*child_connect_func_t)(pxy_conn_child_ctx_t *); typedef void (*child_connect_func_t)(pxy_conn_child_ctx_t *);
typedef void (*child_proto_free_func_t)(pxy_conn_child_ctx_t *); typedef void (*child_proto_free_func_t)(pxy_conn_child_ctx_t *);
typedef enum filter_action (*proto_filter_func_t)(pxy_conn_ctx_t *, filter_list_t *); typedef unsigned char (*proto_filter_func_t)(pxy_conn_ctx_t *, filter_list_t *);
/* /*
* Proxy connection context state, describes a proxy connection * Proxy connection context state, describes a proxy connection
@ -320,6 +320,17 @@ struct pxy_conn_ctx {
unsigned int divert : 1; /* 1 to divert conn */ unsigned int divert : 1; /* 1 to divert conn */
unsigned int pass : 1; /* 1 to pass conn through */ unsigned int pass : 1; /* 1 to pass conn through */
// Enable logging of conn for specific logger types
// Global logging options should be configured for these to write logs
// Default to all logging if no filter rules defined in proxyspec
// Otherwise, logging is disabled, so filter rules should enable each log action specifically
unsigned int log_connect : 1;
unsigned int log_content : 1;
unsigned int log_pcap : 1;
#ifndef WITHOUT_MIRROR
unsigned int log_mirror : 1;
#endif /* !WITHOUT_MIRROR */
#ifdef HAVE_LOCAL_PROCINFO #ifdef HAVE_LOCAL_PROCINFO
/* local process information */ /* local process information */
pxy_conn_lproc_desc_t lproc; pxy_conn_lproc_desc_t lproc;
@ -372,7 +383,6 @@ int pxy_prepare_logging_local_procinfo(pxy_conn_ctx_t *) NONNULL(1);
void pxy_log_connect_src(pxy_conn_ctx_t *) NONNULL(1); void pxy_log_connect_src(pxy_conn_ctx_t *) NONNULL(1);
void pxy_log_connect_srvdst(pxy_conn_ctx_t *) NONNULL(1); void pxy_log_connect_srvdst(pxy_conn_ctx_t *) NONNULL(1);
int pxy_log_content_inbuf(pxy_conn_ctx_t *, struct evbuffer *, int) NONNULL(1);
void pxy_log_connect_nonhttp(pxy_conn_ctx_t *) NONNULL(1); void pxy_log_connect_nonhttp(pxy_conn_ctx_t *) NONNULL(1);
void pxy_log_dbg_evbuf_info(pxy_conn_ctx_t *, pxy_conn_desc_t *, pxy_conn_desc_t *) NONNULL(1,2,3); void pxy_log_dbg_evbuf_info(pxy_conn_ctx_t *, pxy_conn_desc_t *, pxy_conn_desc_t *) NONNULL(1,2,3);
@ -403,7 +413,6 @@ void pxy_conn_free_children(pxy_conn_ctx_t *) NONNULL(1);
int pxy_setup_child_listener(pxy_conn_ctx_t *) NONNULL(1); int pxy_setup_child_listener(pxy_conn_ctx_t *) NONNULL(1);
int pxy_bev_readcb_preexec_logging_and_stats(struct bufferevent *, pxy_conn_ctx_t *) NONNULL(1,2); int pxy_bev_readcb_preexec_logging_and_stats(struct bufferevent *, pxy_conn_ctx_t *) NONNULL(1,2);
int pxy_bev_eventcb_postexec_logging_and_stats(struct bufferevent *, short , pxy_conn_ctx_t *) NONNULL(1,3);
void pxy_bev_readcb(struct bufferevent *, void *); void pxy_bev_readcb(struct bufferevent *, void *);
void pxy_bev_writecb(struct bufferevent *, void *); void pxy_bev_writecb(struct bufferevent *, void *);
@ -426,8 +435,8 @@ int pxy_is_listuser(userlist_t *, const char *
void pxy_classify_user(pxy_conn_ctx_t *) NONNULL(1); void pxy_classify_user(pxy_conn_ctx_t *) NONNULL(1);
void pxy_userauth(pxy_conn_ctx_t *) NONNULL(1); void pxy_userauth(pxy_conn_ctx_t *) NONNULL(1);
#endif /* !WITHOUT_USERAUTH */ #endif /* !WITHOUT_USERAUTH */
enum filter_action pxyconn_set_filter_action(pxy_conn_ctx_t *, filter_site_t *) NONNULL(1,2); unsigned char pxyconn_set_filter_action(pxy_conn_ctx_t *, filter_site_t *) NONNULL(1,2);
enum filter_action pxyconn_filter(pxy_conn_ctx_t *, proto_filter_func_t) NONNULL(1); unsigned char pxyconn_filter(pxy_conn_ctx_t *, proto_filter_func_t) NONNULL(1);
void pxy_conn_setup(evutil_socket_t, struct sockaddr *, int, void pxy_conn_setup(evutil_socket_t, struct sockaddr *, int,
pxy_thrmgr_ctx_t *, proxyspec_t *, global_t *, pxy_thrmgr_ctx_t *, proxyspec_t *, global_t *,
evutil_socket_t) evutil_socket_t)

@ -307,7 +307,7 @@ are diverted to listening programs.
These user control lists can be defined globally or per-proxyspec. These user control lists can be defined globally or per-proxyspec.
.SH Filtering rules .SH Filtering rules
.LP .LP
SSLproxy can divert, split, pass, or block connections based on filtering SSLproxy can divert, split, pass, block, or match connections based on filtering
rules. Filtering rules can be defined globally or per-proxyspec. rules. Filtering rules can be defined globally or per-proxyspec.
.LP .LP
- Divert action diverts packets to listening program, allowing SSL inspection - Divert action diverts packets to listening program, allowing SSL inspection
@ -318,10 +318,12 @@ allowing content logging of packets
- Pass action passes the connection through by engaging passthrough mode, - Pass action passes the connection through by engaging passthrough mode,
effectively disabling SSL inspection and content logging of packets effectively disabling SSL inspection and content logging of packets
- Block action terminates the connection - Block action terminates the connection
- Match action is used to specify log actions for matching connections without
changing their filter actions
.LP .LP
The syntax of filtering rules is as follows: The syntax of filtering rules is as follows:
(Divert|Split|Pass|Block) (Divert|Split|Pass|Block|Match)
([from ( ([from (
user (username|*) [desc keyword]| user (username|*) [desc keyword]|
ip (clientaddr|*)| ip (clientaddr|*)|
@ -333,6 +335,7 @@ The syntax of filtering rules is as follows:
uri (uri[*]|*)| uri (uri[*]|*)|
ip (serveraddr|*)| ip (serveraddr|*)|
*)] *)]
[log ([connect] [content] [pcap] [mirror]|*)]
|*) |*)
.LP .LP
The definition of which connections the rule action will be applied to is The definition of which connections the rule action will be applied to is
@ -348,18 +351,40 @@ cn, and HTTP type of rules use host and uri site fields.
- The proxyspec handling the connection defines the protocol filter for the - The proxyspec handling the connection defines the protocol filter for the
connection. connection.
.LP .LP
If and how a connection should be logged is specified using the log part of
filtering rules. connect enables logging connection information to connect
log file, content enables logging packet contents to content log file,
pcap enables writing packets to pcap files, and mirror enables mirroring
packets to mirror interfaces or targets.
.LP
For example, if the following rules are defined in a structured HTTPS proxyspec, For example, if the following rules are defined in a structured HTTPS proxyspec,
.LP .LP
Split from user soner desc notebook to sni example.com Split from user soner desc notebook to sni example.com log content
Pass from user soner desc android to cn .fbcdn.net* Pass from user soner desc android to cn .fbcdn.net*
.LP .LP
The first filtering rule above splits but does not divert HTTPS connections The first filtering rule above splits but does not divert HTTPS connections
from the user soner who has logged in with a description containing the keyword from the user soner who has logged in with a description containing the keyword
notebook to SSL sites with the SNI of example.com. The second rule passes notebook to SSL sites with the SNI of example.com. Also, the rule specifies
through HTTPS connections from the user soner who has logged in with a that the packet contents of the matching connection be written to content log
description containing the keyword android to SSL sites with the Common Names file configured globally.
containing the substring .fbcdn.net anywhere in it (notice the asterisk at the .LP
end). Note that the second example is a filtering rule you can use to resolve The second rule passes through HTTPS connections from the user soner who has
logged in with a description containing the keyword android to SSL sites with
the Common Names containing the substring .fbcdn.net anywhere in it (notice
the asterisk at the end). Since connection contents cannot be written to log
files in passthrough mode, the rule does not specify any log action.
.LP
The default filter action is Divert. So, if those are the only filtering rules
in that proxyspec, the other connections are diverted to the listening program
specified in that proxyspec, without writing any logs.
.LP
If you want to enable, say, connect logging for the other connections handled
by that proxyspec, without changing their default Divert filter action, you
can add a third filtering rule to that proxyspec:
.LP
Match * log connect
.LP
Note that the second example above is a filtering rule you can use to resolve
one of the certificate issues preventing the Facebook application on Android one of the certificate issues preventing the Facebook application on Android
smartphones to connect to the Internet behind sslproxy. smartphones to connect to the Internet behind sslproxy.
.LP .LP
@ -384,10 +409,21 @@ In terms of possible filter actions,
- HTTP filter rules can take the block action, but not divert, split, or pass - HTTP filter rules can take the block action, but not divert, split, or pass
actions. actions.
.LP .LP
Log actions do not configure any loggers. Global content loggers for
respective log actions should have been configured for those log actions to
have any effect.
.LP
If no filter rules are defined for a proxyspec, all logging actions for that
proxyspec are enabled. Otherwise, all log actions are disabled, and filtering
rules should enable them specifically. Note that if logging is disabled by
filtering rules, the loggers create the log files, but they remain empty.
.LP
You can append an asterisk * to site field of filtering rules for substring You can append an asterisk * to site field of filtering rules for substring
matching. Otherwise, the filter searches for an exact match with the site field matching. Otherwise, the filter searches for an exact match with the site field
in the rule. in the rule.
.LP .LP
The order of from, to, and log parts is not important.
.LP
If the UserAuth option is disabled, only client IP addresses can be used in If the UserAuth option is disabled, only client IP addresses can be used in
the from part of filtering rules. the from part of filtering rules.
.SH Excluding sites from SSL inspection .SH Excluding sites from SSL inspection
@ -410,7 +446,7 @@ on each line. PassSite rules can search for exact or substring matches.
Logging options include traditional SSLproxy connect and content log files as Logging options include traditional SSLproxy connect and content log files as
well as PCAP files and mirroring decrypted traffic to a network interface. well as PCAP files and mirroring decrypted traffic to a network interface.
Additionally, certificates, master secrets and local process information can be Additionally, certificates, master secrets and local process information can be
logged. logged. Filtering rules can selectively modify connection logging.
.SH OPTIONS .SH OPTIONS
.TP .TP
.B \-a \fIpemfile\fP .B \-a \fIpemfile\fP
@ -591,6 +627,9 @@ from the client, since the connection from the client cannot easily be retried.
Specifically, \fB-P\fP does not currently work for clients that do not accept Specifically, \fB-P\fP does not currently work for clients that do not accept
forged certificates. forged certificates.
.TP .TP
.B \-Q
Quit after loading and testing configuration.
.TP
.B \-q \fIcrlurl\fP .B \-q \fIcrlurl\fP
Set CRL distribution point (CDP) \fIcrlurl\fP on forged leaf certificates. Set CRL distribution point (CDP) \fIcrlurl\fP on forged leaf certificates.
Some clients, such as some .NET applications, reject certificates that do not Some clients, such as some .NET applications, reject certificates that do not

@ -274,7 +274,7 @@ PassUsers admin
# Filter rules # Filter rules
# #
# (Divert|Split|Pass|Block) # (Divert|Split|Pass|Block|Match)
# ([from ( # ([from (
# user (username|*) [desc keyword]| # user (username|*) [desc keyword]|
# ip (clientaddr|*)| # ip (clientaddr|*)|
@ -286,6 +286,7 @@ PassUsers admin
# uri (uri[*]|*)| # uri (uri[*]|*)|
# ip (serveraddr|*)| # ip (serveraddr|*)|
# *)] # *)]
# [log ([connect] [content] [pcap] [mirror]|*)]
# |*) # |*)
# #
# Filter rules using host or uri can only take block action, # Filter rules using host or uri can only take block action,
@ -295,7 +296,7 @@ PassUsers admin
# Pass to cn example.com # Pass to cn example.com
# #
#Divert from ip 192.168.0.1 to sni example.com #Divert from ip 192.168.0.1 to sni example.com
#Split from user soner to sni example.com #Split from user soner to sni example.com log content
#Pass from user * desc android to sni *.google.com #Pass from user * desc android to sni *.google.com
#Block from user soner desc android to cn .fbcdn.net* #Block from user soner desc android to cn .fbcdn.net*

@ -332,7 +332,7 @@ Block filtering rule terminates the connection.
The syntax of filtering rules is as follows: The syntax of filtering rules is as follows:
.br .br
(Divert|Split|Pass|Block) (Divert|Split|Pass|Block|Match)
([from ( ([from (
user (username|*) [desc keyword]| user (username|*) [desc keyword]|
ip (clientaddr|*)| ip (clientaddr|*)|
@ -344,6 +344,7 @@ The syntax of filtering rules is as follows:
uri (uri[*]|*)| uri (uri[*]|*)|
ip (serveraddr|*)| ip (serveraddr|*)|
*)] *)]
[log ([connect] [content] [pcap] [mirror]|*)]
|*) |*)
.br .br

@ -700,9 +700,9 @@ START_TEST(opts_set_passsite_01)
ps = filter_rule_str(opts->filter_rules); ps = filter_rule_str(opts->filter_rules);
#ifndef WITHOUT_USERAUTH #ifndef WITHOUT_USERAUTH
fail_unless(!strcmp(ps, "filter rule 0: site=example.com, exact, ip=, user=, keyword=, all=conns||, action=||pass|, apply to=|sni|cn||"), "failed parsing passite example.com: %s", ps); fail_unless(!strcmp(ps, "filter rule 0: site=example.com, exact, ip=, user=, keyword=, all=conns||, action=||pass||, log=|||, apply to=|sni|cn||"), "failed parsing passite example.com: %s", ps);
#else /* WITHOUT_USERAUTH */ #else /* WITHOUT_USERAUTH */
fail_unless(!strcmp(ps, "filter rule 0: site=example.com, exact, ip=, all=conns|, action=||pass|, apply to=|sni|cn||"), "failed parsing passite example.com: %s", ps); fail_unless(!strcmp(ps, "filter rule 0: site=example.com, exact, ip=, all=conns|, action=||pass||, log=|||, apply to=|sni|cn||"), "failed parsing passite example.com: %s", ps);
#endif /* WITHOUT_USERAUTH */ #endif /* WITHOUT_USERAUTH */
free(ps); free(ps);
@ -730,9 +730,9 @@ START_TEST(opts_set_passsite_02)
ps = filter_rule_str(opts->filter_rules); ps = filter_rule_str(opts->filter_rules);
#ifndef WITHOUT_USERAUTH #ifndef WITHOUT_USERAUTH
fail_unless(!strcmp(ps, "filter rule 0: site=example.com, exact, ip=192.168.0.1, user=, keyword=, all=||, action=||pass|, apply to=|sni|cn||"), "failed parsing passite example.com 192.168.0.1: %s", ps); fail_unless(!strcmp(ps, "filter rule 0: site=example.com, exact, ip=192.168.0.1, user=, keyword=, all=||, action=||pass||, log=|||, apply to=|sni|cn||"), "failed parsing passite example.com 192.168.0.1: %s", ps);
#else /* WITHOUT_USERAUTH */ #else /* WITHOUT_USERAUTH */
fail_unless(!strcmp(ps, "filter rule 0: site=example.com, exact, ip=192.168.0.1, all=|, action=||pass|, apply to=|sni|cn||"), "failed parsing passite example.com 192.168.0.1: %s", ps); fail_unless(!strcmp(ps, "filter rule 0: site=example.com, exact, ip=192.168.0.1, all=|, action=||pass||, log=|||, apply to=|sni|cn||"), "failed parsing passite example.com 192.168.0.1: %s", ps);
#endif /* !WITHOUT_USERAUTH */ #endif /* !WITHOUT_USERAUTH */
free(ps); free(ps);
@ -760,7 +760,7 @@ START_TEST(opts_set_passsite_03)
fail_unless(!opts->filter_rules->next, "next set"); fail_unless(!opts->filter_rules->next, "next set");
ps = filter_rule_str(opts->filter_rules); ps = filter_rule_str(opts->filter_rules);
fail_unless(!strcmp(ps, "filter rule 0: site=example.com, exact, ip=, user=root, keyword=, all=||, action=||pass|, apply to=|sni|cn||"), "failed parsing passite example.com root: %s", ps); fail_unless(!strcmp(ps, "filter rule 0: site=example.com, exact, ip=, user=root, keyword=, all=||, action=||pass||, log=|||, apply to=|sni|cn||"), "failed parsing passite example.com root: %s", ps);
free(ps); free(ps);
opts_free(opts); opts_free(opts);
@ -787,7 +787,7 @@ START_TEST(opts_set_passsite_04)
fail_unless(!opts->filter_rules->next, "next set"); fail_unless(!opts->filter_rules->next, "next set");
ps = filter_rule_str(opts->filter_rules); ps = filter_rule_str(opts->filter_rules);
fail_unless(!strcmp(ps, "filter rule 0: site=*.google.com, exact, ip=, user=, keyword=android, all=|users|, action=||pass|, apply to=|sni|cn||"), "failed parsing passite *.google.com * android: %s", ps); fail_unless(!strcmp(ps, "filter rule 0: site=*.google.com, exact, ip=, user=, keyword=android, all=|users|, action=||pass||, log=|||, apply to=|sni|cn||"), "failed parsing passite *.google.com * android: %s", ps);
free(ps); free(ps);
opts_free(opts); opts_free(opts);
@ -842,17 +842,17 @@ START_TEST(opts_set_passsite_05)
fail_unless(opts->filter_rules->next->next->next, "next->next->next not set"); fail_unless(opts->filter_rules->next->next->next, "next->next->next not set");
fail_unless(opts->filter_rules->next->next->next->next, "next->next->next->next not set"); fail_unless(opts->filter_rules->next->next->next->next, "next->next->next->next not set");
fail_unless(!opts->filter_rules->next->next->next->next->next, "next->next->next->next->next set"); fail_unless(!opts->filter_rules->next->next->next->next->next, "next->next->next->next->next set");
fail_unless(!strcmp(ps, "filter rule 0: site=*.google.com, exact, ip=, user=, keyword=android, all=|users|, action=||pass|, apply to=|sni|cn||\n" fail_unless(!strcmp(ps, "filter rule 0: site=*.google.com, exact, ip=, user=, keyword=android, all=|users|, action=||pass||, log=|||, apply to=|sni|cn||\n"
"filter rule 1: site=example.com, exact, ip=, user=root, keyword=, all=||, action=||pass|, apply to=|sni|cn||\n" "filter rule 1: site=example.com, exact, ip=, user=root, keyword=, all=||, action=||pass||, log=|||, apply to=|sni|cn||\n"
"filter rule 2: site=example.com, exact, ip=192.168.0.1, user=, keyword=, all=||, action=||pass|, apply to=|sni|cn||\n" "filter rule 2: site=example.com, exact, ip=192.168.0.1, user=, keyword=, all=||, action=||pass||, log=|||, apply to=|sni|cn||\n"
"filter rule 3: site=example.com, exact, ip=, user=, keyword=, all=|users|, action=||pass|, apply to=|sni|cn||\n" "filter rule 3: site=example.com, exact, ip=, user=, keyword=, all=|users|, action=||pass||, log=|||, apply to=|sni|cn||\n"
"filter rule 4: site=example.com, exact, ip=, user=, keyword=, all=conns||, action=||pass|, apply to=|sni|cn||"), "filter rule 4: site=example.com, exact, ip=, user=, keyword=, all=conns||, action=||pass||, log=|||, apply to=|sni|cn||"),
"failed parsing multiple passites: %s", ps); "failed parsing multiple passites: %s", ps);
#else /* WITHOUT_USERAUTH */ #else /* WITHOUT_USERAUTH */
fail_unless(!opts->filter_rules->next->next->next, "next->next->next set"); fail_unless(!opts->filter_rules->next->next->next, "next->next->next set");
fail_unless(!strcmp(ps, "filter rule 0: site=example.com, exact, ip=192.168.0.1, all=|, action=||pass|, apply to=|sni|cn||\n" fail_unless(!strcmp(ps, "filter rule 0: site=example.com, exact, ip=192.168.0.1, all=|, action=||pass||, log=|||, apply to=|sni|cn||\n"
"filter rule 1: site=example.com, exact, ip=, all=conns|, action=||pass|, apply to=|sni|cn||\n" "filter rule 1: site=example.com, exact, ip=, all=conns|, action=||pass||, log=|||, apply to=|sni|cn||\n"
"filter rule 2: site=example.com, exact, ip=, all=conns|, action=||pass|, apply to=|sni|cn||"), "filter rule 2: site=example.com, exact, ip=, all=conns|, action=||pass||, log=|||, apply to=|sni|cn||"),
"failed parsing multiple passites: %s", ps); "failed parsing multiple passites: %s", ps);
#endif /* WITHOUT_USERAUTH */ #endif /* WITHOUT_USERAUTH */
free(ps); free(ps);

Loading…
Cancel
Save