diff --git a/src/hijack.erl b/src/hijack.erl index cbc0ac7b1961e5a00061f5da0b244a15bc276820..dc004829eede847982bdbdf7be867dd9a057841f 100644 --- a/src/hijack.erl +++ b/src/hijack.erl @@ -4,6 +4,7 @@ -define(OPT_SPEC, [ {consul, $c, "consul", {string, "localhost:8083"}, "Consul host and port"}, {target, $t, "target", {string, "localhost:8080"}, "Target host and port"}, + {snoop, $s, "snoop", {boolean, false}, "Ignore response from target"}, {username, $u, "username", string, "Username"}, {password, $p, "password", string, "Password"}, {path, $r, "path", {string, ".*"}, "Path regexp"}, @@ -21,20 +22,56 @@ main(Args) -> application:ensure_all_started(gun), {ok, {Opts, _}} = getopt:parse(?OPT_SPEC, Args), - case getopt:check(?OPT_SPEC, Opts) of + Opts1 = case proplists:get_value(password, Opts) of + undefined -> + [{password, get_password("Password > ")} | Opts]; + _ -> + Opts + end, + Opts2 = case proplists:get_value(username, Opts1) of + undefined -> + case os:getenv("USER", "") of + false -> + Opts1; + Username -> + [{username, Username} | Opts1] + end; + _ -> + Opts1 + end, + case getopt:check(?OPT_SPEC, Opts2) of ok -> - execute(#state{opts = Opts}); + execute(#state{opts = Opts2}); {error, Reason} -> io:format("~n~p~n~n", [Reason]), getopt:usage(?OPT_SPEC, "hijack") end. +get_password(Prompt) -> + Pid = spawn_link(fun() -> clear_stdin(Prompt) end), + Ref = make_ref(), + {ok, [Password]} = io:fread("", "~s"), + Pid ! {done, self(), Ref}, + receive {Pid, Ref} -> ok end, + Password. + +clear_stdin(Prompt) -> + receive + {done, Parent, Ref} -> + Parent ! {self(), Ref}, + io:format("\e[2K\r") + after + 5 -> + io:format("\e[2K\r~s", [Prompt]), + clear_stdin(Prompt) + end. + execute(State = #state{opts = Opts}) -> Transport = ranch_ssl, - {ConsulHost, ConsulPort} = parse_host_and_port(proplists:get_value(consul, Opts)), + {ConsulHost, ConsulPort} = parse_host_and_port(proplists:get_value(consul, Opts), 8083), case Transport:connect(ConsulHost, ConsulPort, []) of {ok, Socket} -> - {TargetHost, TargetPort} = parse_host_and_port(proplists:get_value(target, Opts)), + {TargetHost, TargetPort} = parse_host_and_port(proplists:get_value(target, Opts), 80), NewState = State#state{transport = Transport, consul_socket = Socket, target_host = TargetHost, target_port = TargetPort}, Parent = self(), spawn( @@ -51,9 +88,13 @@ execute(State = #state{opts = Opts}) -> ok end. -parse_host_and_port(HostAndPort) -> - [Host, Port] = string:tokens(HostAndPort, ":"), - {Host, list_to_integer(Port)}. +parse_host_and_port(HostAndPort, DefaultPort) -> + case string:tokens(HostAndPort, ":") of + [Host] -> + {Host, DefaultPort}; + [Host, Port] -> + {Host, list_to_integer(Port)} + end. transport_loop(Parent, Transport, Socket) -> case Transport:recv(Socket, 0, 3000) of @@ -123,15 +164,21 @@ send_to_consul(State = #state{transport = Transport, consul_socket = Socket}, Ma handle(State = #state{opts = Opts}, #{<<"action">> := <<"authorized">>, <<"username">> := Username}) -> io:format("Logged in as ~s~n", [Username]), + Mode = case proplists:get_value(snoop, Opts) of + true -> + <<"snoop">>; + _ -> + <<"hijack">> + end, send_to_consul(State, #{ <<"action">> => <<"bind">>, <<"host">> => list_to_binary(proplists:get_value(host, Opts, "")), <<"path">> => list_to_binary(proplists:get_value(path, Opts, "")), - <<"mode">> => <<"hijack">> + <<"mode">> => Mode }); -handle(State, #{<<"action">> := <<"bind">>, <<"host">> := Host, <<"path">> := Path}) -> - io:format("Listening to ~s/~s~n", [Host, Path]), +handle(State, #{<<"action">> := <<"bind">>, <<"host">> := Host, <<"path">> := Path, <<"mode">> := Mode}) -> + io:format("Ready, ~sing ~s/~s~n", [Mode, Host, Path]), State; handle(State = #state{transport = Transport, consul_socket = Socket}, #{<<"action">> := <<"error">>, <<"message">> := Message}) ->