소스 검색

Adding a pastebin server for telnet

Vinicius Teshima 4 달 전
커밋
57d3b7be94
3개의 변경된 파일227개의 추가작업 그리고 0개의 파일을 삭제
  1. 5 0
      Makefile
  2. 16 0
      build.sh
  3. 206 0
      src/pastebin.erl

+ 5 - 0
Makefile

@@ -0,0 +1,5 @@
+
+.PHONY: build
+build:
+	@echo "Running build.sh"
+	@sh build.sh

+ 16 - 0
build.sh

@@ -0,0 +1,16 @@
+#!/bin/sh
+
+find ./src -name '*.erl' -printf '%T@ %p\n' \
+        | sort -r | head -n1 | cut -d' ' -f2 |  while read file
+do
+        out_file="$(basename "$file" | cut -d'.' -f1)"
+        echo "------------------------------------"
+        echo "Compiling file '${file}' into '${out_file}'"
+        (
+        set -x
+        time dialyzer -Wno_unknown --src "$file" || exit 1
+        erlc -Wall -Werror "$file"
+        )
+        echo "------------------------------------"
+done
+

+ 206 - 0
src/pastebin.erl

@@ -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).