##! Notice extension that mails out a pretty-printed version of alarm.log ##! in regular intervals, formatted for better human readability. If activated, ##! that replaces the default summary mail having the raw log output. @load base/frameworks/cluster @load ../main module Notice; export { ## Activate pretty-printed alarm summaries. const pretty_print_alarms = T &redef; ## Address to send the pretty-printed reports to. Default if not set is ## :bro:id:`Notice::mail_dest`. ## ## Note that this is overridden by the BroControl MailAlarmsTo option. const mail_dest_pretty_printed = "" &redef; ## If an address from one of these networks is reported, we mark ## the entry with an additional quote symbol (i.e., ">"). Many MUAs ## then highlight such lines differently. global flag_nets: set[subnet] &redef; ## Function that renders a single alarm. Can be overridden. global pretty_print_alarm: function(out: file, n: Info) &redef; ## Force generating mail file, even if reading from traces or no mail ## destination is defined. This is mainly for testing. global force_email_summaries = F &redef; } # We maintain an old-style file recording the pretty-printed alarms. const pp_alarms_name = "alarm-mail.txt"; global pp_alarms: file; global pp_alarms_open: bool = F; # Returns True if pretty-printed alarm summaries are activated. function want_pp() : bool { if ( force_email_summaries ) return T; return (pretty_print_alarms && ! reading_traces() && (mail_dest != "" || mail_dest_pretty_printed != "")); } # Opens and intializes the output file. function pp_open() { if ( pp_alarms_open ) return; pp_alarms_open = T; pp_alarms = open(pp_alarms_name); } # Closes and mails out the current output file. function pp_send(rinfo: Log::RotationInfo) { if ( ! pp_alarms_open ) return; write_file(pp_alarms, "\n\n--\n[Automatically generated]\n\n"); close(pp_alarms); pp_alarms_open = F; local from = strftime("%H:%M:%S", rinfo$open); local to = strftime("%H:%M:%S", rinfo$close); local subject = fmt("Alarm summary from %s-%s", from, to); local dest = mail_dest_pretty_printed != "" ? mail_dest_pretty_printed : mail_dest; if ( dest == "" ) # No mail destination configured, just leave the file alone. This is mainly for # testing. return; local headers = email_headers(subject, dest); local header_name = pp_alarms_name + ".tmp"; local header = open(header_name); write_file(header, headers + "\n"); close(header); system(fmt("/bin/cat %s %s | %s -t -oi && /bin/rm -f %s %s", header_name, pp_alarms_name, sendmail, header_name, pp_alarms_name)); } # Postprocessor function that triggers the email. function pp_postprocessor(info: Log::RotationInfo): bool { if ( want_pp() ) pp_send(info); return T; } event bro_init() { if ( ! want_pp() ) return; # This replaces the standard non-pretty-printing filter. Log::add_filter(Notice::ALARM_LOG, [$name="alarm-mail", $writer=Log::WRITER_NONE, $interv=Log::default_mail_alarms_interval, $postprocessor=pp_postprocessor]); } hook notice(n: Notice::Info) &priority=-5 { if ( ! want_pp() ) return; if ( ACTION_ALARM !in n$actions ) return; if ( ! pp_alarms_open ) pp_open(); pretty_print_alarm(pp_alarms, n); } function do_msg(out: file, n: Info, line1: string, line2: string, line3: string, host1: addr, name1: string, host2: addr, name2: string) { local country = ""; @ifdef ( Notice::ACTION_ADD_GEODATA ) # Make tests happy, cyclic dependency. if ( n?$remote_location && n$remote_location?$country_code ) country = fmt(" (remote location %s)", n$remote_location$country_code); @endif line1 = cat(line1, country); local resolved = ""; if ( host1 != 0.0.0.0 ) resolved = fmt("%s # %s = %s", resolved, host1, name1); if ( host2 != 0.0.0.0 ) resolved = fmt("%s %s = %s", resolved, host2, name2); print out, line1; print out, line2; if ( line3 != "" ) print out, line3; if ( resolved != "" ) print out, resolved; print out, ""; } # Default pretty-printer. function pretty_print_alarm(out: file, n: Info) { local pdescr = ""; @if ( Cluster::is_enabled() ) pdescr = "local"; if ( n?$src_peer ) pdescr = n$src_peer?$descr ? n$src_peer$descr : fmt("%s", n$src_peer$host); pdescr = fmt("<%s> ", pdescr); @endif local msg = fmt( "%s%s", pdescr, n$msg); local who = ""; local h1 = 0.0.0.0; local h2 = 0.0.0.0; local orig_p = ""; local resp_p = ""; if ( n?$id ) { h1 = n$id$orig_h; h2 = n$id$resp_h; who = fmt("%s:%s -> %s:%s", h1, n$id$orig_p, h2, n$id$resp_p); } else if ( n?$src && n?$dst ) { h1 = n$src; h2 = n$dst; who = fmt("%s -> %s", h1, h2); } else if ( n?$src ) { h1 = n$src; who = fmt("%s%s", h1, (n?$p ? fmt(":%s", n$p) : "")); } if ( n?$uid ) who = fmt("%s (uid %s)", who, n$uid ); local flag = (h1 in flag_nets || h2 in flag_nets); local line1 = fmt(">%s %D %s %s", (flag ? ">" : " "), network_time(), n$note, who); local line2 = fmt(" %s", msg); local line3 = n?$sub ? fmt(" %s", n$sub) : ""; if ( h1 == 0.0.0.0 ) { do_msg(out, n, line1, line2, line3, h1, "", h2, ""); return; } if ( reading_traces() ) { do_msg(out, n, line1, line2, line3, h1, "", h2, ""); return; } when ( local h1name = lookup_addr(h1) ) { if ( h2 == 0.0.0.0 ) { do_msg(out, n, line1, line2, line3, h1, h1name, h2, ""); return; } when ( local h2name = lookup_addr(h2) ) { do_msg(out, n, line1, line2, line3, h1, h1name, h2, h2name); return; } timeout 5secs { do_msg(out, n, line1, line2, line3, h1, h1name, h2, "(dns timeout)"); return; } } timeout 5secs { if ( h2 == 0.0.0.0 ) { do_msg(out, n, line1, line2, line3, h1, "(dns timeout)", h2, ""); return; } when ( local h2name_ = lookup_addr(h2) ) { do_msg(out, n, line1, line2, line3, h1, "(dns timeout)", h2, h2name_); return; } timeout 5secs { do_msg(out, n, line1, line2, line3, h1, "(dns timeout)", h2, "(dns timeout)"); return; } } }