Created
March 12, 2014 23:29
-
-
Save arekinath/9518891 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-module(ip_tools). | |
-export([next/1, first/2, last/2, from_str/1, to_str/1, range/2]). | |
next({A,B,C,D}) when D < 255 -> {A,B,C,D+1}; | |
next({A,B,C,_D}) when C < 255 -> {A,B,C+1,0}; | |
next({A,B,_C,_D}) when B < 255 -> {A,B+1,0,0}; | |
next({A,_B,_C,_D}) when A < 255 -> {A+1,0,0,0}. | |
first(Tuple, Bits) when is_tuple(Tuple) -> | |
list_to_tuple(first(tuple_to_list(Tuple), Bits)); | |
first([Next | Rest], Bits) when Bits > 8 -> | |
[Next | first(Rest, Bits - 8)]; | |
first([Next | Rest], Bits) -> | |
[Next band (bnot ((1 bsl (8 - Bits)) - 1)) | [0 || _ <- Rest]]. | |
last(Tuple, Bits) when is_tuple(Tuple) -> | |
list_to_tuple(last(tuple_to_list(Tuple), Bits)); | |
last([Next | Rest], Bits) when Bits > 8 -> | |
[Next | last(Rest, Bits - 8)]; | |
last([Next | Rest], Bits) -> | |
[Next bor ((1 bsl (8 - Bits)) - 1) | [255 || _ <- Rest]]. | |
from_str(Str) -> | |
[A,B,C,D] = [list_to_integer(X) || X <- string:tokens(Str, ".")], | |
{A,B,C,D}. | |
to_str(Ip) -> | |
string:join([integer_to_list(X) || X <- tuple_to_list(Ip)], "."). | |
range(From, To) when From =:= To -> [From]; | |
range(From, To) -> | |
[From | range(next(From), To)]. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env escript | |
%%! -smp enable verbose +K true +A 16 | |
-mode(compile). | |
main([Cidr | Rest]) -> | |
[application:start(X) || X <- [inets,crypto,asn1,public_key,ssh]], | |
{ok, _} = ssh_http:start_link("yolog.zones.eait.uq.edu.au", 22, "user", "..."), | |
Pid = spawn_link(fun bulker_loop/0), | |
true = register(bulker, Pid), | |
do_range([Cidr | Rest]); | |
main(_) -> | |
io:format("usage: scanscan.erl <cidr> [cidr cidr ...]\n"), | |
io:format("scans for things and uploads to elasticsearch\n"), | |
halt(1). | |
do_range([]) -> ok; | |
do_range([Cidr | Rest]) -> | |
[IpStr, BitsStr] = string:tokens(Cidr, "/"), | |
Ip = ip_tools:from_str(IpStr), | |
Bits = list_to_integer(BitsStr), | |
First = ip_tools:first(Ip, Bits), | |
Last = ip_tools:last(Ip, Bits), | |
io:format("[scanning from ~w to ~w]\n", [First, Last]), | |
GS = {fun (I) when I =:= Last -> done; (I) -> ip_tools:next(I) end, First}, | |
pmap(fun(I = {_, _, _, N}) -> | |
if ((N rem 32) == 0) -> | |
io:format("\rdone up to ~-30w", [I]); | |
true -> ok end, | |
check(I) | |
end, GS, 512), | |
io:format("\n"), | |
do_range(Rest). | |
check(Ip) -> | |
Data = thread([ | |
fun(H) -> | |
TS = os:timestamp(), | |
lists:keymerge(1, H, [ | |
{ip, list_to_binary(ip_tools:to_str(Ip))}, | |
{timestamp, list_to_binary(ts_to_jstime(TS))} | |
]) | |
end, | |
fun(H) -> | |
T1 = os:timestamp(), | |
Opts = [{nameservers, [{Ip, 53}]}, {recurse, true}, {timeout, 1000}, {retry, 2}], | |
lists:keymerge(1, H, case inet_res:lookup("www.google.com", in, a, Opts) of | |
[] -> [{recursive_dns, [{open, false}]}]; | |
_Res -> | |
T2 = os:timestamp(), | |
[{recursive_dns, [{open, true}, {probe_time, timer:now_diff(T2, T1)}]}] | |
end) | |
end, | |
fun(H) -> | |
lists:keymerge(1, H, [{ssh, port_banner(Ip, 22)}]) | |
end, | |
fun(H) -> | |
lists:keymerge(1, H, [{vnc, [ | |
{'0', vnc_banner(Ip, 5900)}, | |
{'1', vnc_banner(Ip, 5901)}]}]) | |
end, | |
fun(H) -> | |
lists:keymerge(1, H, [{'tcp32764', port_banner(Ip, 32764, 500)}]) | |
end, | |
fun(H) -> | |
lists:keymerge(1, H, case gen_tcp:connect(Ip, 23, [binary, {active, true}], 1000) of | |
{ok, Sock} -> [{telnet, recv_banner(Sock, <<>>, false)}]; | |
_ -> [{telnet, [{open, false}]}] | |
end) | |
end, | |
fun(H) -> | |
lists:keymerge(1, H, case gen_tcp:connect(Ip, 3306, [binary, {active, true}], 1000) of | |
{ok, Sock} -> [{mysql, recv_mysql(Sock, <<>>)}]; | |
_ -> [{mysql, [{open, false}]}] | |
end) | |
end, | |
fun(H) -> | |
case inet_res:lookup(Ip, in, ptr, [{timeout, 500}, {retry, 2}]) of | |
[Host | _] when is_list(Host) -> [{hostname, list_to_binary(Host)} | H]; | |
_ -> H | |
end | |
end | |
], []), | |
Json = to_json(Data), | |
bulker ! {add_data, self(), Ip, Json}, | |
receive {bulker, ok} -> ok end. | |
port_banner(Ip, Port) -> port_banner(Ip, Port, 1000). | |
port_banner(Ip, Port, Timeout) -> | |
case gen_tcp:connect(Ip, Port, [binary, {packet, line}, {active, once}], Timeout) of | |
{ok, Sock} -> | |
receive | |
{tcp, Sock, Line} -> | |
gen_tcp:close(Sock), | |
[{open, true}, {banner, Line}] | |
after Timeout -> | |
gen_tcp:close(Sock), | |
[{open, true}] | |
end; | |
_ -> [{open, false}] | |
end. | |
vnc_banner(Ip, Port) -> | |
case gen_tcp:connect(Ip, Port, [binary, {packet, line}, {active, once}], 1000) of | |
{ok, Sock} -> | |
receive | |
{tcp, Sock, Banner} -> | |
ok = gen_tcp:send(Sock, <<"RFB 003.003\n">>), | |
ok = inet:setopts(Sock, [{packet, raw}]), | |
R = case gen_tcp:recv(Sock, 4) of | |
{ok, <<0:32/big>>} -> | |
{ok, <<ReasonLen:32/big>>} = gen_tcp:recv(Sock, 4), | |
{ok, ReasonBin} = gen_tcp:recv(Sock, ReasonLen), | |
[{reject_reason, ReasonBin}]; | |
{ok, <<1:32/big>>} -> [{open, true}, {banner, Banner}, {required_auth, false}]; | |
{ok, <<N:32/big>>} when N > 1 -> [{open, true}, {banner, Banner}, {required_auth, true}]; | |
_ -> [{open, false}] | |
end, | |
gen_tcp:close(Sock), R | |
after 1000 -> | |
gen_tcp:close(Sock), | |
[{open, false}] | |
end; | |
_ -> [{open, false}] | |
end. | |
recv_mysql(Sock, Buffer) -> | |
receive | |
{tcp, Sock, Data} -> | |
NewBuffer = <<Buffer/binary, Data/binary>>, | |
case NewBuffer of | |
<<Len:32/little, Pkt:Len/binary-unit:8, _/binary>> -> | |
gen_tcp:close(Sock), | |
case Pkt of | |
<<10, Info/binary>> -> | |
[Sign, Rest] = binary:split(Info, <<0>>), | |
<<Id:32/little, _Rest2/binary>> = Rest, | |
[{open, true}, {version, Sign}, {id, Id}]; | |
_ -> [{open, false}] | |
end; | |
_ when byte_size(NewBuffer) < 4096 -> | |
recv_mysql(Sock, NewBuffer); | |
_ -> | |
gen_tcp:close(Sock), | |
[{open, false}] | |
end; | |
{tcp_closed, Sock} -> | |
[{open, false}] | |
after 2000 -> | |
gen_tcp:close(Sock), | |
[{open, false}] | |
end. | |
-define(IAC, 255). | |
-define(TN_WILL, 251). | |
-define(TN_WONT, 252). | |
-define(TN_DO, 253). | |
-define(TN_DONT, 254). | |
recv_banner(Sock, SoFar, UsedOpts) when byte_size(SoFar) > 4096 -> | |
gen_tcp:close(Sock), | |
[{open, true}, {banner, SoFar}, | |
{ended_at, <<"toolong">>}, {used_rfc854, UsedOpts}]; | |
recv_banner(Sock, SoFar, UsedOpts) -> | |
receive | |
{tcp, Sock, <<?IAC, Cmd, Opt, Rest/binary>>} -> | |
UsedOpts1 = case Cmd of | |
?TN_WILL -> gen_tcp:send(Sock, <<?IAC, ?TN_WONT, Opt>>), true; | |
?TN_DO -> gen_tcp:send(Sock, <<?IAC, ?TN_DONT, Opt>>), true; | |
_ -> UsedOpts | |
end, | |
self() ! {tcp, Sock, Rest}, | |
recv_banner(Sock, SoFar, UsedOpts1); | |
{tcp, Sock, Data} -> | |
recv_banner(Sock, <<SoFar/binary, Data/binary>>, UsedOpts); | |
{tcp_closed, Sock} -> | |
[{open, true}, {banner, SoFar}, | |
{ended_at, <<"close">>}, {used_rfc854, UsedOpts}] | |
after 2000 -> | |
gen_tcp:close(Sock), | |
[{open, true}, {banner, SoFar}, | |
{ended_at, <<"time">>}, {used_rfc854, UsedOpts}] | |
end. | |
bulker_loop() -> | |
bulker_loop([]). | |
bulker_loop(Q) when (length(Q) > 48) -> | |
bulker_submit(Q), | |
bulker_loop([]); | |
bulker_loop(Q) -> | |
receive | |
{add_data, Pid, Ip, Json} -> | |
Pid ! {bulker, ok}, | |
bulker_loop([{Ip, Json} | Q]) | |
after 5000 -> | |
bulker_submit(Q), bulker_loop([]) | |
end. | |
bulker_submit(Q) -> | |
Uri = "http://localhost:9200/scanny/ip/_bulk", | |
Data = iolist_to_binary([ | |
[to_json([{index, [{'_id', list_to_binary(ip_tools:to_str(Ip))}]}]), | |
$\n, Json, $\n] | |
|| {Ip, Json} <- Q]), | |
Headers = [{<<"Content-Type">>, <<"application/json">>}], | |
case ssh_http:post(Uri, Headers, Data) of | |
{ok, Code} when (Code >= 200) and (Code < 300) -> ok; | |
{ok, Code} -> io:format("warning: got response code ~w: '~s'\n", [Code, Data]), ok; | |
{error, _} -> bulker_submit(Q) | |
end. | |
thread([], Acc) -> Acc; | |
thread([F | Rest], Acc) -> | |
thread(Rest, F(Acc)). | |
binjoin([Next], _Sep) -> Next; | |
binjoin([Next | Rest], Sep) -> | |
RestBin = binjoin(Rest, Sep), | |
<<Next/binary, Sep/binary, RestBin/binary>>. | |
ts_to_jstime(TS) -> | |
{{Year,Month,Day},{Hour,Min,Sec}} = calendar:now_to_universal_time(TS), | |
lists:flatten(io_lib:format("~4..0w-~2..0w-~2..0wT~2..0w:~2..0w:~2..0wZ", | |
[Year,Month,Day,Hour,Min,Sec])). | |
to_json(true) -> <<"true">>; | |
to_json(false) -> <<"false">>; | |
to_json(null) -> <<"null">>; | |
to_json({K, V}) when is_atom(K) -> | |
KBin = atom_to_binary(K, utf8), | |
VBin = to_json(V), | |
<<"\"", KBin/binary, "\": ", VBin/binary>>; | |
to_json(Val) when is_list(Val) -> | |
Inner = binjoin([to_json(V) || V <- Val], <<",">>), | |
case Val of | |
[{K,_} | _] when is_atom(K) -> <<"{", Inner/binary, "}">>; | |
_ -> <<"[", Inner/binary, "]">> | |
end; | |
to_json(Val) when is_binary(Val) -> | |
Dirty = [<<I>> || I <- lists:seq(0,31)] ++ [<<I>> || I <- lists:seq(127,255)], | |
Clean = thread([ | |
fun(V) -> binary:replace(V, <<$">>, <<"\\\"">>, [global]) end, | |
fun(V) -> binary:replace(V, <<"\r\n">>, <<" \\r\\n ">>, [global]) end, | |
fun(V) -> binary:replace(V, <<"\n">>, <<" \\n ">>, [global]) end, | |
fun(V) -> binary:replace(V, <<"\r">>, <<" \\r ">>, [global]) end, | |
fun(V) -> binary:replace(V, Dirty, <<$?>>, [global]) end | |
], Val), | |
<<"\"", Clean/binary, "\"">>; | |
to_json(Val) when is_integer(Val) -> | |
integer_to_binary(Val). | |
pmap(Fun, GS, N) when is_tuple(GS) and is_integer(N) -> | |
pmap(Fun, GS, N, []). | |
pmap(_Fun, {_G, done}, _N, []) -> ok; | |
pmap(Fun, GS = {_G, done}, N, Pids) -> | |
receive {done, Pid} -> pmap(Fun, GS, N, Pids -- [Pid]) end; | |
pmap(Fun, GS, N, Pids) when length(Pids) >= N -> | |
receive {done, Pid} -> pmap(Fun, GS, N, Pids -- [Pid]) end; | |
pmap(Fun, {GenFun, State}, N, Pids) -> | |
Me = self(), | |
Pid = spawn_link(fun() -> | |
Fun(State), | |
Me ! {done, self()} | |
end), | |
NextState = GenFun(State), | |
pmap(Fun, {GenFun, NextState}, N, [Pid | Pids]). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-module(ssh_http). | |
-define(DEFAULT_PACKET_SIZE, 32768). | |
-define(DEFAULT_WINDOW_SIZE, 2*?DEFAULT_PACKET_SIZE). | |
-define(DEFAULT_TIMEOUT, 5000). | |
-export([start_link/4, req/4, req/5]). | |
-export([get/2, get/3, | |
post/3, post/4, | |
put/3, put/4, | |
delete/2, delete/3]). | |
-record(state, {ssh, mref, host, ssh_port, user, pw}). | |
start_link(Host, Port, User, Password) -> | |
Pid = spawn_link(fun() -> ssh_master(Host, Port, User, Password) end), | |
true = register(ssh_master, Pid), | |
{ok, Pid}. | |
req(Method, Uri, Headers, Body) -> | |
req(Method, Uri, Headers, Body, 5000). | |
req(Method, Uri, Headers, Body, Timeout) -> | |
ssh_master ! {request, self(), Method, Uri, Headers, Body}, | |
receive | |
{request_ok, Code} -> {ok, Code}; | |
{request_error, Err} -> {error, Err} | |
after Timeout -> | |
{error, timeout} | |
end. | |
get(Uri, Headers) -> req(get, Uri, Headers, <<>>). | |
get(Uri, Headers, Timeout) -> req(get, Uri, Headers, <<>>, Timeout). | |
delete(Uri, Headers) -> req(delete, Uri, Headers, <<>>). | |
delete(Uri, Headers, Timeout) -> req(delete, Uri, Headers, <<>>, Timeout). | |
post(Uri, Headers, Body) -> req(post, Uri, Headers, Body). | |
post(Uri, Headers, Body, Timeout) -> req(post, Uri, Headers, Body, Timeout). | |
put(Uri, Headers, Body) -> req(put, Uri, Headers, Body). | |
put(Uri, Headers, Body, Timeout) -> req(put, Uri, Headers, Body, Timeout). | |
open_ssh(#state{host = Host, ssh_port = Port, user = User, pw = Pw}) -> | |
ssh:connect(Host, Port, [ | |
{user, User}, {password, Pw}, | |
{quiet_mode, true}, {silently_accept_hosts, true}]). | |
open_tcp_chan(Host, Port, #state{ssh = Ssh}) when is_list(Host) -> | |
HostBin = list_to_binary(Host), HostLen = byte_size(HostBin), | |
OrigHost = <<"localhost">>, OrigHostLen = byte_size(OrigHost), | |
OrigPort = crypto:rand_uniform(10000,65000), | |
Msg = <<HostLen:32/big, HostBin/binary, Port:32/big, OrigHostLen:32/big, OrigHost/binary, OrigPort:32/big>>, | |
ssh_connection_manager:open_channel(Ssh, "direct-tcpip", Msg, ?DEFAULT_WINDOW_SIZE, ?DEFAULT_PACKET_SIZE, ?DEFAULT_TIMEOUT). | |
ssh_master(Host, SshPort, User, Password) -> | |
S = #state{host = Host, ssh_port = SshPort, user = User, pw = Password}, | |
{ok, Ssh} = open_ssh(S), | |
SshMref = monitor(process, Ssh), | |
chanreqs = ets:new(chanreqs, [set, public, named_table]), | |
chans = ets:new(chans, [set, public, named_table]), | |
ssh_master_loop(S#state{ssh = Ssh, mref = SshMref}). | |
ssh_master_loop(S = #state{ssh = Ssh, mref = SshMref}) -> | |
Sz = ets:info(chanreqs, size), | |
receive | |
{'DOWN', SshMref, process, Ssh, Reason} -> | |
io:format("lost ssh connection: ~999p\n", [Reason]), | |
{ok, NewSsh} = open_ssh(S), | |
NewSshMref = monitor(process, NewSsh), | |
ets:delete_all_objects(chanreqs), | |
ets:delete_all_objects(chans), | |
ssh_master_loop(S#state{ssh = NewSsh, mref = NewSshMref}); | |
{'DOWN', _, process, ReqPid, Reason} -> | |
case ets:lookup(chanreqs, ReqPid) of | |
[{ReqPid, closed, _}] -> | |
ets:delete(chanreqs, ReqPid); | |
[{ReqPid, none, CallerPid}] -> | |
CallerPid ! {request_error, Reason}; | |
[{ReqPid, Chan, CallerPid}] when (Reason =:= normal) -> | |
ets:delete(chans, Chan), | |
CallerPid ! {request_error, Reason}; | |
[{ReqPid, Chan, CallerPid}] -> | |
io:format("[~p] closed after crash\n", [Chan]), | |
ssh_connection:close(Ssh, Chan), | |
ets:delete(chans, Chan), | |
CallerPid ! {request_error, Reason}; | |
_ -> ok | |
end, | |
ssh_master_loop(S); | |
{request, Pid, Method, Uri, Headers, Body} when (Sz < 16) -> | |
{ok, {http, [], RemoteHost, RemotePort, PathStr, QueryStr}} = http_uri:parse(Uri), | |
PathBin = list_to_binary(PathStr), | |
QueryBin = list_to_binary(QueryStr), | |
Path = <<PathBin/binary, QueryBin/binary>>, | |
{Kid,_} = spawn_monitor(fun() -> | |
receive go -> ok end, | |
{ok, Chan} = open_tcp_chan(RemoteHost, RemotePort, S), | |
true = ets:insert(chanreqs, {self(), Chan, Pid}), | |
true = ets:insert(chans, {Chan, self()}), | |
MethodBin = list_to_binary(string:to_upper(atom_to_list(Method))), | |
Body0 = <<MethodBin/binary, " ", Path/binary, " HTTP/1.0\r\n">>, | |
Body1 = lists:foldl(fun({K,V}, Acc) -> | |
<<Acc/binary, K/binary, ": ", V/binary, "\r\n">> | |
end, Body0, Headers), | |
Body2 = case byte_size(Body) of | |
0 -> Body1; | |
K -> B = integer_to_binary(K), <<Body1/binary, "Content-Length: ", B/binary, "\r\n">> | |
end, | |
Body3 = <<Body2/binary, "\r\n", Body/binary>>, | |
ok = ssh_connection:send(Ssh, Chan, Body3), | |
receive | |
{ssh_cm, Ssh, {data, Chan, _, Bin}} -> | |
[Head | _] = binary:split(Bin, <<"\r\n">>), | |
[_Ver, CodeBin | _Rest] = binary:split(Head, <<" ">>, [global]), | |
Code = binary_to_integer(CodeBin), | |
Pid ! {request_ok, Code} | |
end, | |
receive | |
{ssh_cm, Ssh, {closed, Chan}} -> | |
ets:insert(chanreqs, {self(), closed, Pid}), | |
ets:delete(chans, Chan) | |
after 100 -> | |
ok | |
end, | |
ssh_connection:close(Ssh, Chan), | |
ets:insert(chanreqs, {self(), closed, Pid}), | |
ets:delete(chans, Chan) | |
end), | |
true = ets:insert(chanreqs, {Kid, none, Pid}), | |
Kid ! go, | |
ssh_master_loop(S) | |
end. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment