zeek/src/Options.cc
Tim Wojtulewicz a8fc63e182 Merge remote-tracking branch 'microsoft/master'
* microsoft/master: (71 commits)
  Clang formatting
  Mask ports before inserting them into the map
  Fix compiler warning from applied patch
  Remove statistics plugin in favor of stats bif
  Add EventHandler version of stats plugin
  Mark a few EventHandler methods const
  Changed implementation from std::map to std::unordered_map of Val.cc
  Removed const, Windows build is now working
  Added fixes suggested in PR
  Update src/packet_analysis/protocol/ip/IP.cc
  Apply suggestions from code review
  Clang format again but now with v13.0.1
  Rewrote usages of define(_MSC_VER) to ifdef _MSC_VER
  Clang format it all
  Fixed initial CR comments
  Add NEWS entry about Windows port
  Add a couple of extra unistd.h includes to fix a build failure
  Use std::chrono instead of gettimeofday
  Update libkqueue submodule [nomail]
  Don't call tokenize_string if the input string is empty
  ...
2022-11-11 15:23:21 -07:00

699 lines
23 KiB
C++

// See the file "COPYING" in the main distribution directory for copyright.
#include "zeek/Options.h"
#include "zeek/zeek-config.h"
#if defined(HAVE_GETOPT_H) && ! defined(_MSC_VER)
#include <getopt.h>
#endif
#include <unistd.h>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <sstream>
#include "zeek/3rdparty/bsd-getopt-long.h"
#include "zeek/ScriptProfile.h"
#include "zeek/logging/writers/ascii/Ascii.h"
#include "zeek/script_opt/ScriptOpt.h"
namespace zeek
{
void Options::filter_supervisor_options()
{
pcap_filter = {};
signature_files = {};
pcap_output_file = {};
}
void Options::filter_supervised_node_options()
{
auto og = *this;
*this = {};
debug_log_streams = og.debug_log_streams;
debug_script_tracing_file = og.debug_script_tracing_file;
script_code_to_exec = og.script_code_to_exec;
script_prefixes = og.script_prefixes;
signature_re_level = og.signature_re_level;
ignore_checksums = og.ignore_checksums;
use_watchdog = og.use_watchdog;
pseudo_realtime = og.pseudo_realtime;
dns_mode = og.dns_mode;
bare_mode = og.bare_mode;
perftools_check_leaks = og.perftools_check_leaks;
perftools_profile = og.perftools_profile;
deterministic_mode = og.deterministic_mode;
abort_on_scripting_errors = og.abort_on_scripting_errors;
pcap_filter = og.pcap_filter;
signature_files = og.signature_files;
// TODO: These are likely to be handled in a node-specific or
// use-case-specific way. e.g. interfaces is already handled for the
// "cluster" use-case, but don't have supervised-pcap-reading
// functionality yet.
/* interface = og.interface; */
/* pcap_file = og.pcap_file; */
pcap_output_file = og.pcap_output_file;
random_seed_input_file = og.random_seed_input_file;
random_seed_output_file = og.random_seed_output_file;
process_status_file = og.process_status_file;
plugins_to_load = og.plugins_to_load;
scripts_to_load = og.scripts_to_load;
script_options_to_set = og.script_options_to_set;
}
bool fake_dns()
{
return getenv("ZEEK_DNS_FAKE");
}
extern const char* zeek_version();
void usage(const char* prog, int code)
{
fprintf(stderr, "zeek version %s\n", zeek_version());
fprintf(stderr, "usage: %s [options] [file ...]\n", prog);
fprintf(stderr, "usage: %s --test [doctest-options] -- [options] [file ...]\n", prog);
fprintf(stderr, " <file> | Zeek script file, or read stdin\n");
fprintf(stderr,
" -a|--parse-only | exit immediately after parsing scripts\n");
fprintf(stderr,
" -b|--bare-mode | don't load scripts from the base/ directory\n");
fprintf(stderr,
" -c|--capture-unprocessed <file> | write unprocessed packets to a tcpdump file\n");
fprintf(stderr, " -d|--debug-script | activate Zeek script debugging\n");
fprintf(stderr, " -e|--exec <zeek code> | augment loaded scripts by given code\n");
fprintf(stderr, " -f|--filter <filter> | tcpdump filter\n");
fprintf(stderr, " -h|--help | command line help\n");
fprintf(stderr,
" -i|--iface <interface> | read from given interface (only one allowed)\n");
fprintf(
stderr,
" -p|--prefix <prefix> | add given prefix to Zeek script file resolution\n");
fprintf(stderr, " -r|--readfile <readfile> | read from given tcpdump file (only one "
"allowed, pass '-' as the filename to read from stdin)\n");
fprintf(stderr, " -s|--rulefile <rulefile> | read rules from given file\n");
fprintf(stderr, " -t|--tracefile <tracefile> | activate execution tracing\n");
fprintf(stderr, " -u|--usage-issues | find variable usage issues and exit\n");
fprintf(stderr, " --no-unused-warnings | suppress warnings of unused "
"functions/hooks/events\n");
fprintf(stderr, " -v|--version | print version and exit\n");
fprintf(stderr, " -w|--writefile <writefile> | write to given tcpdump file\n");
#ifdef DEBUG
fprintf(stderr, " -B|--debug <dbgstreams> | Enable debugging output for selected "
"streams ('-B help' for help)\n");
#endif
fprintf(stderr, " -C|--no-checksums | ignore checksums\n");
fprintf(stderr, " -D|--deterministic | initialize random seeds to zero\n");
fprintf(stderr, " -E|--event-trace <file> | generate a replayable event trace to "
"the given file\n");
fprintf(stderr, " -F|--force-dns | force DNS\n");
fprintf(stderr, " -G|--load-seeds <file> | load seeds from given file\n");
fprintf(stderr, " -H|--save-seeds <file> | save seeds to given file\n");
fprintf(stderr, " -I|--print-id <ID name> | print out given ID\n");
fprintf(stderr, " -N|--print-plugins | print available plugins and exit (-NN "
"for verbose)\n");
fprintf(stderr, " -O|--optimize <option> | enable script optimization (use -O help "
"for options)\n");
fprintf(stderr, " -0|--optimize-files=<pat> | enable script optimization for all "
"functions in files with names containing the given pattern\n");
fprintf(stderr, " -o|--optimize-funcs=<pat> | enable script optimization for "
"functions with names fully matching the given pattern\n");
fprintf(stderr, " -P|--prime-dns | prime DNS\n");
fprintf(stderr,
" -Q|--time | print execution time summary to stderr\n");
fprintf(stderr, " -S|--debug-rules | enable rule debugging\n");
fprintf(stderr, " -T|--re-level <level> | set 'RE_level' for rules\n");
fprintf(stderr, " -U|--status-file <file> | Record process status in file\n");
fprintf(stderr, " -W|--watchdog | activate watchdog timer\n");
fprintf(stderr,
" -X|--zeekygen <cfgfile> | generate documentation based on config file\n");
#ifdef USE_PERFTOOLS_DEBUG
fprintf(stderr, " -m|--mem-leaks | show leaks [perftools]\n");
fprintf(stderr, " -M|--mem-profile | record heap [perftools]\n");
#endif
fprintf(
stderr,
" --profile-scripts[=file] | profile scripts to given file (default stdout)\n");
fprintf(stderr, " --pseudo-realtime[=<speedup>] | enable pseudo-realtime for performance "
"evaluation (default 1)\n");
fprintf(stderr, " -j|--jobs | enable supervisor mode\n");
fprintf(stderr, " --test | run unit tests ('--test -h' for help, "
"not available when built without ENABLE_ZEEK_UNIT_TESTS)\n");
fprintf(stderr, " $ZEEKPATH | file search path (%s)\n",
util::zeek_path().c_str());
fprintf(stderr, " $ZEEK_PLUGIN_PATH | plugin search path (%s)\n",
util::zeek_plugin_path());
fprintf(stderr, " $ZEEK_PLUGIN_ACTIVATE | plugins to always activate (%s)\n",
util::zeek_plugin_activate());
fprintf(stderr, " $ZEEK_PREFIXES | prefix list (%s)\n",
util::zeek_prefixes().c_str());
fprintf(stderr, " $ZEEK_DNS_FAKE | disable DNS lookups (%s)\n",
fake_dns() ? "on" : "off");
fprintf(stderr, " $ZEEK_SEED_FILE | file to load seeds from (not set)\n");
fprintf(stderr, " $ZEEK_LOG_SUFFIX | ASCII log file extension (.%s)\n",
logging::writer::detail::Ascii::LogExt().c_str());
fprintf(stderr, " $ZEEK_PROFILER_FILE | Output file for script execution "
"statistics (not set)\n");
fprintf(stderr,
" $ZEEK_DISABLE_ZEEKYGEN | Disable Zeekygen documentation support (%s)\n",
getenv("ZEEK_DISABLE_ZEEKYGEN") ? "set" : "not set");
fprintf(stderr,
" $ZEEK_DNS_RESOLVER | IPv4/IPv6 address of DNS resolver to use (%s)\n",
getenv("ZEEK_DNS_RESOLVER")
? getenv("ZEEK_DNS_RESOLVER")
: "not set, will use first IPv4 address from /etc/resolv.conf");
fprintf(stderr, " $ZEEK_DEBUG_LOG_STDERR | Use stderr for debug logs generated via "
"the -B flag");
fprintf(stderr, "\n");
exit(code);
}
static void print_analysis_help()
{
fprintf(stderr, "--optimize options when using ZAM:\n");
fprintf(stderr, " ZAM execute scripts using ZAM and all optimizations\n");
fprintf(stderr, " help print this list\n");
fprintf(stderr, " report-uncompilable print names of functions that can't be compiled\n");
fprintf(stderr, "\n primarily for developers:\n");
fprintf(stderr, " dump-uds dump use-defs to stdout; implies xform\n");
fprintf(stderr, " dump-xform dump transformed scripts to stdout; implies xform\n");
fprintf(stderr, " dump-ZAM dump generated ZAM code; implies gen-ZAM-code\n");
fprintf(stderr,
" gen-ZAM-code generate ZAM code (without turning on additional optimizations)\n");
fprintf(stderr, " inline inline function calls\n");
fprintf(stderr, " no-ZAM-opt omit low-level ZAM optimization\n");
fprintf(stderr, " optimize-all optimize all scripts, even inlined ones\n");
fprintf(stderr, " optimize-AST optimize the (transformed) AST; implies xform\n");
fprintf(stderr,
" profile-ZAM generate to stdout a ZAM execution profile; implies -O ZAM\n");
fprintf(stderr, " report-recursive report on recursive functions and exit\n");
fprintf(stderr, " xform transform scripts to \"reduced\" form\n");
fprintf(stderr, "\n--optimize options when generating C++:\n");
fprintf(stderr, " add-C++ add C++ script bodies to existing generated code\n");
fprintf(
stderr,
" allow-cond allow standalone compilation of functions influenced by conditionals\n");
fprintf(stderr, " gen-C++ generate C++ script bodies\n");
fprintf(stderr, " gen-standalone-C++ generate \"standalone\" C++ script bodies\n");
fprintf(stderr, " help print this list\n");
fprintf(stderr, " report-C++ report available C++ script bodies and exit\n");
fprintf(stderr, " report-uncompilable print names of functions that can't be compiled\n");
fprintf(stderr, " use-C++ use available C++ script bodies\n");
}
static void set_analysis_option(const char* opt, Options& opts)
{
auto& a_o = opts.analysis_options;
if ( ! opt || util::streq(opt, "ZAM") )
{
a_o.inliner = a_o.optimize_AST = a_o.activate = true;
a_o.gen_ZAM = true;
return;
}
if ( util::streq(opt, "help") )
{
print_analysis_help();
exit(0);
}
if ( util::streq(opt, "dump-uds") )
a_o.activate = a_o.dump_uds = true;
else if ( util::streq(opt, "dump-xform") )
a_o.activate = a_o.dump_xform = true;
else if ( util::streq(opt, "dump-ZAM") )
a_o.activate = a_o.dump_ZAM = true;
else if ( util::streq(opt, "add-C++") )
a_o.add_CPP = true;
else if ( util::streq(opt, "allow-cond") )
a_o.allow_cond = true;
else if ( util::streq(opt, "gen-C++") )
a_o.gen_CPP = true;
else if ( util::streq(opt, "gen-standalone-C++") )
a_o.gen_standalone_CPP = true;
else if ( util::streq(opt, "gen-ZAM-code") )
a_o.activate = a_o.gen_ZAM_code = true;
else if ( util::streq(opt, "inline") )
a_o.inliner = true;
else if ( util::streq(opt, "no-ZAM-opt") )
a_o.activate = a_o.no_ZAM_opt = true;
else if ( util::streq(opt, "optimize-all") )
a_o.activate = a_o.compile_all = true;
else if ( util::streq(opt, "optimize-AST") )
a_o.activate = a_o.optimize_AST = true;
else if ( util::streq(opt, "profile-ZAM") )
a_o.activate = a_o.gen_ZAM_code = a_o.profile_ZAM = true;
else if ( util::streq(opt, "report-C++") )
a_o.report_CPP = true;
else if ( util::streq(opt, "report-recursive") )
a_o.inliner = a_o.report_recursive = true;
else if ( util::streq(opt, "report-uncompilable") )
a_o.report_uncompilable = true;
else if ( util::streq(opt, "use-C++") )
a_o.use_CPP = true;
else if ( util::streq(opt, "xform") )
a_o.activate = true;
else
{
fprintf(stderr, "zeek: unrecognized -O/--optimize option: %s\n\n", opt);
print_analysis_help();
exit(1);
}
}
Options parse_cmdline(int argc, char** argv)
{
Options rval;
// When running unit tests, the first argument on the command line must be
// --test, followed by doctest options. Optionally, users can use "--" as
// separator to pass Zeek options afterwards:
//
// zeek --test [doctest-options] -- [zeek-options]
// Just locally filtering out the args for Zeek usage from doctest args.
std::vector<std::string> zeek_args;
if ( argc > 1 && strcmp(argv[1], "--test") == 0 )
{
#ifdef DOCTEST_CONFIG_DISABLE
fprintf(stderr, "ERROR: C++ unit tests are disabled for this build.\n"
" Please re-compile with ENABLE_ZEEK_UNIT_TESTS "
"to run the C++ unit tests.\n");
usage(argv[0], 1);
#endif
auto is_separator = [](const char* cstr)
{
return strcmp(cstr, "--") == 0;
};
auto first = argv;
auto last = argv + argc;
auto separator = std::find_if(first, last, is_separator);
zeek_args.emplace_back(argv[0]);
if ( separator != last )
{
auto first_zeek_arg = std::next(separator);
for ( auto i = first_zeek_arg; i != last; ++i )
zeek_args.emplace_back(*i);
}
rval.run_unit_tests = true;
for ( ptrdiff_t i = 0; i < std::distance(first, separator); ++i )
rval.doctest_args.emplace_back(argv[i]);
}
else
{
if ( argc > 1 )
{
auto endsWith = [](const std::string& str, const std::string& suffix)
{
return str.size() >= suffix.size() &&
0 == str.compare(str.size() - suffix.size(), suffix.size(), suffix);
};
auto i = 0;
for ( ; i < argc && ! endsWith(argv[i], "--"); ++i )
{
zeek_args.emplace_back(argv[i]);
}
if ( i < argc )
{
// If a script is invoked with Zeek as the interpreter, the arguments provided
// directly in the interpreter line of the script won't be broken apart in the
// argv on Linux so we split it up here.
if ( endsWith(argv[i], "--") && zeek_args.size() == 1 )
{
std::istringstream iss(argv[i]);
for ( std::string s; iss >> s; )
{
if ( ! endsWith(s, "--") )
{
zeek_args.emplace_back(s);
}
}
}
// There is an additional increment here to skip over the "--" if it was found.
if ( endsWith(argv[i], "--") )
++i;
// The first argument after the double hyphens in implicitly a script name.
rval.scripts_to_load.emplace_back(argv[i++]);
// If there are more argument, grab them for script arguments
for ( ; i < argc; ++i )
rval.script_args.emplace_back(argv[i]);
}
}
}
int profile_scripts = 0;
int no_unused_warnings = 0;
struct option long_opts[] = {
{"parse-only", no_argument, nullptr, 'a'},
{"bare-mode", no_argument, nullptr, 'b'},
{"capture-unprocessed", required_argument, nullptr, 'c'},
{"debug-script", no_argument, nullptr, 'd'},
{"exec", required_argument, nullptr, 'e'},
{"filter", required_argument, nullptr, 'f'},
{"help", no_argument, nullptr, 'h'},
{"iface", required_argument, nullptr, 'i'},
{"zeekygen", required_argument, nullptr, 'X'},
{"prefix", required_argument, nullptr, 'p'},
{"readfile", required_argument, nullptr, 'r'},
{"rulefile", required_argument, nullptr, 's'},
{"tracefile", required_argument, nullptr, 't'},
{"writefile", required_argument, nullptr, 'w'},
{"usage-issues", no_argument, nullptr, 'u'},
{"version", no_argument, nullptr, 'v'},
{"no-checksums", no_argument, nullptr, 'C'},
{"force-dns", no_argument, nullptr, 'F'},
{"deterministic", no_argument, nullptr, 'D'},
{"event-trace", required_argument, nullptr, 'E'},
{"load-seeds", required_argument, nullptr, 'G'},
{"save-seeds", required_argument, nullptr, 'H'},
{"print-plugins", no_argument, nullptr, 'N'},
{"optimize", required_argument, nullptr, 'O'},
{"optimize-funcs", required_argument, nullptr, 'o'},
{"optimize-files", required_argument, nullptr, '0'},
{"prime-dns", no_argument, nullptr, 'P'},
{"time", no_argument, nullptr, 'Q'},
{"debug-rules", no_argument, nullptr, 'S'},
{"re-level", required_argument, nullptr, 'T'},
{"watchdog", no_argument, nullptr, 'W'},
{"print-id", required_argument, nullptr, 'I'},
{"status-file", required_argument, nullptr, 'U'},
{"debug", required_argument, nullptr, 'B'},
#ifdef USE_PERFTOOLS_DEBUG
{"mem-leaks", no_argument, nullptr, 'm'},
{"mem-profile", no_argument, nullptr, 'M'},
#endif
{"profile-scripts", optional_argument, &profile_scripts, 1},
{"no-unused-warnings", no_argument, &no_unused_warnings, 1},
{"pseudo-realtime", optional_argument, nullptr, '~'},
{"jobs", optional_argument, nullptr, 'j'},
{"test", no_argument, nullptr, '#'},
{nullptr, 0, nullptr, 0},
};
char opts[256];
util::safe_strncpy(opts, "B:c:E:e:f:G:H:I:i:j::n:O:0:o:p:r:s:T:t:U:w:X:CDFMNPQSWabdhmuv",
sizeof(opts));
int op;
int long_optsind;
opterr = 0;
// getopt may permute the array, so need yet another array
auto zargs = std::make_unique<char*[]>(zeek_args.size());
for ( size_t i = 0; i < zeek_args.size(); ++i )
zargs[i] = zeek_args[i].data();
while ( (op = getopt_long(zeek_args.size(), zargs.get(), opts, long_opts, &long_optsind)) !=
EOF )
switch ( op )
{
case 'a':
rval.parse_only = true;
break;
case 'b':
rval.bare_mode = true;
break;
case 'c':
rval.unprocessed_output_file = optarg;
break;
case 'd':
rval.debug_scripts = true;
break;
case 'e':
rval.script_code_to_exec = optarg;
break;
case 'f':
rval.pcap_filter = optarg;
break;
case 'h':
rval.print_usage = true;
break;
case 'i':
if ( rval.interface )
{
fprintf(stderr, "ERROR: Only a single interface option (-i) is allowed.\n");
exit(1);
}
if ( rval.pcap_file )
{
fprintf(stderr, "ERROR: Using -i is not allow when reading a pcap file.\n");
exit(1);
}
rval.interface = optarg;
break;
case 'j':
rval.supervisor_mode = true;
if ( optarg )
{
// TODO: for supervised offline pcap reading, the argument is
// expected to be number of workers like "-j 4" or possibly a
// list of worker/proxy/logger counts like "-j 4,2,1"
}
break;
case 'p':
rval.script_prefixes.emplace_back(optarg);
break;
case 'r':
if ( rval.pcap_file )
{
fprintf(stderr, "ERROR: Only a single readfile option (-r) is allowed.\n");
exit(1);
}
if ( rval.interface )
{
fprintf(stderr, "Using -r is not allowed when reading a live interface.\n");
exit(1);
}
rval.pcap_file = optarg;
break;
case 's':
rval.signature_files.emplace_back(optarg);
break;
case 't':
rval.debug_script_tracing_file = optarg;
break;
case 'u':
++rval.analysis_options.usage_issues;
break;
case 'v':
rval.print_version = true;
break;
case 'w':
rval.pcap_output_file = optarg;
break;
case 'B':
#ifdef DEBUG
rval.debug_log_streams = optarg;
#else
if ( util::streq(optarg, "help") )
{
fprintf(stderr, "debug streams unavailable\n");
exit(1);
}
#endif
break;
case 'C':
rval.ignore_checksums = true;
break;
case 'D':
rval.deterministic_mode = true;
break;
case 'E':
rval.event_trace_file = optarg;
break;
case 'F':
if ( rval.dns_mode != detail::DNS_DEFAULT )
usage(zargs[0], 1);
rval.dns_mode = detail::DNS_FORCE;
break;
case 'G':
rval.random_seed_input_file = optarg;
break;
case 'H':
rval.random_seed_output_file = optarg;
break;
case 'I':
rval.identifier_to_print = optarg;
break;
case 'N':
++rval.print_plugins;
break;
case 'O':
set_analysis_option(optarg, rval);
break;
case 'o':
add_func_analysis_pattern(rval.analysis_options, optarg);
break;
case '0':
add_file_analysis_pattern(rval.analysis_options, optarg);
break;
case 'P':
if ( rval.dns_mode != detail::DNS_DEFAULT )
usage(zargs[0], 1);
rval.dns_mode = detail::DNS_PRIME;
break;
case 'Q':
rval.print_execution_time = true;
break;
case 'S':
rval.print_signature_debug_info = true;
break;
case 'T':
rval.signature_re_level = atoi(optarg);
break;
case 'U':
rval.process_status_file = optarg;
break;
case 'W':
rval.use_watchdog = true;
break;
case 'X':
rval.zeekygen_config_file = optarg;
break;
#ifdef USE_PERFTOOLS_DEBUG
case 'm':
rval.perftools_check_leaks = 1;
break;
case 'M':
rval.perftools_profile = 1;
break;
#endif
case '~':
rval.pseudo_realtime = 1.0;
if ( optarg )
rval.pseudo_realtime = atof(optarg);
break;
case '#':
fprintf(stderr, "ERROR: --test only allowed as first argument.\n");
usage(zargs[0], 1);
break;
case 0:
// This happens for long options that don't have
// a short-option equivalent.
if ( profile_scripts )
{
activate_script_profiling(optarg);
profile_scripts = 0;
}
if ( no_unused_warnings )
rval.no_unused_warnings = true;
break;
case '?':
default:
usage(zargs[0], 1);
break;
}
// Process remaining arguments. X=Y arguments indicate script
// variable/parameter assignments. X::Y arguments indicate plugins to
// activate/query. The remainder are treated as scripts to load.
while ( optind < static_cast<int>(zeek_args.size()) )
{
if ( strchr(zargs[optind], '=') )
rval.script_options_to_set.emplace_back(zargs[optind++]);
else if ( strstr(zargs[optind], "::") )
rval.plugins_to_load.emplace(zargs[optind++]);
else
rval.scripts_to_load.emplace_back(zargs[optind++]);
}
auto canonify_script_path = [](std::string* path)
{
if ( path->empty() )
return;
*path = util::detail::normalize_path(*path);
if ( (*path)[0] == '/' || (*path)[0] == '~' )
// Absolute path
return;
if ( (*path)[0] != '.' )
{
// Look up file in ZEEKPATH
auto res = util::find_script_file(*path, util::zeek_path());
if ( res.empty() )
{
fprintf(stderr, "failed to locate script: %s\n", path->data());
exit(1);
}
*path = res;
if ( (*path)[0] == '/' || (*path)[0] == '~' )
// Now an absolute path
return;
}
// Need to translate relative path to absolute.
char cwd[PATH_MAX];
if ( ! getcwd(cwd, sizeof(cwd)) )
{
fprintf(stderr, "failed to get current directory: %s\n", strerror(errno));
exit(1);
}
*path = std::string(cwd) + "/" + *path;
};
if ( rval.supervisor_mode )
{
// Translate any relative paths supplied to supervisor into absolute
// paths for use by supervised nodes since they have the option to
// operate out of a different working directory.
for ( auto& s : rval.scripts_to_load )
canonify_script_path(&s);
}
return rval;
}
} // namespace zeek