From 0f5ed122fbcf507205bafd59e9a6e7135aa30640 Mon Sep 17 00:00:00 2001 From: Soner Tari Date: Sun, 19 Sep 2021 22:57:58 +0300 Subject: [PATCH] Add Define option for macro definitions and macro expansion to filtering rules The new Define option can be used for defining macros to be used in filtering rules. Macro names must begin with a '$' char. Macro values must be separated with spaces. Macros are expanded by rewriting the rule with the values of macro. PassSite rules do not support macros (the PassSite option will be deprecated in favor of filtering rules in the future). --- NEWS.md | 18 +- README.md | 37 +-- src/opts.c | 533 ++++++++++++++++++++++++++++++++++++-------- src/opts.h | 12 + src/sslproxy.1 | 38 ++-- src/sslproxy.conf | 38 ++-- src/sslproxy.conf.5 | 36 +-- 7 files changed, 547 insertions(+), 165 deletions(-) diff --git a/NEWS.md b/NEWS.md index 10aa808..4e29e41 100644 --- a/NEWS.md +++ b/NEWS.md @@ -5,19 +5,21 @@ (Divert|Split|Pass|Block|Match) ([from ( - user (username|*) [desc keyword]| - ip (clientaddr|*)| + user (username|$macro|*) [desc keyword]| + ip (clientaddr|$macro|*)| *)] [to ( - sni (servername[*]|*)| - cn (commonname[*]|*)| - host (host[*]|*)| - uri (uri[*]|*)| - ip (serveraddr|*)| + sni (servername[*]|$macro|*)| + cn (commonname[*]|$macro|*)| + host (host[*]|$macro|*)| + uri (uri[*]|$macro|*)| + ip (serveraddr|$macro|*)| *)] - [log ([[!]connect] [[!]master] [[!]cert] [[!]content] [[!]pcap] [[!]mirror]|*)] + [log ([[!]connect] [[!]master] [[!]cert] + [[!]content] [[!]pcap] [[!]mirror] [$macro]|*|!*)] |*) +- Add Define config option for defining macros to be used in filtering rules. - Add -Q test config option. diff --git a/README.md b/README.md index d02a46d..aeab15c 100644 --- a/README.md +++ b/README.md @@ -295,17 +295,18 @@ The syntax of filtering rules is as follows: (Divert|Split|Pass|Block|Match) ([from ( - user (username|*) [desc keyword]| - ip (clientaddr|*)| + user (username|$macro|*) [desc keyword]| + ip (clientaddr|$macro|*)| *)] [to ( - sni (servername[*]|*)| - cn (commonname[*]|*)| - host (host[*]|*)| - uri (uri[*]|*)| - ip (serveraddr|*)| + sni (servername[*]|$macro|*)| + cn (commonname[*]|$macro|*)| + host (host[*]|$macro|*)| + uri (uri[*]|$macro|*)| + ip (serveraddr|$macro|*)| *)] - [log ([[!]connect] [[!]master] [[!]cert] [[!]content] [[!]pcap] [[!]mirror]|*)] + [log ([[!]connect] [[!]master] [[!]cert] + [[!]content] [[!]pcap] [[!]mirror] [$macro]|*|!*)] |*) The definition of which connections the rule action will be applied to is @@ -316,8 +317,10 @@ that the rule is defined for. user or description keyword, or `*` for all. - The `to` part defines destination filter based on server IP address, SNI or Common Names of SSL connections, Host or URI fields in HTTP Request headers, or -`*` for all. Dst Host type of rules use `ip`, SSL type of rules use `sni` and -`cn`, and HTTP type of rules use `host` and `uri` site fields. +`*` for all. + + Dst Host type of rules use `ip` site field + + SSL type of rules use `sni` and `cn` site fields + + HTTP type of rules use `host` and `uri` site fields - The proxyspec handling the connection defines the protocol filter for the connection. @@ -394,12 +397,16 @@ If no filtering rules are defined for a proxyspec, all log actions for that proxyspec are enabled. Otherwise, all log actions are disabled, and filtering rules should enable them specifically. -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 -in the rule. +Macro expansion is supported. The `Define` option can be used for defining +macros to be used in filtering rules. Macro names must start with a `$` char. +The macro name must be followed by words separated with spaces. -The order of from, to, and log parts is not important. The order of log -actions is not important. +You can append an asterisk `*` to the site field of filtering rules for +substring matching. Otherwise, the filter searches for an exact match with the +site field in the rule. + +The order of filtering rules is important. The order of from, to, and log +parts is not important. The order of log actions is not important. If the UserAuth option is disabled, only client IP addresses can be used in the from part of filtering rules. diff --git a/src/opts.c b/src/opts.c index 3ff6a16..07ac789 100644 --- a/src/opts.c +++ b/src/opts.c @@ -158,6 +158,31 @@ free_userlist(userlist_t *ul) } #endif /* !WITHOUT_USERAUTH */ +static void +opts_free_values(value_t *value) +{ + while (value) { + value_t *next = value->next; + free(value->value); + free(value); + value = next; + } +} + +static void +opts_free_macros(opts_t *opts) +{ + macro_t *macro = opts->macro; + while (macro) { + macro_t *next = macro->next; + free(macro->name); + opts_free_values(macro->value); + free(macro); + macro = next; + } + opts->macro = NULL; +} + void opts_free_filter_rules(opts_t *opts) { @@ -285,6 +310,8 @@ opts_free(opts_t *opts) free_userlist(opts->passusers); #endif /* !WITHOUT_USERAUTH */ + opts_free_macros(opts); + // No need to call opts_free_filter_rules() here, filter rules are freed during startup opts_free_filter_rules(opts); opts_free_filter(opts); @@ -632,7 +659,7 @@ opts_proto_dbg_dump(opts_t *opts) } static void -opts_append_to_list(filter_rule_t **list, filter_rule_t *rule) +opts_append_to_filter_rules(filter_rule_t **list, filter_rule_t *rule) { filter_rule_t *l = *list; while (l) { @@ -773,6 +800,32 @@ clone_global_opts(global_t *global, const char *argv0, tmp_global_opts_t *tmp_gl } #endif /* !WITHOUT_USERAUTH */ + macro_t *macro = global->opts->macro; + while (macro) { + macro_t *m = malloc(sizeof(macro_t)); + memset(m, 0, sizeof(macro_t)); + + m->name = strdup(macro->name); + + value_t *value = macro->value; + while (value) { + value_t *v = malloc(sizeof(value_t)); + memset(v, 0, sizeof(value_t)); + + v->value = strdup(value->value); + + v->next = m->value; + m->value = v; + + value = value->next; + } + + m->next = opts->macro; + opts->macro = m; + + macro = macro->next; + } + filter_rule_t *rule = global->opts->filter_rules; while (rule) { filter_rule_t *fr = malloc(sizeof(filter_rule_t)); @@ -818,7 +871,7 @@ clone_global_opts(global_t *global, const char *argv0, tmp_global_opts_t *tmp_gl fr->precedence = rule->precedence; - opts_append_to_list(&opts->filter_rules, fr); + opts_append_to_filter_rules(&opts->filter_rules, fr); rule = rule->next; } @@ -1135,6 +1188,61 @@ proxyspec_parse(int *argc, char **argv[], const char *natengine, global_t *globa set_divert(spec, tmp_global_opts->split); } +static char * +value_str(value_t *value) +{ + char *s = NULL; + + while (value) { + char *p; + if (asprintf(&p, "%s%s%s", STRORNONE(s), s ? ", " : "", value->value) < 0) { + goto err; + } + if (s) + free(s); + s = p; + value = value->next; + } + goto out; +err: + if (s) { + free(s); + s = NULL; + } +out: + return s; +} + +static char * +macro_str(macro_t *macro) +{ + char *s = NULL; + + if (!macro) { + s = strdup(""); + goto out; + } + + while (macro) { + char *p; + if (asprintf(&p, "%s%smacro %s = %s", STRORNONE(s), s ? "\n" : "", macro->name, value_str(macro->value)) < 0) { + goto err; + } + if (s) + free(s); + s = p; + macro = macro->next; + } + goto out; +err: + if (s) { + free(s); + s = NULL; + } +out: + return s; +} + char * filter_rule_str(filter_rule_t *rule) { @@ -1551,6 +1659,7 @@ opts_str(opts_t *opts) { char *s = NULL; char *proto_dump = NULL; + char *ms = NULL; char *frs = NULL; char *fs = NULL; @@ -1567,6 +1676,10 @@ opts_str(opts_t *opts) goto out; #endif /* !WITHOUT_USERAUTH */ + ms = macro_str(opts->macro); + if (!ms) + goto out; + frs = filter_rule_str(opts->filter_rules); if (!frs) goto out; @@ -1606,7 +1719,7 @@ opts_str(opts_t *opts) #ifndef WITHOUT_USERAUTH "%s|%s|%d|%s|%s" #endif /* !WITHOUT_USERAUTH */ - "%s|%d\n%s%s%s%s%s", + "%s|%d\n%s%s%s%s%s%s%s", (opts->divert ? "divert" : "split"), (!opts->sslcomp ? "|no sslcomp" : ""), #ifdef HAVE_SSLV2 @@ -1649,6 +1762,7 @@ opts_str(opts_t *opts) (opts->validate_proto ? "|validate_proto" : ""), opts->max_http_header_size, proto_dump, + strlen(ms) ? "\n" : "", ms, strlen(frs) ? "\n" : "", frs, strlen(fs) ? "\n" : "", fs) < 0) { s = NULL; @@ -1660,6 +1774,8 @@ out: if (pu) free(pu); #endif /* !WITHOUT_USERAUTH */ + if (ms) + free(ms); if (frs) free(frs); if (fs) @@ -2454,7 +2570,7 @@ opts_set_passsite(opts_t *opts, char *value, int line_num) rule->cn = 1; rule->pass = 1; - opts_append_to_list(&opts->filter_rules, rule); + opts_append_to_filter_rules(&opts->filter_rules, rule); #ifdef DEBUG_OPTS log_dbg_printf("Filter rule: %s, %s, %s" @@ -2491,8 +2607,79 @@ opts_set_passsite(opts_t *opts, char *value, int line_num) #endif /* DEBUG_OPTS */ } +static macro_t * +opts_find_macro(macro_t *macro, char *name) +{ + while (macro) { + if (equal(macro->name, name)) { + return macro; + } + macro = macro->next; + } + return NULL; +} + static void -opts_set_site(filter_rule_t *rule, char *site, int line_num) +opts_set_macro(opts_t *opts, char *value, int line_num) +{ +#define MAX_MACRO_TOKENS 50 + + // $name value1 [value2 [value3] ...] + char *argv[sizeof(char *) * MAX_MACRO_TOKENS]; + int argc = 0; + char *p, *last = NULL; + + for ((p = strtok_r(value, " ", &last)); + p; + (p = strtok_r(NULL, " ", &last))) { + if (argc < MAX_MACRO_TOKENS) { + argv[argc++] = p; + } else { + fprintf(stderr, "Too many arguments in macro definition on line %d\n", line_num); + exit(EXIT_FAILURE); + } + } + + if (argc < 2) { + fprintf(stderr, "Macro definition requires at least two arguments on line %d\n", line_num); + exit(EXIT_FAILURE); + } + + if (argv[0][0] != '$') { + fprintf(stderr, "Macro name should start with '$' on line %d\n", line_num); + exit(EXIT_FAILURE); + } + + if (opts_find_macro(opts->macro, argv[0])) { + fprintf(stderr, "Macro name '%s' already exists on line %d\n", argv[0], line_num); + exit(EXIT_FAILURE); + } + + macro_t *macro = malloc(sizeof(macro_t)); + memset(macro, 0, sizeof(macro_t)); + + macro->name = strdup(argv[0]); + + int i = 1; + while (i < argc) { + value_t *v = malloc(sizeof(value_t)); + memset(v, 0, sizeof(value_t)); + + v->value = strdup(argv[i++]); + v->next = macro->value; + macro->value = v; + } + + macro->next = opts->macro; + opts->macro = macro; + +#ifdef DEBUG_OPTS + log_dbg_printf("Macro: %s = %s\n", macro->name, value_str(macro->value)); +#endif /* DEBUG_OPTS */ +} + +static void +opts_set_site(filter_rule_t *rule, const char *site, int line_num) { // The for loop with strtok_r() does not output empty strings // So, no need to check if the length of argv[0] > 0 @@ -2503,10 +2690,13 @@ opts_set_site(filter_rule_t *rule, char *site, int line_num) exit(EXIT_FAILURE); } - if (site[len - 1] == '*') { + // Don't modify site, site is reused in macro expansion + rule->site = strdup(site); + + if (rule->site[len - 1] == '*') { rule->exact = 0; len--; - site[len] = '\0'; + rule->site[len] = '\0'; // site == "*" ? if (len == 0) rule->all_sites = 1; @@ -2514,8 +2704,7 @@ opts_set_site(filter_rule_t *rule, char *site, int line_num) rule->exact = 1; } - rule->site = strdup(site); - + // redundant? if (equal(rule->site, "*")) rule->all_sites = 1; } @@ -2532,40 +2721,24 @@ opts_inc_arg_index(int i, int argc, char *last, int line_num) } static void -filter_rule_parse(opts_t *opts, const char *name, char *value, int line_num) +filter_rule_translate(opts_t *opts, const char *name, int argc, char **argv, int line_num) { -#define MAX_FILTER_RULE_TOKENS 13 - //(Divert|Split|Pass|Block|Match) // ([from ( - // user (username|*) [desc keyword]| - // ip (clientaddr|*)| + // user (username|$macro|*) [desc keyword]| + // ip (clientaddr|$macro|*)| // *)] // [to ( - // sni (servername[*]|*)| - // cn (commonname[*]|*)| - // host (host[*]|*)| - // uri (uri[*]|*)| - // ip (serveraddr|*)| + // sni (servername[*]|$macro|*)| + // cn (commonname[*]|$macro|*)| + // host (host[*]|$macro|*)| + // uri (uri[*]|$macro|*)| + // ip (serveraddr|$macro|*)| // *)] - // [log ([[!]connect] [[!]master] [[!]cert] [[!]content] [[!]pcap] [[!]mirror]|*)] + // [log ([[!]connect] [[!]master] [[!]cert] + // [[!]content] [[!]pcap] [[!]mirror] [$macro]|*|!*)] // |*) - char *argv[sizeof(char *) * MAX_FILTER_RULE_TOKENS]; - int argc = 0; - char *p, *last = NULL; - - for ((p = strtok_r(value, " ", &last)); - p; - (p = strtok_r(NULL, " ", &last))) { - if (argc < MAX_FILTER_RULE_TOKENS) { - argv[argc++] = p; - } else { - fprintf(stderr, "Too many arguments in filter rule on line %d\n", line_num); - exit(EXIT_FAILURE); - } - } - filter_rule_t *rule = malloc(sizeof(filter_rule_t)); memset(rule, 0, sizeof(filter_rule_t)); @@ -2583,62 +2756,32 @@ filter_rule_parse(opts_t *opts, const char *name, char *value, int line_num) // precedence can only go up not down rule->precedence = 0; - int done_all = 0; int done_from = 0; int done_to = 0; - int done_log = 0; int i = 0; while (i < argc) { if (equal(argv[i], "*")) { - if (done_all) { - fprintf(stderr, "Only one '*' statement allowed on line %d\n", line_num); - exit(EXIT_FAILURE); - } - if (++i > argc) { - fprintf(stderr, "Too many arguments for '*' on line %d\n", line_num); - exit(EXIT_FAILURE); - } - done_all = 1; + i++; } else if (equal(argv[i], "from")) { - if (done_from) { - fprintf(stderr, "Only one 'from' statement allowed on line %d\n", line_num); - exit(EXIT_FAILURE); - } - i = opts_inc_arg_index(i, argc, argv[i], line_num); #ifndef WITHOUT_USERAUTH if (equal(argv[i], "user") || equal(argv[i], "desc")) { if (equal(argv[i], "user")) { i = opts_inc_arg_index(i, argc, argv[i], line_num); - if (!opts->user_auth) { - fprintf(stderr, "User filter requires user auth on line %d\n", line_num); - exit(EXIT_FAILURE); - } - rule->precedence++; if (equal(argv[i], "*")) { rule->all_users = 1; } else { - if (!sys_isuser(argv[i])) { - fprintf(stderr, "No such user '%s' on line %d\n", argv[i], line_num); - exit(EXIT_FAILURE); - } rule->precedence++; rule->user = strdup(argv[i]); } i++; } - // It is possible to define desc without user (i.e. * or all_users), hence no 'else' here if (i < argc && equal(argv[i], "desc")) { - if (!opts->user_auth) { - fprintf(stderr, "Desc filter requires user auth on line %d\n", line_num); - exit(EXIT_FAILURE); - } - i = opts_inc_arg_index(i, argc, argv[i], line_num); rule->precedence++; rule->keyword = strdup(argv[i++]); @@ -2662,17 +2805,8 @@ filter_rule_parse(opts_t *opts, const char *name, char *value, int line_num) else if (equal(argv[i], "*")) { i++; } - else { - 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], "to")) { - if (done_to) { - fprintf(stderr, "Only one 'to' 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], "sni") || equal(argv[i], "cn") || equal(argv[i], "host") || equal(argv[i], "uri") || equal(argv[i], "ip")) { rule->precedence++; @@ -2695,17 +2829,8 @@ filter_rule_parse(opts_t *opts, const char *name, char *value, int line_num) else if (equal(argv[i], "*")) { i++; } - else { - 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); - } - rule->precedence++; i = opts_inc_arg_index(i, argc, argv[i], line_num); @@ -2751,8 +2876,6 @@ filter_rule_parse(opts_t *opts, const char *name, char *value, int line_num) || equal(argv[i], "mirror") || equal(argv[i], "!mirror") #endif /* !WITHOUT_MIRROR */ ); - - done_log = 1; } else if (equal(argv[i], "*")) { rule->log_connect = 2; @@ -2764,7 +2887,6 @@ filter_rule_parse(opts_t *opts, const char *name, char *value, int line_num) rule->log_mirror = 2; #endif /* !WITHOUT_MIRROR */ i++; - done_log = 1; } else if (equal(argv[i], "!*")) { rule->log_connect = 1; @@ -2776,17 +2898,8 @@ filter_rule_parse(opts_t *opts, const char *name, char *value, int line_num) 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); } } - else { - fprintf(stderr, "Unknown argument in filter rule at '%s' on line %d\n", argv[i], line_num); - exit(EXIT_FAILURE); - } } if (!done_from) { @@ -2802,7 +2915,7 @@ filter_rule_parse(opts_t *opts, const char *name, char *value, int line_num) rule->dstip = 1; } - opts_append_to_list(&opts->filter_rules, rule); + opts_append_to_filter_rules(&opts->filter_rules, rule); #ifdef DEBUG_OPTS log_dbg_printf("Filter rule: %s, %s, %s" @@ -2839,6 +2952,226 @@ filter_rule_parse(opts_t *opts, const char *name, char *value, int line_num) #endif /* DEBUG_OPTS */ } +static void +filter_rule_parse(opts_t *opts, const char *name, int argc, char **argv, int line_num); + +#define MAX_FILTER_RULE_TOKENS 13 + +static int +filter_rule_expand_macro(opts_t *opts, const char *name, int argc, char **argv, int i, int line_num) +{ + if (argv[i][0] == '$') { + macro_t *macro; + if ((macro = opts_find_macro(opts->macro, argv[i]))) { + value_t *value = macro->value; + while (value) { + char *expanded_argv[sizeof(char *) * MAX_FILTER_RULE_TOKENS]; + memcpy(expanded_argv, argv, sizeof expanded_argv); + + expanded_argv[i] = value->value; + + filter_rule_parse(opts, name, argc, expanded_argv, line_num); + + value = value->next; + } + // End of macro expansion, the caller must stop processing the rule + return 1; + } + else { + fprintf(stderr, "No such macro '%s' on line %d\n", argv[i], line_num); + exit(EXIT_FAILURE); + } + } + return 0; +} + +static void +filter_rule_parse(opts_t *opts, const char *name, int argc, char **argv, int line_num) +{ + int done_all = 0; + int done_from = 0; + int done_to = 0; + int done_log = 0; + int i = 0; + while (i < argc) { + if (equal(argv[i], "*")) { + if (done_all) { + fprintf(stderr, "Only one '*' statement allowed on line %d\n", line_num); + exit(EXIT_FAILURE); + } + if (++i > argc) { + fprintf(stderr, "Too many arguments for '*' on line %d\n", line_num); + exit(EXIT_FAILURE); + } + done_all = 1; + } + else if (equal(argv[i], "from")) { + if (done_from) { + fprintf(stderr, "Only one 'from' statement allowed on line %d\n", line_num); + exit(EXIT_FAILURE); + } + + i = opts_inc_arg_index(i, argc, argv[i], line_num); +#ifndef WITHOUT_USERAUTH + if (equal(argv[i], "user") || equal(argv[i], "desc")) { + if (equal(argv[i], "user")) { + i = opts_inc_arg_index(i, argc, argv[i], line_num); + + if (!opts->user_auth) { + fprintf(stderr, "User filter requires user auth on line %d\n", line_num); + exit(EXIT_FAILURE); + } + + if (equal(argv[i], "*")) { + // Nothing to do + } + else if (filter_rule_expand_macro(opts, name, argc, argv, i, line_num)) { + return; + } + else if (!sys_isuser(argv[i])) { + fprintf(stderr, "No such user '%s' on line %d\n", argv[i], line_num); + exit(EXIT_FAILURE); + } + i++; + } + + // It is possible to define desc without user (i.e. * or all_users), hence no 'else' here + if (i < argc && equal(argv[i], "desc")) { + if (!opts->user_auth) { + fprintf(stderr, "Desc filter requires user auth on line %d\n", line_num); + exit(EXIT_FAILURE); + } + + i = opts_inc_arg_index(i, argc, argv[i], line_num); + if (filter_rule_expand_macro(opts, name, argc, argv, i, line_num)) { + return; + } + i++; + } + + done_from = 1; + } + else +#endif /* !WITHOUT_USERAUTH */ + if (equal(argv[i], "ip")) { + i = opts_inc_arg_index(i, argc, argv[i], line_num); + if (equal(argv[i], "*")) { + // Nothing to do + } + else if (filter_rule_expand_macro(opts, name, argc, argv, i, line_num)) { + return; + } + i++; + done_from = 1; + } + else if (equal(argv[i], "*")) { + i++; + } + else { + 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], "to")) { + if (done_to) { + fprintf(stderr, "Only one 'to' 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], "sni") || equal(argv[i], "cn") || equal(argv[i], "host") || equal(argv[i], "uri") || equal(argv[i], "ip")) { + + i = opts_inc_arg_index(i, argc, argv[i], line_num); + if (filter_rule_expand_macro(opts, name, argc, argv, i, line_num)) { + return; + } + i++; + + done_to = 1; + } + else if (equal(argv[i], "*")) { + i++; + } + else { + 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], "master") || equal(argv[i], "cert") || equal(argv[i], "content") || equal(argv[i], "pcap") || + equal(argv[i], "!connect") || equal(argv[i], "!master") || equal(argv[i], "!cert") || equal(argv[i], "!content") || equal(argv[i], "!pcap") +#ifndef WITHOUT_MIRROR + || equal(argv[i], "mirror") || equal(argv[i], "!mirror") +#endif /* !WITHOUT_MIRROR */ + || argv[i][0] == '$') { + do { + if (filter_rule_expand_macro(opts, name, argc, argv, i, line_num)) { + return; + } + if (++i == argc) + break; + } while (equal(argv[i], "connect") || equal(argv[i], "master") || equal(argv[i], "cert") || equal(argv[i], "content") || equal(argv[i], "pcap") || + equal(argv[i], "!connect") || equal(argv[i], "!master") || equal(argv[i], "!cert") || equal(argv[i], "!content") || equal(argv[i], "!pcap") +#ifndef WITHOUT_MIRROR + || equal(argv[i], "mirror") || equal(argv[i], "!mirror") +#endif /* !WITHOUT_MIRROR */ + || argv[i][0] == '$'); + + done_log = 1; + } + else if (equal(argv[i], "*")) { + i++; + done_log = 1; + } + else if (equal(argv[i], "!*")) { + i++; + done_log = 1; + } + else if (filter_rule_expand_macro(opts, name, argc, argv, i, line_num)) { + return; + } + else { + fprintf(stderr, "Unknown argument in filter rule at '%s' on line %d\n", argv[i], line_num); + exit(EXIT_FAILURE); + } + } + else { + fprintf(stderr, "Unknown argument in filter rule at '%s' on line %d\n", argv[i], line_num); + exit(EXIT_FAILURE); + } + } + + // All checks passed and all macros expanded, if any + filter_rule_translate(opts, name, argc, argv, line_num); +} + +static void +opts_set_filter_rule(opts_t *opts, const char *name, char *value, int line_num) +{ + char *argv[sizeof(char *) * MAX_FILTER_RULE_TOKENS]; + int argc = 0; + char *p, *last = NULL; + + for ((p = strtok_r(value, " ", &last)); + p; + (p = strtok_r(NULL, " ", &last))) { + if (argc < MAX_FILTER_RULE_TOKENS) { + argv[argc++] = p; + } else { + fprintf(stderr, "Too many arguments in filter rule on line %d\n", line_num); + exit(EXIT_FAILURE); + } + } + + filter_rule_parse(opts, name, argc, argv, line_num); +} + static filter_site_t * opts_find_site(filter_site_t *site, filter_rule_t *rule) { @@ -3864,12 +4197,14 @@ set_option(opts_t *opts, const char *argv0, #endif /* DEBUG_OPTS */ } else if (equal(name, "PassSite")) { opts_set_passsite(opts, value, line_num); + } else if (equal(name, "Define")) { + opts_set_macro(opts, value, line_num); } else if (equal(name, "Split") || equal(name, "Pass") || equal(name, "Block") || equal(name, "Match")) { - filter_rule_parse(opts, name, value, line_num); + opts_set_filter_rule(opts, name, value, line_num); } else if (equal(name, "Divert")) { yes = is_yesno(value); if (yes == -1) { - filter_rule_parse(opts, name, value, line_num); + opts_set_filter_rule(opts, name, value, line_num); } else { yes ? opts_set_divert(opts) : opts_unset_divert(opts); } diff --git a/src/opts.h b/src/opts.h index 28101a5..94d2cc0 100644 --- a/src/opts.h +++ b/src/opts.h @@ -143,6 +143,7 @@ typedef struct opts { // Freed during startup after filter is created and debug printed struct filter_rule *filter_rules; struct filter *filter; + struct macro *macro; global_t *global; } opts_t; @@ -176,6 +177,17 @@ typedef struct proxyspec { opts_t *opts; } proxyspec_t; +typedef struct value { + char *value; + struct value *next; +} value_t; + +typedef struct macro { + char *name; + struct value *value; + struct macro *next; +} macro_t; + typedef struct filter_rule { char *site; unsigned int all_sites : 1; /* 1 to match all sites == '*' */ diff --git a/src/sslproxy.1 b/src/sslproxy.1 index 1bfd27d..95fffa0 100644 --- a/src/sslproxy.1 +++ b/src/sslproxy.1 @@ -308,17 +308,18 @@ The syntax of filtering rules is as follows: (Divert|Split|Pass|Block|Match) ([from ( - user (username|*) [desc keyword]| - ip (clientaddr|*)| + user (username|$macro|*) [desc keyword]| + ip (clientaddr|$macro|*)| *)] [to ( - sni (servername[*]|*)| - cn (commonname[*]|*)| - host (host[*]|*)| - uri (uri[*]|*)| - ip (serveraddr|*)| + sni (servername[*]|$macro|*)| + cn (commonname[*]|$macro|*)| + host (host[*]|$macro|*)| + uri (uri[*]|$macro|*)| + ip (serveraddr|$macro|*)| *)] - [log ([[!]connect] [[!]master] [[!]cert] [[!]content] [[!]pcap] [[!]mirror]|*)] + [log ([[!]connect] [[!]master] [[!]cert] + [[!]content] [[!]pcap] [[!]mirror] [$macro]|*|!*)] |*) .LP The definition of which connections the rule action will be applied to is @@ -329,8 +330,11 @@ that the rule is defined for. user or description keyword, or * for all. - The to part defines destination filter based on server IP address, SNI or Common Names of SSL connections, Host or URI fields in HTTP Request headers, or -* for all. Dst Host type of rules use ip, SSL type of rules use sni and -cn, and HTTP type of rules use host and uri site fields. +* for all. + + Dst Host type of rules use ip site field + + SSL type of rules use sni and cn site fields + + HTTP type of rules use host and uri site fields +.br - The proxyspec handling the connection defines the protocol filter for the connection. .LP @@ -407,12 +411,16 @@ If no filtering rules are defined for a proxyspec, all log actions for that proxyspec are enabled. Otherwise, all log actions are disabled, and filtering rules should enable them specifically. .LP -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 -in the rule. +Macro expansion is supported. The Define option can be used for defining +macros to be used in filtering rules. Macro names must start with a $ char. +The macro name must be followed by words separated with spaces. +.LP +You can append an asterisk * to the site field of filtering rules for +substring matching. Otherwise, the filter searches for an exact match with the +site field in the rule. .LP -The order of from, to, and log parts is not important. The order of log -actions is not important. +The order of filtering rules is important. The order of from, to, and log +parts is not important. The order of log actions is not important. .LP If the UserAuth option is disabled, only client IP addresses can be used in the from part of filtering rules. diff --git a/src/sslproxy.conf b/src/sslproxy.conf index 850aff2..368d8b7 100644 --- a/src/sslproxy.conf +++ b/src/sslproxy.conf @@ -272,22 +272,27 @@ PassUsers admin #PassSite *.google.com * android #PassSite .fbcdn.net* soner android -# Filter rules +# Define macro to be used in filtering rules. Macro names must start with a $ +# char. The macro name must be followed by words separated with spaces. +#Define $macro value1 value2 + +# 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] [[!]master] [[!]cert] [[!]content] [[!]pcap] [[!]mirror]|*)] -# |*) +#(Divert|Split|Pass|Block|Match) +# ([from ( +# user (username|$macro|*) [desc keyword]| +# ip (clientaddr|$macro|*)| +# *)] +# [to ( +# sni (servername[*]|$macro|*)| +# cn (commonname[*]|$macro|*)| +# host (host[*]|$macro|*)| +# uri (uri[*]|$macro|*)| +# ip (serveraddr|$macro|*)| +# *)] +# [log ([[!]connect] [[!]master] [[!]cert] +# [[!]content] [[!]pcap] [[!]mirror] [$macro]|*|!*)] +# |*) # # PassSite example.com is equivalent to the following two Pass rules: # Pass to sni example.com @@ -371,4 +376,7 @@ ProxySpec { ValidateProto yes # Proxyspec specific passsites are appended to the cloned global passsites PassSite example2.com + + Define $admin_users soner admin + Pass from user $admin_users desc android to cn .fbcdn.net* } diff --git a/src/sslproxy.conf.5 b/src/sslproxy.conf.5 index de8d784..ff0809d 100644 --- a/src/sslproxy.conf.5 +++ b/src/sslproxy.conf.5 @@ -312,6 +312,13 @@ asterisk to the site field to search for substring match. Note that the substring search is not a regex or wildcard search, and that the asterisk at the end is removed before search. .TP +\fBDefine STRING\fR +Define macro to be used in filtering rules. Macro names must start with a $ +char. The macro name must be followed by words separated with spaces. For +example, + +Define $macro value1 value2 +.TP \fBDivert STRING\fR Divert filtering rule diverts packets to listening program, allowing SSL inspection by listening program and content logging of packets. @@ -334,17 +341,18 @@ The syntax of filtering rules is as follows: (Divert|Split|Pass|Block|Match) ([from ( - user (username|*) [desc keyword]| - ip (clientaddr|*)| + user (username|$macro|*) [desc keyword]| + ip (clientaddr|$macro|*)| *)] [to ( - sni (servername[*]|*)| - cn (commonname[*]|*)| - host (host[*]|*)| - uri (uri[*]|*)| - ip (serveraddr|*)| + sni (servername[*]|$macro|*)| + cn (commonname[*]|$macro|*)| + host (host[*]|$macro|*)| + uri (uri[*]|$macro|*)| + ip (serveraddr|$macro|*)| *)] - [log ([[!]connect] [[!]master] [[!]cert] [[!]content] [[!]pcap] [[!]mirror]|*)] + [log ([[!]connect] [[!]master] [[!]cert] + [[!]content] [[!]pcap] [[!]mirror] [$macro]|*|!*)] |*) .br @@ -352,8 +360,8 @@ See sslproxy(1) for the details. .TP \fBProxySpec STRING\fR One line proxy specification: type listenaddr+port up:port ua:addr ra:addr. -The other options of one line proxyspecs are set to the global defaults. -Multiple specs are allowed, one on each line. +The other options of one line proxyspecs are set to the global configuration +preceding them. Multiple specs are allowed, one on each line. .TP \fBProxySpec {\fR .br @@ -427,15 +435,17 @@ ValidateProto .br PassSite .br +Define +.br Divert|Split|Pass|Block|Match filtering rules .br \fB}\fR .br Structured proxy specifications may consist of the options listed above. The -Proto, Addr, Port, and DivertPort options are mandatory, and equivalent to -type, listenaddr, port, and up options in one line proxyspecs, respectively. -If an option is not specified, the global default value is used. +Proto, Addr, and Port options are mandatory, and equivalent to type, +listenaddr, and port options in one line proxyspecs, respectively. If an +option is not specified, the global default value is used. .SH "FILES" .LP /etc/sslproxy/sslproxy.conf