mirror of
https://github.com/zeek/zeek.git
synced 2025-10-02 06:38:20 +00:00

This is obviously a change that break backwards-compatibility. I hope I caught all cases where vectors are used ... I've completely removed the VECTOR_MIN constant. Turns out that was already not working: some code pieces were nevertheless hard-coding the 1-based indexing ...
566 lines
13 KiB
Text
566 lines
13 KiB
Text
# $Id:$
|
|
|
|
@load conn
|
|
@load notice
|
|
@load weird
|
|
|
|
module IrcBot;
|
|
|
|
export {
|
|
global detailed_log = open_log_file("irc.detailed") &redef;
|
|
global bot_log = open_log_file("irc-bots") &redef;
|
|
|
|
global summary_interval = 1 min &redef;
|
|
|
|
global detailed_logging = T &redef;
|
|
global content_dir = "irc-bots" &redef;
|
|
|
|
global bot_nicks =
|
|
/^\[([^\]]+\|)+[0-9]{2,}]/ # [DEU|XP|L|00]
|
|
| /^\[[^ ]+\]([^ ]+\|)+([0-9a-zA-Z-]+)/ # [0]CHN|3436036 [DEU][1]3G-QE
|
|
| /^DCOM[0-9]+$/ # DCOM7845
|
|
| /^\{[A-Z]+\}-[0-9]+/ # {XP}-5021040
|
|
| /^\[[0-9]+-[A-Z0-9]+\][a-z]+/ # [0058-X2]wpbnlgwf
|
|
| /^\[[a-zA-Z0-9]\]-[a-zA-Z0-9]+$/ # [SD]-743056826
|
|
| /^[a-z]+[A-Z]+-[0-9]{5,}$/
|
|
| /^[A-Z]{3}-[0-9]{4}/ # ITD-1119
|
|
;
|
|
|
|
global bot_cmds =
|
|
/(^| *)[.?#!][^ ]{0,5}(scan|ndcass|download|cvar\.|execute|update|dcom|asc|scanall) /
|
|
| /(^| +\]\[ +)\* (ipscan|wormride)/
|
|
| /(^| *)asn1/
|
|
;
|
|
|
|
global skip_msgs =
|
|
/.*AUTH .*/
|
|
| /.*\*\*\* Your host is .*/
|
|
| /.*\*\*\* If you are having problems connecting .*/
|
|
;
|
|
|
|
redef enum Notice += {
|
|
IrcBotServerFound,
|
|
IrcBotClientFound,
|
|
};
|
|
|
|
type channel: record {
|
|
name: string;
|
|
passwords: set[string];
|
|
topic: string &default="";
|
|
topic_history: vector of string;
|
|
};
|
|
|
|
type bot_client: record {
|
|
host: addr;
|
|
p: port;
|
|
nick: string &default="";
|
|
user: string &default="";
|
|
realname: string &default="";
|
|
channels: table[string] of channel;
|
|
servers: set[addr] &optional;
|
|
first_seen: time;
|
|
last_seen: time;
|
|
};
|
|
|
|
type bot_server: record {
|
|
host: addr;
|
|
p: set[port];
|
|
clients: table[addr] of bot_client;
|
|
global_users: string &default="";
|
|
passwords: set[string];
|
|
channels: table[string] of channel;
|
|
first_seen: time;
|
|
last_seen: time;
|
|
};
|
|
|
|
type bot_conn: record {
|
|
client: bot_client;
|
|
server: bot_server;
|
|
conn: connection;
|
|
fd: file;
|
|
ircx: bool &default=F;
|
|
};
|
|
|
|
# We keep three sets of clients/servers:
|
|
# (1) tables containing all IRC clients/servers
|
|
# (2) sets containing potential bot hosts
|
|
# (3) sets containing confirmend bot hosts
|
|
#
|
|
# Hosts are confirmed when a connection is established between
|
|
# potential bot hosts.
|
|
#
|
|
# FIXME: (1) should really be moved into the general IRC script.
|
|
|
|
global expire_server:
|
|
function(t: table[addr] of bot_server, idx: addr): interval;
|
|
global expire_client:
|
|
function(t: table[addr] of bot_client, idx: addr): interval;
|
|
|
|
global servers: table[addr] of bot_server &write_expire=24 hrs
|
|
&expire_func=expire_server &persistent;
|
|
global clients: table[addr] of bot_client &write_expire=24 hrs
|
|
&expire_func=expire_client &persistent;
|
|
|
|
global potential_bot_clients: set[addr] &persistent;
|
|
global potential_bot_servers: set[addr] &persistent;
|
|
global confirmed_bot_clients: set[addr] &persistent;
|
|
global confirmed_bot_servers: set[addr] &persistent;
|
|
|
|
# All IRC connections.
|
|
global conns: table[conn_id] of bot_conn &persistent;
|
|
|
|
# Connections between confirmed hosts.
|
|
global bot_conns: set[conn_id] &persistent;
|
|
|
|
# Helper functions for readable output.
|
|
global strset_to_str: function(s: set[string]) : string;
|
|
global portset_to_str: function(s: set[port]) : string;
|
|
global addrset_to_str: function(s: set[addr]) : string;
|
|
}
|
|
|
|
function strset_to_str(s: set[string]) : string
|
|
{
|
|
if ( |s| == 0 )
|
|
return "<none>";
|
|
|
|
local r = "";
|
|
for ( i in s )
|
|
{
|
|
if ( r != "" )
|
|
r = cat(r, ",");
|
|
r = cat(r, fmt("\"%s\"", i));
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
function portset_to_str(s: set[port]) : string
|
|
{
|
|
if ( |s| == 0 )
|
|
return "<none>";
|
|
|
|
local r = "";
|
|
for ( i in s )
|
|
{
|
|
if ( r != "" )
|
|
r = cat(r, ",");
|
|
r = cat(r, fmt("%d", i));
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
function addrset_to_str(s: set[addr]) : string
|
|
{
|
|
if ( |s| == 0 )
|
|
return "<none>";
|
|
|
|
local r = "";
|
|
for ( i in s )
|
|
{
|
|
if ( r != "" )
|
|
r = cat(r, ",");
|
|
r = cat(r, fmt("%s", i));
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
function fmt_time(t: time) : string
|
|
{
|
|
return strftime("%y-%m-%d-%H-%M-%S", t);
|
|
}
|
|
|
|
event print_bot_state()
|
|
{
|
|
local bot_summary_log = open_log_file("irc-bots.summary");
|
|
disable_print_hook(bot_summary_log);
|
|
|
|
print bot_summary_log, "---------------------------";
|
|
print bot_summary_log, strftime("%y-%m-%d-%H-%M-%S", network_time());
|
|
print bot_summary_log, "---------------------------";
|
|
print bot_summary_log;
|
|
print bot_summary_log, "Known servers";
|
|
|
|
for ( h in confirmed_bot_servers )
|
|
{
|
|
local s = servers[h];
|
|
|
|
print bot_summary_log,
|
|
fmt(" %s %s - clients: %d ports %s password(s) %s last-seen %s first-seen %s global-users %s",
|
|
(is_local_addr(s$host) ? "L" : "R"),
|
|
s$host, length(s$clients), portset_to_str(s$p),
|
|
strset_to_str(s$passwords),
|
|
fmt_time(s$last_seen), fmt_time(s$first_seen),
|
|
s$global_users);
|
|
|
|
for ( name in s$channels )
|
|
{
|
|
local ch = s$channels[name];
|
|
print bot_summary_log,
|
|
fmt(" channel %s: topic \"%s\", password(s) %s",
|
|
ch$name, ch$topic,
|
|
strset_to_str(ch$passwords));
|
|
}
|
|
}
|
|
|
|
print bot_summary_log, "\nKnown clients";
|
|
|
|
for ( h in confirmed_bot_clients )
|
|
{
|
|
local c = clients[h];
|
|
print bot_summary_log,
|
|
fmt(" %s %s - server(s) %s user %s nick %s realname %s last-seen %s first-seen %s",
|
|
(is_local_addr(h) ? "L" : "R"), h,
|
|
addrset_to_str(c$servers),
|
|
c$user, c$nick, c$realname,
|
|
fmt_time(c$last_seen), fmt_time(c$first_seen));
|
|
}
|
|
|
|
close(bot_summary_log);
|
|
|
|
if ( summary_interval != 0 secs )
|
|
schedule summary_interval { print_bot_state() };
|
|
}
|
|
|
|
event bro_init()
|
|
{
|
|
if ( summary_interval != 0 secs )
|
|
schedule summary_interval { print_bot_state() };
|
|
}
|
|
|
|
function do_log_force(c: connection, msg: string)
|
|
{
|
|
local id = c$id;
|
|
print bot_log, fmt("%.6f %s:%d > %s:%d %s %s",
|
|
network_time(), id$orig_h, id$orig_p,
|
|
id$resp_h, id$resp_p, c$addl, msg);
|
|
}
|
|
|
|
function do_log(c: connection, msg: string)
|
|
{
|
|
if ( c$id !in bot_conns )
|
|
return;
|
|
|
|
do_log_force(c, msg);
|
|
}
|
|
|
|
function log_msg(c: connection, cmd: string, prefix: string, msg: string)
|
|
{
|
|
if ( skip_msgs in msg )
|
|
return;
|
|
|
|
do_log(c, fmt("MSG command=%s prefix=%s msg=\"%s\"", cmd, prefix, msg));
|
|
}
|
|
|
|
function update_timestamps(c: connection) : bot_conn
|
|
{
|
|
local conn = conns[c$id];
|
|
|
|
conn$client$last_seen = network_time();
|
|
conn$server$last_seen = network_time();
|
|
|
|
# To prevent the set of entries from premature expiration,
|
|
# we need to make a write access (can't use read_expire as we
|
|
# iterate over the entries on a regular basis).
|
|
clients[c$id$orig_h] = conn$client;
|
|
servers[c$id$resp_h] = conn$server;
|
|
|
|
return conn;
|
|
}
|
|
|
|
function add_server(c: connection) : bot_server
|
|
{
|
|
local s_h = c$id$resp_h;
|
|
|
|
if ( s_h in servers )
|
|
return servers[s_h];
|
|
|
|
local empty_table1: table[addr] of bot_client;
|
|
local empty_table2: table[string] of channel;
|
|
local empty_set: set[string];
|
|
local empty_set2: set[port];
|
|
|
|
local server = [$host=s_h, $p=empty_set2, $clients=empty_table1,
|
|
$channels=empty_table2, $passwords=empty_set,
|
|
$first_seen=network_time(), $last_seen=network_time()];
|
|
servers[s_h] = server;
|
|
|
|
return server;
|
|
}
|
|
|
|
function add_client(c: connection) : bot_client
|
|
{
|
|
local c_h = c$id$orig_h;
|
|
|
|
if ( c_h in clients )
|
|
return clients[c_h];
|
|
|
|
local empty_table: table[string] of channel;
|
|
local empty_set: set[addr];
|
|
local client = [$host=c_h, $p=c$id$resp_p, $servers=empty_set,
|
|
$channels=empty_table, $first_seen=network_time(),
|
|
$last_seen=network_time()];
|
|
clients[c_h] = client;
|
|
|
|
return client;
|
|
}
|
|
|
|
function check_bot_conn(c: connection)
|
|
{
|
|
if ( c$id in bot_conns )
|
|
return;
|
|
|
|
local client = c$id$orig_h;
|
|
local server = c$id$resp_h;
|
|
|
|
if ( client !in potential_bot_clients || server !in potential_bot_servers )
|
|
return;
|
|
|
|
# New confirmed bot_conn.
|
|
|
|
add bot_conns[c$id];
|
|
|
|
if ( server !in confirmed_bot_servers )
|
|
{
|
|
NOTICE([$note=IrcBotServerFound, $src=server, $p=c$id$resp_p, $conn=c,
|
|
$msg=fmt("ircbot server found: %s:%d", server, $p=c$id$resp_p)]);
|
|
add confirmed_bot_servers[server];
|
|
}
|
|
|
|
if ( client !in confirmed_bot_clients )
|
|
{
|
|
NOTICE([$note=IrcBotClientFound, $src=client, $p=c$id$orig_p, $conn=c,
|
|
$msg=fmt("ircbot client found: %s:%d", client, $p=c$id$orig_p)]);
|
|
add confirmed_bot_clients[client];
|
|
}
|
|
}
|
|
|
|
function get_conn(c: connection) : bot_conn
|
|
{
|
|
local conn: bot_conn;
|
|
|
|
if ( c$id in conns )
|
|
{
|
|
check_bot_conn(c);
|
|
return update_timestamps(c);
|
|
}
|
|
|
|
local c_h = c$id$orig_h;
|
|
local s_h = c$id$resp_h;
|
|
|
|
local client : bot_client;
|
|
local server : bot_server;
|
|
|
|
if ( c_h in clients )
|
|
client = clients[c_h];
|
|
else
|
|
client = add_client(c);
|
|
|
|
if ( s_h in servers )
|
|
server = servers[s_h];
|
|
else
|
|
server = add_server(c);
|
|
|
|
server$clients[c_h] = client;
|
|
add server$p[c$id$resp_p];
|
|
add client$servers[s_h];
|
|
|
|
conn$server = server;
|
|
conn$client = client;
|
|
conn$conn = c;
|
|
conns[c$id] = conn;
|
|
update_timestamps(c);
|
|
|
|
return conn;
|
|
}
|
|
|
|
function expire_server(t: table[addr] of bot_server, idx: addr): interval
|
|
{
|
|
local server = t[idx];
|
|
for ( c in server$clients )
|
|
{
|
|
local client = server$clients[c];
|
|
delete client$servers[idx];
|
|
}
|
|
|
|
delete potential_bot_servers[idx];
|
|
delete confirmed_bot_servers[idx];
|
|
return 0secs;
|
|
}
|
|
|
|
function expire_client(t: table[addr] of bot_client, idx: addr): interval
|
|
{
|
|
local client = t[idx];
|
|
for ( s in client$servers )
|
|
if ( s in servers )
|
|
delete servers[s]$clients[idx];
|
|
delete potential_bot_clients[idx];
|
|
delete confirmed_bot_clients[idx];
|
|
return 0secs;
|
|
}
|
|
|
|
function remove_connection(c: connection)
|
|
{
|
|
local conn = conns[c$id];
|
|
delete conns[c$id];
|
|
delete bot_conns[c$id];
|
|
}
|
|
|
|
event connection_state_remove(c: connection)
|
|
{
|
|
if ( c$id !in conns )
|
|
return;
|
|
|
|
remove_connection(c);
|
|
}
|
|
|
|
event bro_init()
|
|
{
|
|
set_buf(detailed_log, F);
|
|
set_buf(bot_log, F);
|
|
}
|
|
|
|
event irc_client(c: connection, prefix: string, data: string)
|
|
{
|
|
if ( detailed_logging )
|
|
print detailed_log, fmt("%.6f %s > (%s) %s", network_time(), id_string(c$id), prefix, data);
|
|
|
|
local conn = get_conn(c);
|
|
|
|
if ( data == /^ *[iI][rR][cC][xX] *$/ )
|
|
conn$ircx = T;
|
|
}
|
|
|
|
event irc_server(c: connection, prefix: string, data: string)
|
|
{
|
|
if ( detailed_logging )
|
|
print detailed_log, fmt("%.6f %s < (%s) %s", network_time(), id_string(c$id), prefix, data);
|
|
|
|
local conn = get_conn(c);
|
|
}
|
|
|
|
event irc_user_message(c: connection, user: string, host: string, server: string, real_name: string)
|
|
{
|
|
local conn = get_conn(c);
|
|
conn$client$user = user;
|
|
conn$client$realname = real_name;
|
|
|
|
do_log(c, fmt("USER user=%s host=%s server=%s real_name=%s", user, host, server, real_name));
|
|
}
|
|
|
|
function get_channel(conn: bot_conn, channel: string) : channel
|
|
{
|
|
if ( channel in conn$server$channels )
|
|
return conn$server$channels[channel];
|
|
else
|
|
{
|
|
local empty_set: set[string];
|
|
local empty_vec: vector of string;
|
|
local ch = [$name=channel, $passwords=empty_set, $topic_history=empty_vec];
|
|
conn$server$channels[ch$name] = ch;
|
|
return ch;
|
|
}
|
|
}
|
|
|
|
event irc_join_message(c: connection, info_list: irc_join_list)
|
|
{
|
|
local conn = get_conn(c);
|
|
|
|
for ( i in info_list )
|
|
{
|
|
local ch = get_channel(conn, i$channel);
|
|
|
|
if ( i$password != "" )
|
|
add ch$passwords[i$password];
|
|
|
|
conn$client$channels[ch$name] = ch;
|
|
|
|
do_log(c, fmt("JOIN channel=%s password=%s", i$channel, i$password));
|
|
}
|
|
}
|
|
|
|
global urls: set[string] &read_expire = 7 days &persistent;
|
|
|
|
event http_request(c: connection, method: string, original_URI: string,
|
|
unescaped_URI: string, version: string)
|
|
{
|
|
if ( original_URI in urls )
|
|
do_log_force(c, fmt("Request for URL %s", original_URI));
|
|
}
|
|
|
|
event irc_channel_topic(c: connection, channel: string, topic: string)
|
|
{
|
|
if ( bot_cmds in topic )
|
|
{
|
|
do_log_force(c, fmt("Matching TOPIC %s", topic));
|
|
add potential_bot_servers[c$id$resp_h];
|
|
}
|
|
|
|
local conn = get_conn(c);
|
|
|
|
local ch = get_channel(conn, channel);
|
|
ch$topic_history[|ch$topic_history|] = ch$topic;
|
|
ch$topic = topic;
|
|
|
|
if ( c$id in bot_conns )
|
|
{
|
|
do_log(c, fmt("TOPIC channel=%s topic=\"%s\"", channel, topic));
|
|
|
|
local s = split(topic, / /);
|
|
for ( i in s )
|
|
{
|
|
local w = s[i];
|
|
if ( w == /[a-zA-Z]+:\/\/.*/ )
|
|
{
|
|
add urls[w];
|
|
do_log(c, fmt("URL channel=%s url=\"%s\"",
|
|
channel, w));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
event irc_nick_message(c: connection, who: string, newnick: string)
|
|
{
|
|
if ( bot_nicks in newnick )
|
|
{
|
|
do_log_force(c, fmt("Matching NICK %s", newnick));
|
|
add potential_bot_clients[c$id$orig_h];
|
|
}
|
|
|
|
local conn = get_conn(c);
|
|
conn$client$nick = newnick;
|
|
|
|
do_log(c, fmt("NICK who=%s nick=%s", who, newnick));
|
|
}
|
|
|
|
event irc_password_message(c: connection, password: string)
|
|
{
|
|
local conn = get_conn(c);
|
|
add conn$server$passwords[password];
|
|
|
|
do_log(c, fmt("PASS password=%s", password));
|
|
}
|
|
|
|
event irc_privmsg_message(c: connection, source: string, target: string,
|
|
message: string)
|
|
{
|
|
log_msg(c, "privmsg", source, fmt("->%s %s", target, message));
|
|
}
|
|
|
|
event irc_notice_message(c: connection, source: string, target: string,
|
|
message: string)
|
|
{
|
|
log_msg(c, "notice", source, fmt("->%s %s", target, message));
|
|
}
|
|
|
|
event irc_global_users(c: connection, prefix: string, msg: string)
|
|
{
|
|
local conn = get_conn(c);
|
|
|
|
# Better would be to parse the message to extract the counts.
|
|
conn$server$global_users = msg;
|
|
|
|
log_msg(c, "globalusers", prefix, msg);
|
|
}
|