Merge branch 'master' into topic/jsiwek/broker

This commit is contained in:
Jon Siwek 2015-02-16 10:00:17 -06:00
commit e95116ba85
26 changed files with 328 additions and 54 deletions

34
CHANGES
View file

@ -1,4 +1,38 @@
2.3-419 | 2015-02-13 09:10:44 -0600
* BIT-1011: Update the SOCKS analyzer to support user/pass login.
(Nicolas Retrain, Seth Hall, Jon Siwek)
- Add a new field to socks.log: "password".
- Two new events: "socks_login_userpass_request" and
"socks_login_userpass_reply".
- Two new weirds for unsupported SOCKS authentication method or
version.
- A new test for authenticated socks traffic.
2.3-416 | 2015-02-12 12:18:42 -0600
* Submodule update - newest sqlite version (Johanna Amann)
* Fix use of deprecated gperftools headers. (Jon Siwek)
2.3-413 | 2015-02-08 18:23:05 -0800
* Fixing analyzer tag types for some Files::* functions. (Robin Sommer)
* Changing load order for plugin scripts. (Robin Sommer)
2.3-411 | 2015-02-05 10:05:48 -0600
* Fix file analysis of files with total size below the bof_buffer size
never delivering content to stream analyzers. (Seth Hall)
* Add/fix log fields in x509 diff canonifier. (Jon Siwek)
* "id" not defined for debug code when using -DPROFILE_BRO_FUNCTIONS
(Mike Smiley)
2.3-406 | 2015-02-03 17:02:45 -0600
* Add x509 canonifier to a unit test. (Jon Siwek)

View file

@ -1 +1 @@
2.3-406
2.3-419

@ -1 +1 @@
Subproject commit 0b713c027d3efaaca50e5df995c02656175573cd
Subproject commit 3714d3594ce0d2b8a757c04e6e7d901d6b559915

@ -1 +1 @@
Subproject commit d43cc790e5b8709b5e032e52ad0e00936494739b
Subproject commit 420c5b42c0c90f22fc7a862fc491c8e554d05381

View file

