<commit_before>-module(dberl.auth).

-import(crypto).
-import(error_logger).
-import(file).
-import(gen_server).
-import(hex).
-import(io).
-import(lists).
-import(os).

-behaviour(gen_server).

%% api
-export([start_link/0, start_link/2, stop/0]).

%% gen_server callbacks
-export([init/1,
	 code_change/3,
	 handle_call/3,
	 handle_cast/2,
	 handle_info/2,
	 terminate/2]).

-record(state, {
	  owner,
	  sock
	 }).

-define(SERVER, ?MODULE).
-define(PORT, 1236).
-define(HOST, "localhost").

start_link() ->
    DbusHost = ?HOST,
    DbusPort = ?PORT,
    start_link(DbusHost, DbusPort).

start_link(DbusHost, DbusPort) ->
    gen_server:start_link({local, ?SERVER}, ?MODULE, [DbusHost, DbusPort, self()], []).

stop() ->
    gen_server:cast(?SERVER, stop).


%%
%% gen_server callbacks
%%
init([DbusHost, DbusPort, Owner]) ->
    User = os:getenv("USER"),
    {ok, Sock} = tcp_conn:connect(DbusHost, DbusPort, [list, {packet, 0}]),
    ok = connection:send(Sock, <<0>>),
    ok = connection:send(Sock, ["AUTH DBUS_COOKIE_SHA1 ",
			     list_to_hexlist(User),
			     "\r\n"]),
    {ok, #state{sock=Sock,
		owner=Owner}}.


code_change(_OldVsn, State, _Extra) ->
    {ok, State}.


handle_call(Request, _From, State) ->
    error_logger:error_msg("Unhandled call in ~p: ~p~n", [?MODULE, Request]),
    {reply, ok, State}.


handle_cast(stop, State) ->
    {stop, normal, State};
handle_cast(Request, State) ->
    error_logger:error_msg("Unhandled cast in ~p: ~p~n", [?MODULE, Request]),
    {noreply, State}.


handle_info({received, Sock, "DATA " ++ Line}, #state{sock=Sock}=State) ->
    Data = hex_to_list(strip_eol(Line, [])),
    [Context, CookieId, ServerChallenge] = split(Data, $\ ),
    io:format("Data: ~p,~p,~p ~n", [Context, CookieId, ServerChallenge]),

    case read_cookie(CookieId) of
	error ->
	    {stop, {no_cookie, CookieId}, State};
	{ok, Cookie} ->
	    Challenge = calc_challenge(),
	    Response = calc_response(ServerChallenge, Challenge, Cookie),
	    ok = connection:send(Sock, ["DATA " ++ Response ++ "\r\n"]),

	    {noreply, State}
    end;

handle_info({received, Sock, "OK " ++ Line}, #state{sock=Sock}=State) ->
    Guid = strip_eol(Line, []),
    error_logger:info_msg("GUID ~p~n", [Guid]),
    ok = connection:setopts(Sock, [binary, {packet, raw}]),%, {recbuf, 8196}]),
    ok = connection:send(Sock, ["BEGIN\r\n"]),

    Owner = State#state.owner,
    Owner ! {auth_ok, self(), Sock},
    {stop, normal, State};

handle_info(Info, State) ->
    error_logger:error_msg("Unhandled info in ~p: ~p~n", [?MODULE, Info]),
    {noreply, State}.


terminate(_Reason, _State) ->
    terminated.


calc_challenge() ->
    {MegaSecs, Secs, _MicroSecs} = now(),
    UnixTime = MegaSecs * 1000000 + Secs,
    Challenge = list_to_hexlist("Hello " ++ integer_to_list(UnixTime)),
    Challenge.

calc_response(ServerChallenge, Challenge, Cookie) ->
    A1 = ServerChallenge ++ ":" ++ Challenge ++ ":" ++ Cookie,
    io:format("A1: ~p~n", [A1]),
    Digest = crypto:sha(A1),
    DigestHex = list_to_hexlist(binary_to_list(Digest)),
    Response = list_to_hexlist(Challenge ++ " " ++ DigestHex),
    Response.


strip_eol([], Res) ->
    Res;
strip_eol([$\r|R], Res) ->
    strip_eol(R, Res);
