diff --git a/priv/www/index.html b/priv/www/index.html new file mode 100644 index 0000000000000000000000000000000000000000..414f86883e2cb0071f0bdec21a959e92e7841589 --- /dev/null +++ b/priv/www/index.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <title></title> + <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,300italic,700,700italic"> + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/3.0.3/normalize.css"> + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/milligram/1.1.0/milligram.css"> + <script src="https://cdnjs.cloudflare.com/ajax/libs/zepto/1.1.6/zepto.js"></script> +</head> +<body> +<h1>Hello</h1> +</body> +</html> diff --git a/rebar.config b/rebar.config index e43e32a54e57f4573bb8cae82cd40a7206609fd6..a443400b4fe8ec28a3618d2e77a4eda927c83575 100644 --- a/rebar.config +++ b/rebar.config @@ -1,6 +1,7 @@ {erl_opts, [debug_info]}. {escript_main_app, hijack}. +{escript_incl_extra, [{"priv/www/*", "_build/default/lib/hijack/"}]}. {deps, [ {getopt, "0.8.2"}, diff --git a/src/hijack.erl b/src/hijack.erl index dc004829eede847982bdbdf7be867dd9a057841f..ec64e21bbad84700d031ba3d18a96799b85bbd68 100644 --- a/src/hijack.erl +++ b/src/hijack.erl @@ -5,6 +5,7 @@ {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"}, + {port, $w, "port", {integer, 4040}, "Port for hijack webserver"}, {username, $u, "username", string, "Username"}, {password, $p, "password", string, "Password"}, {path, $r, "path", {string, ".*"}, "Path regexp"}, @@ -16,7 +17,9 @@ transport = ranch_ssl, consul_socket = undefined, target_host = "localhost", - target_port = 80 + target_port = 80, + httpd_pid, + httpd_port }). main(Args) -> @@ -66,6 +69,48 @@ clear_stdin(Prompt) -> clear_stdin(Prompt) end. +start_httpd(State = #state{opts = Opts}) -> + application:ensure_all_started(inets), + Port = proplists:get_value(port, Opts, 4040), + CommonConfig = [ + {bind_address, "localhost"}, + {port, Port}, + {erl_script_alias, {"/esi", [hijack_www]}}, + {server_name, "hijack"}, + {server_root, "."} + ], + ServiceConfig = try escript:script_name() of + File -> + {ok, Sections} = escript:extract(File, []), + Archive = proplists:get_value(archive, Sections), + {ok, ZipHandle} = zip:zip_open(Archive, [memory]), + [ + {modules, [mod_esi, mod_zipget]}, + {document_root, "."}, + {zip_handle, ZipHandle}, + {zip_root, "priv/www"} + ] + catch + _:_ -> + [ + {modules, [mod_esi, mod_get]}, + {document_root, "priv/www"} + ] + end, + case inets:start(httpd, CommonConfig ++ ServiceConfig) of + {ok, Pid} when Port == 0 -> + OtherPort = proplists:get_value(port, httpd:info(Pid)), + io:format("Default port busy, started webserver at http://localhost:~b~n", [OtherPort]), + State#state{httpd_pid = Pid, httpd_port = OtherPort}; + {ok, Pid} -> + io:format("Started webserver at http://localhost:~b~n", [Port]), + State#state{httpd_pid = Pid, httpd_port = Port}; + {error, _Reason} when Port /= 0 -> + start_httpd(State#state{opts = lists:keyreplace(port, 1, Opts, {port, 0})}); + {error, _Reason} -> + io:format("Error!, unable to start hijack webserver") + end. + execute(State = #state{opts = Opts}) -> Transport = ranch_ssl, {ConsulHost, ConsulPort} = parse_host_and_port(proplists:get_value(consul, Opts), 8083), @@ -179,7 +224,7 @@ handle(State = #state{opts = Opts}, #{<<"action">> := <<"authorized">>, <<"usern handle(State, #{<<"action">> := <<"bind">>, <<"host">> := Host, <<"path">> := Path, <<"mode">> := Mode}) -> io:format("Ready, ~sing ~s/~s~n", [Mode, Host, Path]), - State; + start_httpd(State); handle(State = #state{transport = Transport, consul_socket = Socket}, #{<<"action">> := <<"error">>, <<"message">> := Message}) -> io:format("~s~n", [Message]), diff --git a/src/hijack_www.erl b/src/hijack_www.erl new file mode 100644 index 0000000000000000000000000000000000000000..c3d1ccde497bee971e989f7d41ae08c7a231d610 --- /dev/null +++ b/src/hijack_www.erl @@ -0,0 +1,12 @@ +-module(hijack_www). + +-export([ + echo/3, + hello/3 +]). + +echo(SessionID, _Env, Input) -> + mod_esi:deliver(SessionID, Input). + +hello(SessionID, _Env, _Input) -> + mod_esi:deliver(SessionID, "world"). diff --git a/src/mod_zipget.erl b/src/mod_zipget.erl new file mode 100644 index 0000000000000000000000000000000000000000..892359196a420be1290f00e71ecfaa876cc077e4 --- /dev/null +++ b/src/mod_zipget.erl @@ -0,0 +1,53 @@ +-module(mod_zipget). + +-export([do/1]). + +-include_lib("inets/include/httpd.hrl"). + +do(Info) -> + case Info#mod.method of + "GET" -> + case proplists:get_value(status, Info#mod.data) of + {_StatusCode, _PhraseArgs, _Reason} -> + {proceed, Info#mod.data}; + undefined -> + case proplists:get_value(response, Info#mod.data) of + undefined -> + do_get(Info); + _Response -> + {proceed, Info#mod.data} + end + end; + _ -> + {proceed, Info#mod.data} + end. + +do_get(Info) -> + send_response(Info#mod.socket, Info#mod.socket_type, Info#mod.request_uri, Info). + +send_response(Socket, SocketType, "", Info) -> + send_response(Socket, SocketType, "/index.html", Info); + +send_response(Socket, SocketType, "/", Info) -> + send_response(Socket, SocketType, "/index.html", Info); + +send_response(Socket, SocketType, RequestURI, Info) -> + ZipHandle = httpd_util:lookup(Info#mod.config_db, zip_handle), + ZipRoot = httpd_util:lookup(Info#mod.config_db, zip_root, ""), + Path = ZipRoot ++ RequestURI, + case zip:zip_get(Path, ZipHandle) of + {ok, {Path, Content}} -> + Suffix = httpd_util:suffix(Path), + MimeType = httpd_util:lookup_mime_default(Info#mod.config_db, Suffix, "text/plain"), + Headers = [ + {content_type, MimeType}, + {content_length, integer_to_list(byte_size(Content))} + ], + httpd_response:send_header(Info, 200, Headers), + httpd_socket:deliver(SocketType, Socket, Content), + {proceed, [{response, {already_sent, 200, byte_size(Content)}}, {mime_type, MimeType} | Info#mod.data]}; + {error, file_not_found} -> + {proceed, [{status, httpd_file:handle_error(enoent, "open", Info, Path)} | Info#mod.data]}; + {error, Reason} -> + {proceed, [{status, httpd_file:handle_error(Reason, "open", Info, Path)} | Info#mod.data]} + end.