@ -267,7 +267,7 @@ export {
## mts: The set of MIME types, each in the form "foo/bar" (case-insensitive).
##
## Returns: True if the MIME types were successfully registered.
global register_for_mime_types: function(tag: Analyzer::Tag, mts: set[string]) : bool;
global register_for_mime_types: function(tag: Files::Tag, mts: set[string]) : bool;
## Registers a MIME type for an analyzer. If a future file with this type is seen,
## the analyzer will be automatically assigned to parsing it. The function *adds*
@ -278,20 +278,20 @@ export {
## mt: The MIME type in the form "foo/bar" (case-insensitive).
##
## Returns: True if the MIME type was successfully registered.
global register_for_mime_type: function(tag: Analyzer::Tag, mt: string) : bool;
global register_for_mime_type: function(tag: Files::Tag, mt: string) : bool;
## Returns a set of all MIME types currently registered for a specific analyzer.
##
## tag: The tag of the analyzer.
##
## Returns: The set of MIME types.
global registered_mime_types: function(tag: Analyzer::Tag) : set[string];
global registered_mime_types: function(tag: Files::Tag) : set[string];
## Returns a table of all MIME-type-to-analyzer mappings currently registered.
##
## Returns: A table mapping each analyzer to the set of MIME types
## registered for it.
global all_registered_mime_types: function() : table[Analyzer::Tag] of set[string];
global all_registered_mime_types: function() : table[Files::Tag] of set[string];
## Event that can be handled to access the Info record as it is sent on
## to the logging framework.
@ -306,8 +306,8 @@ redef record fa_file += {
global registered_protocols: table[Analyzer::Tag] of ProtoRegistration = table();
# Store the MIME type to analyzer mappings.
global mime_types: table[Analyzer::Tag] of set[string];
global mime_type_to_analyzers: table[string] of set[Analyzer::Tag];
global mime_types: table[Files::Tag] of set[string];
global mime_type_to_analyzers: table[string] of set[Files::Tag];
global analyzer_add_callbacks: table[Files::Tag] of function(f: fa_file, args: AnalyzerArgs) = table();
@ -401,7 +401,7 @@ function register_protocol(tag: Analyzer::Tag, reg: ProtoRegistration): bool
return result;
}
function register_for_mime_types(tag: Analyzer::Tag, mime_types: set[string]) : bool
function register_for_mime_types(tag: Files::Tag, mime_types: set[string]) : bool
{
local rc = T;
@ -414,7 +414,7 @@ function register_for_mime_types(tag: Analyzer::Tag, mime_types: set[string]) :
return rc;
}
function register_for_mime_type(tag: Analyzer::Tag, mt: string) : bool
function register_for_mime_type(tag: Files::Tag, mt: string) : bool
{
if ( tag !in mime_types )
{
@ -431,12 +431,12 @@ function register_for_mime_type(tag: Analyzer::Tag, mt: string) : bool
return T;
}
function registered_mime_types(tag: Analyzer::Tag) : set[string]
function registered_mime_types(tag: Files::Tag) : set[string]
{
return tag in mime_types ? mime_types[tag] : set();
}
function all_registered_mime_types(): table[Analyzer::Tag] of set[string]
function all_registered_mime_types(): table[Files::Tag] of set[string]
{
return mime_types;
}
@ -451,7 +451,7 @@ function describe(f: fa_file): string
return handler$describe(f);
}
event get_file_handle(tag: Analyzer::Tag, c: connection, is_orig: bool) &priority=5
event get_file_handle(tag: Files::Tag, c: connection, is_orig: bool) &priority=5
{
if ( tag !in registered_protocols )
return;

View file

@ -16,8 +16,10 @@ export {
id: conn_id &log;
## Protocol version of SOCKS.
version: count &log;
## Username for the proxy if extracted from the network.
## Username used to request a login to the proxy.
user: string &log &optional;
## Password used to request a login to the proxy.
password: string &log &optional;
## Server status for the attempt at using the proxy.
status: string &log &optional;
## Client requested SOCKS address. Could be an address, a name
@ -91,3 +93,21 @@ event socks_reply(c: connection, version: count, reply: count, sa: SOCKS::Addres
if ( "SOCKS" in c$service )
Log::write(SOCKS::LOG, c$socks);
}
event socks_login_userpass_request(c: connection, user: string, password: string) &priority=5
{
# Authentication only possible with the version 5.
set_session(c, 5);
c$socks$user = user;
c$socks$password = password;
}
event socks_login_userpass_reply(c: connection, code: count) &priority=5
{
# Authentication only possible with the version 5.
set_session(c, 5);
c$socks$status = v5_status[code];
}

@ -1 +1 @@
Subproject commit 7e15efe9d28d46bfa662fcdd1cbb15ce1db285c9
Subproject commit f2e34d731ed29bb993fbb065846faa342a8c824f

View file

@ -323,7 +323,7 @@ int BroFunc::IsPure() const
Val* BroFunc::Call(val_list* args, Frame* parent) const
{
#ifdef PROFILE_BRO_FUNCTIONS
DEBUG_MSG("Function: %s\n", id->Name());
DEBUG_MSG("Function: %s\n", Name());
#endif
SegmentProfiler(segment_logger, location);

View file

@ -57,8 +57,7 @@ void SOCKS_Analyzer::DeliverStream(int len, const u_char* data, bool orig)
// with the rest of the conneciton.
//
// Note that we assume that no payload data arrives before both endpoints
// are done with there part of the SOCKS protocol.
// are done with their part of the SOCKS protocol.
if ( ! pia )
{
pia = new pia::PIA_TCP(Conn());

View file

@ -27,3 +27,19 @@ event socks_request%(c: connection, version: count, request_type: count, sa: SOC
## p: The destination port for the proxied traffic.
event socks_reply%(c: connection, version: count, reply: count, sa: SOCKS::Address, p: port%);
## Generated when a SOCKS client performs username and password based login.
##
## c: The parent connection of the proxy.
##
## user: The given username.
##
## password: The given password.
event socks_login_userpass_request%(c: connection, user: string, password: string%);
## Generated when a SOCKS server replies to a username/password login attempt.
##
## c: The parent connection of the proxy.
##
## code: The response code for the attempted login.
event socks_login_userpass_reply%(c: connection, code: count%);

View file

@ -148,6 +148,37 @@ refine connection SOCKS_Conn += {
return true;
%}
function socks5_auth_request_userpass(request: SOCKS5_Auth_Request_UserPass_v1): bool
%{
StringVal* user = new StringVal(${request.username}.length(), (const char*) ${request.username}.begin());
StringVal* pass = new StringVal(${request.password}.length(), (const char*) ${request.password}.begin());
BifEvent::generate_socks_login_userpass_request(bro_analyzer(),
bro_analyzer()->Conn(),
user, pass);
return true;
%}
function socks5_unsupported_authentication_method(auth_method: uint8): bool
%{
reporter->Weird(bro_analyzer()->Conn(), fmt("socks5_unsupported_authentication_method_%d", auth_method));
return true;
%}
function socks5_unsupported_authentication_version(auth_method: uint8, version: uint8): bool
%{
reporter->Weird(bro_analyzer()->Conn(), fmt("socks5_unsupported_authentication_%d_%d", auth_method, version));
return true;
%}
function socks5_auth_reply_userpass(reply: SOCKS5_Auth_Reply_UserPass_v1): bool
%{
BifEvent::generate_socks_login_userpass_reply(bro_analyzer(),
bro_analyzer()->Conn(),
${reply.code});
return true;
%}
function version_error(version: uint8): bool
%{
bro_analyzer()->ProtocolViolation(fmt("unsupported/unknown SOCKS version %d", version));
@ -176,3 +207,22 @@ refine typeattr SOCKS5_Request += &let {
refine typeattr SOCKS5_Reply += &let {
proc: bool = $context.connection.socks5_reply(this);
};
refine typeattr SOCKS5_Auth_Negotiation_Reply += &let {
};
refine typeattr SOCKS5_Auth_Request_UserPass_v1 += &let {
proc: bool = $context.connection.socks5_auth_request_userpass(this);
};
refine typeattr SOCKS5_Auth_Reply_UserPass_v1 += &let {
proc: bool = $context.connection.socks5_auth_reply_userpass(this);
};
refine typeattr SOCKS5_Unsupported_Authentication_Method += &let {
proc: bool = $context.connection.socks5_unsupported_authentication_method($context.connection.v5_auth_method());
};
refine typeattr SOCKS5_Unsupported_Authentication_Version += &let {
proc: bool = $context.connection.socks5_unsupported_authentication_version($context.connection.v5_auth_method(), version);
};

View file

@ -1,4 +1,9 @@
type SOCKS_Message(is_orig: bool) = case $context.connection.v5_in_auth_sub_negotiation() of {
true -> auth: SOCKS5_Auth_Message(is_orig);
false -> msg: SOCKS_Version(is_orig);
};
type SOCKS_Version(is_orig: bool) = record {
version: uint8;
msg: case version of {
@ -14,10 +19,11 @@ type SOCKS_Version_Error(version: uint8) = record {
# SOCKS5 Implementation
type SOCKS5_Message(is_orig: bool) = case $context.connection.v5_past_authentication() of {
true -> msg: SOCKS5_Real_Message(is_orig);
false -> auth: SOCKS5_Auth_Negotiation(is_orig);
true -> msg: SOCKS5_Real_Message(is_orig);
};
type SOCKS5_Auth_Negotiation(is_orig: bool) = case is_orig of {
true -> req: SOCKS5_Auth_Negotiation_Request;
false -> rep: SOCKS5_Auth_Negotiation_Reply;
@ -31,7 +37,61 @@ type SOCKS5_Auth_Negotiation_Request = record {
type SOCKS5_Auth_Negotiation_Reply = record {
selected_auth_method: uint8;
} &let {
in_auth_sub_neg = $context.connection.set_v5_in_auth_sub_negotiation(selected_auth_method == 0 || selected_auth_method == 0xff ? false : true);
past_auth = $context.connection.set_v5_past_authentication();
set_auth = $context.connection.set_v5_auth_method(selected_auth_method);
};
type SOCKS5_Auth_Message(is_orig: bool) = case is_orig of {
true -> req: SOCKS5_Auth_Request;
false -> rep: SOCKS5_Auth_Reply;
};
type SOCKS5_Auth_Request = case $context.connection.v5_auth_method() of {
0x02 -> userpass : SOCKS5_Auth_Request_UserPass;
default -> unsupported : SOCKS5_Unsupported_Authentication_Method;
};
type SOCKS5_Unsupported_Authentication_Method = record {
crap: bytestring &restofdata;
};
type SOCKS5_Unsupported_Authentication_Version(version: uint8) = record {
crap: bytestring &restofdata;
};
type SOCKS5_Auth_Request_UserPass = record {
version: uint8;
msg: case version of {
1 -> v1: SOCKS5_Auth_Request_UserPass_v1;
default -> unsupported: SOCKS5_Unsupported_Authentication_Version(version);
};
};
type SOCKS5_Auth_Request_UserPass_v1 = record {
ulen : uint8;
username : bytestring &length=ulen;
plen : uint8;
password : bytestring &length=plen;
};
type SOCKS5_Auth_Reply = case $context.connection.v5_auth_method() of {
0x02 -> userpass : SOCKS5_Auth_Reply_UserPass;
default -> unsupported : SOCKS5_Unsupported_Authentication_Method;
} &let {
in_auth_sub_neg = $context.connection.set_v5_in_auth_sub_negotiation(false);
};
type SOCKS5_Auth_Reply_UserPass = record {
version: uint8;
msg: case version of {
1 -> v1: SOCKS5_Auth_Reply_UserPass_v1;
default -> unsupported: SOCKS5_Unsupported_Authentication_Version(version);
};
};
type SOCKS5_Auth_Reply_UserPass_v1 = record {
code : uint8;
};
type SOCKS5_Real_Message(is_orig: bool) = case is_orig of {
@ -98,11 +158,26 @@ type SOCKS4_Reply = record {
refine connection SOCKS_Conn += {
%member{
bool v5_in_auth_sub_negotiation_;
bool v5_authenticated_;
uint8 selected_auth_method_;
%}
%init{
v5_in_auth_sub_negotiation_ = false;
v5_authenticated_ = false;
selected_auth_method_ = 255;
%}
function v5_in_auth_sub_negotiation(): bool
%{
return v5_in_auth_sub_negotiation_;
%}
function set_v5_in_auth_sub_negotiation(b: bool): bool
%{
v5_in_auth_sub_negotiation_ = b;
return true;
%}
function v5_past_authentication(): bool
@ -115,5 +190,16 @@ refine connection SOCKS_Conn += {
v5_authenticated_ = true;
return true;
%}
function set_v5_auth_method(method: uint8): bool
%{
selected_auth_method_ = method;
return true;
%}
function v5_auth_method(): uint8
%{
return selected_auth_method_;
%}
};

View file

@ -20,7 +20,7 @@ connection SOCKS_Conn(bro_analyzer: BroAnalyzer) {
%include socks-protocol.pac
flow SOCKS_Flow(is_orig: bool) {
datagram = SOCKS_Version(is_orig) withcontext(connection, this);
datagram = SOCKS_Message(is_orig) withcontext(connection, this);
};
%include socks-analyzer.pac

View file

@ -492,18 +492,22 @@ void File::EndOfFile()
if ( done )
return;
if ( ! did_mime_type &&
LookupFieldDefaultCount(missing_bytes_idx) == 0 )
DetectMIME();
analyzers.DrainModifications();
if ( file_reassembler )
{
file_reassembler->Flush();
analyzers.DrainModifications();
}
// Mark the bof_buffer as full in case it isn't yet
// so that the whole thing can be flushed out to
// any stream analyzers.
if ( ! bof_buffer.full )
{
bof_buffer.full = true;
DeliverStream((const u_char*) "", 0);
}
analyzers.DrainModifications();
done = true;
file_analysis::Analyzer* a = 0;

View file

@ -172,7 +172,7 @@ bool Manager::ActivateDynamicPluginInternal(const std::string& name, bool ok_if_
// Load {bif,scripts}/__load__.bro automatically.
string init = dir + "scripts/__load__.bro";
string init = dir + "lib/bif/__load__.bro";
if ( is_file(init) )
{
@ -180,7 +180,7 @@ bool Manager::ActivateDynamicPluginInternal(const std::string& name, bool ok_if_
scripts_to_load.push_back(init);
}
init = dir + "lib/bif/__load__.bro";
init = dir + "scripts/__load__.bro";
if ( is_file(init) )
{

View file

@ -48,8 +48,8 @@
#endif
#ifdef USE_PERFTOOLS_DEBUG
#include <google/heap-checker.h>
#include <google/heap-profiler.h>
#include <gperftools/heap-checker.h>
#include <gperftools/heap-profiler.h>
extern HeapLeakChecker* heap_checker;
#endif

View file

@ -17,6 +17,6 @@ Demo::Foo - A Foo test logging writer (dynamic, version 1.0)
[http] 1340213020.732963|CjhGID4nQcgTWjvg4c|10.0.0.55|53994|60.190.189.214|8124|5|GET|www.osnews.com|/images/icons/17.gif|http://www.osnews.com/|Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:10.0.2) Gecko/20100101 Firefox/10.0.2|0|0|304|Not Modified|-|-|-||-|-|-|-|-|-|-
[http] 1340213021.300269|CjhGID4nQcgTWjvg4c|10.0.0.55|53994|60.190.189.214|8124|6|GET|www.osnews.com|/images/left.gif|http://www.osnews.com/|Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:10.0.2) Gecko/20100101 Firefox/10.0.2|0|0|304|Not Modified|-|-|-||-|-|-|-|-|-|-
[http] 1340213021.861584|CjhGID4nQcgTWjvg4c|10.0.0.55|53994|60.190.189.214|8124|7|GET|www.osnews.com|/images/icons/32.gif|http://www.osnews.com/|Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:10.0.2) Gecko/20100101 Firefox/10.0.2|0|0|304|Not Modified|-|-|-||-|-|-|-|-|-|-
[packet_filter] 1412721099.419280|bro|ip or not ip|T|T
[socks] 1340213015.276495|CjhGID4nQcgTWjvg4c|10.0.0.55|53994|60.190.189.214|8124|5|-|succeeded|-|www.osnews.com|80|192.168.0.31|-|2688
[packet_filter] 1423781675.402129|bro|ip or not ip|T|T
[socks] 1340213015.276495|CjhGID4nQcgTWjvg4c|10.0.0.55|53994|60.190.189.214|8124|5|-|-|succeeded|-|www.osnews.com|80|192.168.0.31|-|2688
[tunnel] 1340213015.276495|-|10.0.0.55|0|60.190.189.214|8124|Tunnel::SOCKS|Tunnel::DISCOVER

View file

@ -0,0 +1,10 @@
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path files
#open 2015-02-05-13-55-41
#fields ts fuid tx_hosts rx_hosts conn_uids source depth analyzers mime_type filename duration local_orig is_orig seen_bytes total_bytes missing_bytes overflow_bytes timedout parent_fuid md5 sha1 sha256 extracted
#types time string set[addr] set[addr] set[string] string count set[string] string string interval bool bool count count count count bool string string string string string
1362692527.009512 FakNcS1Jfe01uljb3 192.150.187.43 141.142.228.5 CXWv6p3arKYeMETxOg HTTP 0 MD5,SHA1 text/plain - 0.000263 - F 4705 4705 0 0 F - 397168fd09991a0e712254df7bc639ac 1dd7ac0398df6cbc0696445a91ec681facf4dc47 - -
#close 2015-02-05-13-55-41

View file

@ -0,0 +1,10 @@
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path socks
#open 2015-02-05-16-13-12
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version user password status request.host request.name request_p bound.host bound.name bound_p
#types time string addr port addr port count string string string addr string port addr string port
1368517392.724989 CXWv6p3arKYeMETxOg 192.168.0.2 55951 192.168.0.1 1080 5 bob alice succeeded 192.168.0.2 - 22 192.168.0.1 - 55951
#close 2015-02-05-16-13-12

View file

@ -0,0 +1,10 @@
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path tunnel
#open 2015-02-05-16-13-12
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p tunnel_type action
#types time string addr port addr port enum enum
1368517392.728523 - 192.168.0.2 0 192.168.0.1 1080 Tunnel::SOCKS Tunnel::DISCOVER
#close 2015-02-05-16-13-12

View file

@ -3,8 +3,8 @@
#empty_field (empty)
#unset_field -
#path socks
#open 2013-08-26-19-04-20
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version user status request.host request.name request_p bound.host bound.name bound_p
#types time string addr port addr port count string string addr string port addr string port
1340213015.276495 CjhGID4nQcgTWjvg4c 10.0.0.55 53994 60.190.189.214 8124 5 - succeeded - www.osnews.com 80 192.168.0.31 - 2688
#close 2013-08-26-19-04-20
#open 2015-02-05-17-39-14
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version user password status request.host request.name request_p bound.host bound.name bound_p
#types time string addr port addr port count string string string addr string port addr string port
1340213015.276495 CjhGID4nQcgTWjvg4c 10.0.0.55 53994 60.190.189.214 8124 5 - - succeeded - www.osnews.com 80 192.168.0.31 - 2688
#close 2015-02-05-17-39-14

View file

@ -3,8 +3,8 @@
#empty_field (empty)
#unset_field -
#path socks
#open 2013-08-26-19-04-20
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version user status request.host request.name request_p bound.host bound.name bound_p
#types time string addr port addr port count string string addr string port addr string port
1340113261.914619 CXWv6p3arKYeMETxOg 10.0.0.50 59580 85.194.84.197 1080 5 - succeeded - www.google.com 443 0.0.0.0 - 443
#close 2013-08-26-19-04-20
#open 2015-02-05-17-39-29
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version user password status request.host request.name request_p bound.host bound.name bound_p
#types time string addr port addr port count string string string addr string port addr string port
1340113261.914619 CXWv6p3arKYeMETxOg 10.0.0.50 59580 85.194.84.197 1080 5 - - succeeded - www.google.com 443 0.0.0.0 - 443
#close 2015-02-05-17-39-29

Binary file not shown.

View file

@ -0,0 +1,6 @@
# @TEST-EXEC: bro -r $TRACES/http/get.trace %INPUT
# @TEST-EXEC: btest-diff files.log
@load frameworks/files/hash-all-files
redef default_file_bof_buffer_size=5000;

View file

@ -0,0 +1,5 @@
# @TEST-EXEC: bro -r $TRACES/socks-auth.pcap %INPUT
# @TEST-EXEC: btest-diff socks.log
# @TEST-EXEC: btest-diff tunnel.log
@load base/protocols/socks

View file

@ -3,19 +3,25 @@
# A diff canonifier that removes all X.509 Distinguished Name subject fields
# because that output can differ depending on installed OpenSSL version.
BEGIN { FS="\t"; OFS="\t"; s_col = -1; i_col = -1; cs_col = -1; ci_col = -1 }
BEGIN { FS="\t"; OFS="\t"; s_col = -1; i_col = -1; is_col = -1; cs_col = -1; ci_col = -1; cert_subj_col = -1; cert_issuer_col = -1 }
/^#fields/ {
for ( i = 2; i < NF; ++i )
{
if ( $i == "subject" )
s_col = i-1;
if ( $i == "issuer_subject" )
if ( $i == "issuer" )
i_col = i-1;
if ( $i == "issuer_subject" )
is_col = i-1;
if ( $i == "client_subject" )
cs_col = i-1;
if ( $i == "client_issuer_subject" )
if ( $i == "client_issuer" )
ci_col = i-1;
if ( $i == "certificate.subject" )
cert_subj_col = i-1;
if ( $i == "certificate.issuer" )
cert_issuer_col = i-1;
}
}
@ -31,6 +37,12 @@ i_col >= 0 {
$i_col = "+";
}
is_col >= 0 {
if ( $is_col != "-" )
# Mark that it's set, but ignore content.
$is_col = "+";
}
cs_col >= 0 {
if ( $cs_col != "-" )
# Mark that it's set, but ignore content.
@ -43,6 +55,18 @@ ci_col >= 0 {
$ci_col = "+";
}
cert_subj_col >= 0 {
if ( $cert_subj_col != "-" )
# Mark that it's set, but ignore content.
$cert_subj_col = "+";
}
cert_issuer_col >= 0 {
if ( $cert_issuer_col != "-" )
# Mark that it's set, but ignore content.
$cert_issuer_col = "+";
}
{
print;
}