|
|
@@ -0,0 +1,206 @@
|
|
|
+-module(pastebin).
|
|
|
+-export([main/0, conn_handler/2, conn_accepter/1]).
|
|
|
+-export([open/3, read_all/2, read_all/3, close/2]).
|
|
|
+
|
|
|
+-spec main() -> ok.
|
|
|
+main() ->
|
|
|
+ io:format("Creating Socket on 6969.\n"),
|
|
|
+ {ok, RootSock} = gen_tcp:listen(6969, [binary,
|
|
|
+ {packet, 0},
|
|
|
+ {active, false},
|
|
|
+ {reuseaddr, true}]),
|
|
|
+ io:format("Sucess in creating Socket on 6969.\n"),
|
|
|
+ io:format("Listening on 6969.\n"),
|
|
|
+ spawn(?MODULE, conn_accepter, [RootSock]),
|
|
|
+ ok.
|
|
|
+
|
|
|
+
|
|
|
+-spec take(non_neg_integer(), [any()]) -> [any()].
|
|
|
+take(_, []) -> [];
|
|
|
+take(_, [H]) -> [H];
|
|
|
+take(1, [H|_]) -> [H];
|
|
|
+take(N, [H|T]) -> [H] ++ take((N-1), T).
|
|
|
+
|
|
|
+-spec fail_on_error(R, What, Sock) -> ok | no_return() when
|
|
|
+ R :: ok | {error, Reason},
|
|
|
+ What :: string(),
|
|
|
+ Sock :: gen_tcp:socket(),
|
|
|
+ Reason :: closed | timeout | inet:posix().
|
|
|
+fail_on_error(R, What, Sock) ->
|
|
|
+ case R of
|
|
|
+ ok -> ok;
|
|
|
+ {error, Reason} -> conn_handler({fail, What, Reason}, Sock)
|
|
|
+ end.
|
|
|
+
|
|
|
+-spec recv_until(F, Sock, Bin) -> binary() when
|
|
|
+ F :: fun((binary()) -> true | false),
|
|
|
+ Sock :: gen_tcp:socket(),
|
|
|
+ Bin :: binary().
|
|
|
+recv_until(F, Sock, Bin) ->
|
|
|
+ Packet = recv(Sock, 0),
|
|
|
+ case F(Packet) of
|
|
|
+ true -> Bin;
|
|
|
+ false -> recv_until(F, Sock, <<Bin/binary, Packet/binary>>)
|
|
|
+ end.
|
|
|
+
|
|
|
+-spec send(Sock, Packet) -> ok when
|
|
|
+ Sock :: gen_tcp:socket(),
|
|
|
+ Packet :: iodata().
|
|
|
+send(Sock, Packet) ->
|
|
|
+ ok = fail_on_error(gen_tcp:send(Sock, Packet), "send", Sock),
|
|
|
+ ok.
|
|
|
+
|
|
|
+-spec recv(Sock, Length) -> Packet | error when
|
|
|
+ Sock :: gen_tcp:socket(),
|
|
|
+ Length :: non_neg_integer(),
|
|
|
+ Packet :: string() | binary() | term().
|
|
|
+recv(Sock, Length) ->
|
|
|
+ case gen_tcp:recv(Sock, Length) of
|
|
|
+ {ok, Packet} -> Packet;
|
|
|
+ {error, Reason} -> conn_handler({fail, "recv", Reason}, Sock),
|
|
|
+ exit(failed_recv)
|
|
|
+ end.
|
|
|
+
|
|
|
+-spec open(File, Modes, Sock) -> Fd when
|
|
|
+ File :: file:name_all() | iodata(),
|
|
|
+ Modes :: [file:mode() | ram | directory],
|
|
|
+ Sock :: gen_tcp:socket(),
|
|
|
+ Fd :: file:io_device().
|
|
|
+open(File, Modes, Sock) ->
|
|
|
+ case file:open(File, Modes) of
|
|
|
+ {ok, Fd} -> Fd;
|
|
|
+ {error, Reason} ->
|
|
|
+ conn_handler({fail, "open", Reason}, Sock),
|
|
|
+ exit(failed_open)
|
|
|
+ end.
|
|
|
+
|
|
|
+-spec read_all(file:io_device(), gen_tcp:socket()) -> binary().
|
|
|
+read_all(Fd, Sock) ->
|
|
|
+ read_all(Fd, Sock, <<>>).
|
|
|
+-spec read_all(file:io_device(), gen_tcp:socket(), binary()) -> binary().
|
|
|
+read_all(Fd, Sock, Bin) ->
|
|
|
+ case file:read(Fd, 1024) of
|
|
|
+ {ok, Data} -> read_all(Fd, Sock, <<Bin/binary, Data/binary>>);
|
|
|
+ eof -> Bin;
|
|
|
+ {error, Reason} ->
|
|
|
+ conn_handler({fail, "close", Reason}, Sock),
|
|
|
+ exit(failed_read)
|
|
|
+ end.
|
|
|
+
|
|
|
+-spec close(Fd, Sock) -> ok when
|
|
|
+ Fd :: file:io_device(),
|
|
|
+ Sock :: gen_tcp:socket().
|
|
|
+close(Fd, Sock) ->
|
|
|
+ ok = fail_on_error(file:close(Fd), "close", Sock),
|
|
|
+ ok.
|
|
|
+
|
|
|
+-spec rand_choice(L) -> atom() when
|
|
|
+ L :: list(atom()).
|
|
|
+rand_choice(L) ->
|
|
|
+ lists:nth(rand:uniform(length(L)), L).
|
|
|
+
|
|
|
+-spec conn_handler(State, Sock) -> ok | error when
|
|
|
+ State :: init | {command, binary(), string()} | {challenge, binary()}
|
|
|
+ | {fail, string(), Reason},
|
|
|
+ Sock :: gen_tcp:socket(),
|
|
|
+ Reason :: closed | timeout | inet:posix().
|
|
|
+conn_handler({fail, What, Reason}, Sock) ->
|
|
|
+ io:format("ERROR: Failed ~s `~w`\n", [What, Reason]),
|
|
|
+ gen_tcp:close(Sock),
|
|
|
+ error;
|
|
|
+
|
|
|
+conn_handler(init, Sock) ->
|
|
|
+ conn_handler({command, recv(Sock, 0), "POST | GET"}, Sock);
|
|
|
+
|
|
|
+conn_handler({command, <<"POST", _/binary>>, _}, Sock) ->
|
|
|
+ send(Sock, "OK.\r\n"),
|
|
|
+ Bin = recv_until(fun(B) -> B == <<".\r\n">> end, Sock, <<>>),
|
|
|
+ io:format("Got: `~w`\n", [Bin]),
|
|
|
+ conn_handler({challenge, Bin}, Sock);
|
|
|
+
|
|
|
+conn_handler({command, <<"GET ", IdRaw/binary>>, _}, Sock) ->
|
|
|
+ if (byte_size(IdRaw) =/= (64 + 2)) ->
|
|
|
+ send(Sock, "INVALID ID.\r\n"),
|
|
|
+ gen_tcp:close(Sock),
|
|
|
+ exit(invalid_id);
|
|
|
+ true -> ok
|
|
|
+ end,
|
|
|
+
|
|
|
+ IdRawList = take(64, binary:bin_to_list(IdRaw)),
|
|
|
+ IsHex = fun(E) -> ((E >= $A) and (E =< $F))
|
|
|
+ or ((E >= $0) and (E =< $9)) end,
|
|
|
+ ValidId = lists:all(IsHex, IdRawList),
|
|
|
+ if ValidId -> ok;
|
|
|
+ true ->
|
|
|
+ send(Sock, "INVALID ID.\r\n"),
|
|
|
+ gen_tcp:close(Sock),
|
|
|
+ exit(invalid_filename)
|
|
|
+ end,
|
|
|
+
|
|
|
+ Id = binary_to_list(string:trim(IdRaw)),
|
|
|
+ io:format("Getting File: `~s`\n", [Id]),
|
|
|
+
|
|
|
+ Fd = open(["./", Id],
|
|
|
+ [read, raw, binary, {encoding, latin1}],
|
|
|
+ Sock),
|
|
|
+
|
|
|
+ Bin = read_all(Fd, Sock),
|
|
|
+
|
|
|
+ send(Sock, [Bin, "\r\n"]),
|
|
|
+
|
|
|
+ gen_tcp:close(Sock);
|
|
|
+
|
|
|
+conn_handler({command, InvalCmd, Exp}, Sock) ->
|
|
|
+ io:format("ERROR: Recieved invalid command: `~w` was expecting `~s`\n",
|
|
|
+ [InvalCmd, Exp]),
|
|
|
+ send(Sock, ["INVALID COMMAND `", string:trim(InvalCmd), "` Expected: ",
|
|
|
+ Exp, "\r\n"]),
|
|
|
+ gen_tcp:close(Sock);
|
|
|
+
|
|
|
+conn_handler({challenge, Bin}, Sock) ->
|
|
|
+ {L, R} = {rand:uniform(255), rand:uniform(255)},
|
|
|
+ {LStr, RStr} = {integer_to_list(L), integer_to_list(R)},
|
|
|
+ Op = rand_choice(['+', '-']),
|
|
|
+ send(Sock, io_lib:format("CHALLENGE ~s ~s ~s.\r\n", [LStr, Op, RStr])),
|
|
|
+ case recv(Sock, 0) of
|
|
|
+ <<"ACCEPT ", ResRaw/binary>> ->
|
|
|
+ Res = binary_to_list(string:trim(ResRaw)),
|
|
|
+ ExRes = integer_to_list(case Op of '+' -> L + R; '-' -> L - R end),
|
|
|
+
|
|
|
+ if (Res == ExRes) -> ok;
|
|
|
+ true ->
|
|
|
+ send(Sock, "CHALLENGE FAILED.\r\n"),
|
|
|
+ gen_tcp:close(Sock),
|
|
|
+ exit(failed_challenge)
|
|
|
+ end,
|
|
|
+
|
|
|
+ send(Sock, "CHALLENGE SUCCESS.\r\n"),
|
|
|
+
|
|
|
+ FileId = binary:encode_hex(crypto:strong_rand_bytes(32)),
|
|
|
+ FileIdStr = binary_to_list(FileId),
|
|
|
+ Fd = open(["./", FileIdStr],
|
|
|
+ [write, exclusive, raw, binary, {encoding, latin1}],
|
|
|
+ Sock),
|
|
|
+ ok = fail_on_error(file:write(Fd, Bin), "write", Sock),
|
|
|
+ close(Fd, Sock),
|
|
|
+ io:format("File with size ~s saved with name ~s",
|
|
|
+ [integer_to_list(byte_size(Bin)), FileIdStr]),
|
|
|
+ send(Sock, ["POSTED ", FileIdStr, ".\r\n"]),
|
|
|
+ gen_tcp:close(Sock);
|
|
|
+ Packet -> conn_handler({command, Packet, "ACCEPT"}, Sock)
|
|
|
+ end;
|
|
|
+
|
|
|
+conn_handler(State, Sock) ->
|
|
|
+ Msg = io_lib:format("Invalid State: `~w`\n", [State]),
|
|
|
+ io:format(Msg),
|
|
|
+ gen_tcp:send(Sock, Msg),
|
|
|
+ gen_tcp:close(Sock).
|
|
|
+
|
|
|
+-spec conn_accepter(RootSock) -> no_return() when
|
|
|
+ RootSock :: gen_tcp:socket().
|
|
|
+conn_accepter(RootSock) ->
|
|
|
+ {ok, ConnSock} = gen_tcp:accept(RootSock),
|
|
|
+
|
|
|
+ spawn(?MODULE, conn_handler, [init, ConnSock]),
|
|
|
+
|
|
|
+ conn_accepter(RootSock).
|