strip_eol([$\n|R], Res) ->
    strip_eol(R, Res);
strip_eol([E|R], Res) ->
    strip_eol(R, Res ++ [E]).


list_to_hexlist(List) ->
    Fun = fun(E) ->
		  byte_to_hex(E)
	  end,
    
    lists:flatten(lists:map(Fun, List)).

byte_to_hex(E) ->
    High = E div 16,
    Low = E - High * 16,

    [nibble_to_hex(High), nibble_to_hex(Low)].

nibble_to_hex(Nibble) when Nibble >= 0, Nibble =< 9 ->
    Nibble + $0;
nibble_to_hex(Nibble) when Nibble >= 10, Nibble =< 15  ->
    Nibble - 10 + $a.
   

hex_to_list(Hex) ->
    hex_to_list(Hex, []).

hex_to_list([], List) ->
    List;
hex_to_list([H1, H2|R], List) ->
    List1 = List ++ [hex:from([H1, H2])],
    hex_to_list(R, List1).

read_cookie(CookieId) ->
    Home = os:getenv("HOME"),
    Name = Home ++ "/.dbus-keyrings/org_freedesktop_general",
    {ok, File} = file:open(Name, [read]),
    Result = read_cookie(File, CookieId),
    ok = file:close(File),
    Result.

read_cookie(Device, CookieId) ->
    case io:get_line(Device, "") of
	eof ->
	    error;
	Line ->
	    [CookieId1, _Time, Cookie] = split(strip_eol(Line, []), $\ ),
	    if
		CookieId == CookieId1 ->
		    {ok, Cookie};
		true ->
		    read_cookie(Device, CookieId)
	    end
    end.

split(List, Char) when is_list(List),
		       is_integer(Char) ->
    split(List, Char, "", []).

split([], _Char, Str, Res) ->
    Res ++ [Str];
split([Char|R], Char, Str, Res) ->
    split(R, Char, "", Res ++ [Str]);
split([C|R], Char, Str, Res) ->
    split(R, Char, Str ++ [C], Res).
<commit_msg>Remove start_link/0<commit_after>%%%
%%% D-Bus outgoing connection authenticator
%%%

-module(dberl.auth).

-import(crypto).
-import(error_logger).
-import(file).
-import(gen_server).
-import(hex).
-import(io).
-import(lists).
-import(os).

-behaviour(gen_server).

%% api
-export([start_link/2, stop/0]).

%% gen_server callbacks
-export([init/1,
	 code_change/3,
	 handle_call/3,
	 handle_cast/2,
	 handle_info/2,
	 terminate/2]).

-record(state, {
	  owner,
	  sock
	 }).

-define(SERVER, ?MODULE).

start_link(DbusHost, DbusPort) ->
    gen_server:start_link({local, ?SERVER}, ?MODULE, [DbusHost, DbusPort, self()], []).

stop() ->
    gen_server:cast(?SERVER, stop).


%%
%% gen_server callbacks
%%
init([DbusHost, DbusPort, Owner]) ->
    User = os:getenv("USER"),
    {ok, Sock} = tcp_conn:connect(DbusHost, DbusPort, [list, {packet, 0}]),
    ok = connection:send(Sock, <<0>>),
    ok = connection:send(Sock, ["AUTH DBUS_COOKIE_SHA1 ",
			     list_to_hexlist(User),
			     "\r\n"]),
    {ok, #state{sock=Sock,
		owner=Owner}}.


code_change(_OldVsn, State, _Extra) ->
    {ok, State}.


handle_call(Request, _From, State) ->
    error_logger:error_msg("Unhandled call in ~p: ~p~n", [?MODULE, Request]),
    {reply, ok, State}.


handle_cast(stop, State) ->
    {stop, normal, State};
handle_cast(Request, State) ->
    error_logger:error_msg("Unhandled cast in ~p: ~p~n", [?MODULE, Request]),
    {noreply, State}.


