%% server_node.erl
%% Starts the REAL inets httpd (lib/inets from the OTP build under test) with a
%% default configuration on a free port, then runs a 1-second census loop that
%% records how many httpd_request_handler processes are alive.
%%
%% Files written under WorkDir:
%%   port.txt      - actual listening port
%%   beam_pid.txt  - OS pid of the beam VM (for teardown)
%%   census.log    - census lines: CENSUS <unix_ts> <handler_count> <pid:info;...>
%%                   plus STARTED/PATH lines
-module(server_node).
-export([main/1]).

main(WorkDir) ->
    ok = file:write_file(filename:join(WorkDir, "beam_pid.txt"), os:getpid()),
    {ok, _} = application:ensure_all_started(inets),
    %% Record exactly which inets code the running node uses (build-tree proof)
    {ok, InetsVsn} = application:get_key(inets, vsn),
    log(WorkDir, "PATH vsn=~s inets_lib=~p handler_lib=~p chunk_lib=~p~n",
        [InetsVsn, code:which(inets), code:which(httpd_request_handler),
         code:which(http_chunk)]),
    ok = file:write_file(filename:join(WorkDir, "index.html"),
                         <<"<!DOCTYPE html><html><body><h1>OK</h1></body></html>\n">>),
    %% Default configuration: keep_alive_timeout (150 s) and
    %% minimum_bytes_per_second (false) are left at their defaults on purpose
    %% (the advisory's claim is about the default config). max_clients is set
    %% to its DOCUMENTED DEFAULT of 150 because httpd_conf only stores the
    %% value when the user supplies it; with it unset the manager compares
    %% against 'undefined' and accepts unboundedly (an even easier DoS).
    {ok, Pid} = inets:start(httpd, [
        {port, 0},
        {bind_address, any},
        {server_name, "vulntest"},
        {server_root, WorkDir},
        {document_root, WorkDir},
        {mime_types, [{"html", "text/html"}, {"htm", "text/html"}]},
        {max_clients, 150}
    ]),
    Info = httpd:info(Pid),
    Port = proplists:get_value(port, Info),
    ok = file:write_file(filename:join(WorkDir, "port.txt"),
                         integer_to_list(Port)),
    log(WorkDir, "STARTED port=~p manager=~p~n",
        [Port, proplists:get_value(pid, Info)]),
    spawn(fun () -> census_loop(WorkDir, 1000) end),
    ok.

census_loop(WorkDir, Interval) ->
    Ts = os:system_time(second),
    Handlers = handler_pids(),
    Detail = string:join(
               [io_lib:format("~p:~p",
                              [P, erlang:process_info(P, [message_queue_len, status])])
                || P <- Handlers], ";"),
    append(WorkDir, io_lib:format("CENSUS ~p ~p ~s~n",
                                  [Ts, length(Handlers), Detail])),
    receive stop_census -> ok after Interval -> census_loop(WorkDir, Interval) end.

handler_pids() ->
    [P || P <- erlang:processes(), is_request_handler(P)].

is_request_handler(P) ->
    try proc_lib:translate_initial_call(P) =:= {httpd_request_handler, init, 1}
    catch _:_ -> false end.

log(Dir, Fmt, Args) ->
    file:write_file(filename:join(Dir, "census.log"), io_lib:format(Fmt, Args), [append]).

append(Dir, IoData) ->
    file:write_file(filename:join(Dir, "census.log"), IoData, [append]).