handle_info({received, Sock, "DATA " ++ Line}, #state{sock=Sock}=State) ->
    Data = hex_to_list(strip_eol(Line, [])),
    [Context, CookieId, ServerChallenge] = split(Data, $\ ),
    io:format("Data: ~p,~p,~p ~n", [Context, CookieId, ServerChallenge]),

    case read_cookie(CookieId) of
	error ->
	    {stop, {no_cookie, CookieId}, State};
	{ok, Cookie} ->
	    Challenge = calc_challenge(),
	    Response = calc_response(ServerChallenge, Challenge, Cookie),
	    ok = connection:send(Sock, ["DATA " ++ Response ++ "\r\n"]),

	    {noreply, State}
    end;

handle_info({received, Sock, "OK " ++ Line}, #state{sock=Sock}=State) ->
    Guid = strip_eol(Line, []),
    error_logger:info_msg("GUID ~p~n", [Guid]),
    ok = connection:setopts(Sock, [binary, {packet, raw}]),%, {recbuf, 8196}]),
    ok = connection:send(Sock, ["BEGIN\r\n"]),

    Owner = State#state.owner,
    Owner ! {auth_ok, self(), Sock},
    {stop, normal, State};

handle_info(Info, State) ->
    error_logger:error_msg("Unhandled info in ~p: ~p~n", [?MODULE, Info]),
    {noreply, State}.


terminate(_Reason, _State) ->
    terminated.


calc_challenge() ->
    {MegaSecs, Secs, _MicroSecs} = now(),
    UnixTime = MegaSecs * 1000000 + Secs,
    Challenge = list_to_hexlist("Hello " ++ integer_to_list(UnixTime)),
    Challenge.

calc_response(ServerChallenge, Challenge, Cookie) ->
    A1 = ServerChallenge ++ ":" ++ Challenge ++ ":" ++ Cookie,
    io:format("A1: ~p~n", [A1]),
    Digest = crypto:sha(A1),
    DigestHex = list_to_hexlist(binary_to_list(Digest)),
    Response = list_to_hexlist(Challenge ++ " " ++ DigestHex),
    Response.


strip_eol([], Res) ->
    Res;
strip_eol([$\r|R], Res) ->
    strip_eol(R, Res);
strip_eol([$\n|R], Res) ->
    strip_eol(R, Res);
strip_eol([E|R], Res) ->
    strip_eol(R, Res ++ [E]).


list_to_hexlist(List) ->
    Fun = fun(E) ->
		  byte_to_hex(E)
	  end,
    
    lists:flatten(lists:map(Fun, List)).

byte_to_hex(E) ->
    High = E div 16,
    Low = E - High * 16,

    [nibble_to_hex(High), nibble_to_hex(Low)].

nibble_to_hex(Nibble) when Nibble >= 0, Nibble =< 9 ->
    Nibble + $0;
nibble_to_hex(Nibble) when Nibble >= 10, Nibble =< 15  ->
    Nibble - 10 + $a.
   

hex_to_list(Hex) ->
    hex_to_list(Hex, []).

hex_to_list([], List) ->
    List;
hex_to_list([H1, H2|R], List) ->
    List1 = List ++ [hex:from([H1, H2])],
    hex_to_list(R, List1).

read_cookie(CookieId) ->
    Home = os:getenv("HOME"),
    Name = Home ++ "/.dbus-keyrings/org_freedesktop_general",
    {ok, File} = file:open(Name, [read]),
    Result = read_cookie(File, CookieId),
    ok = file:close(File),
    Result.

read_cookie(Device, CookieId) ->
    case io:get_line(Device, "") of
	eof ->
	    error;
	Line ->
	    [CookieId1, _Time, Cookie] = split(strip_eol(Line, []), $\ ),
	    if
		CookieId == CookieId1 ->
		    {ok, Cookie};
		true ->
		    read_cookie(Device, CookieId)
	    end
    end.

split(List, Char) when is_list(List),
		       is_integer(Char) ->
    split(List, Char, "", []).

split([], _Char, Str, Res) ->
    Res ++ [Str];
split([Char|R], Char, Str, Res) ->
    split(R, Char, "", Res ++ [Str]);
split([C|R], Char, Str, Res) ->
    split(R, Char, Str ++ [C], Res).

{'fraction_non_alphanumeric': 0.19546378517171284, 'fraction_numerical': 0.010698619878035734, 'mean_word_length': 3.5224963715529753, 'pattern_counts': {'":': 4, '<': 11, '<?xml version=': 0, '>': 86, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5242987', 'n_tokens_mistral': 3858, 'n_tokens_neox': 3608, 'n_words': 708}