Author: bdonlan
Date: 2005-12-09 23:49:14 -0500 (Fri, 09 Dec 2005)
New Revision: 949

Modified:
   trunk/erlang/server/haver_worker.erl
   trunk/erlang/server/schema.hrl
   trunk/erlang/server/server.erl
Log:
Added authentication and registration support

Modified: trunk/erlang/server/haver_worker.erl
===================================================================
--- trunk/erlang/server/haver_worker.erl        2005-12-10 02:46:45 UTC (rev 
948)
+++ trunk/erlang/server/haver_worker.erl        2005-12-10 04:49:14 UTC (rev 
949)
@@ -3,19 +3,63 @@
 -include_lib("mnemosyne/include/mnemosyne.hrl").
 -include("schema.hrl").
 
+% AUTH query utilities
+
+is_authenticated(Uid) ->
+    Record = mnesia:dirty_read({users, Uid}),
+    case Record of
+        [] -> notfound;
+        [R] -> R#users.auth
+    end.
+
+get_userinfo(Uid) ->
+    case mnesia:dirty_read({userattr, Uid}) of
+        [] -> notfound;
+        [R] -> R
+    end.
+
+is_admin(Uid) ->
+    R = get_userinfo(Uid),
+    case R of
+        notfound -> no;
+        _ ->
+            case is_authenticated(Uid) of
+                no -> no;
+                yes -> R#userattr.admin
+            end
+    end.
+
+% digests
+
+available_digests() -> ["sha1", "md5"].
+digest("sha1", Data) -> crypto:sha(Data);
+digest("md5", Data) -> crypto:md5(Data);
+digest(M, _) -> notfound.
+
 haver_sendline(Line) ->
     Socket = get(socket),
-    gen_tcp:send(Socket, haver_protocol:encode(Line)),
-    gen_tcp:send(Socket, "\r\n").
+    io:format("lineenc ~p~n", [haver_protocol:encode(Line)]),
+%    gen_tcp:send(Socket, haver_protocol:encode(Line)),
+%    gen_tcp:send(Socket, "\r\n").
+    gen_tcp:send(Socket, lists:append(haver_protocol:encode(Line), "\r\n")).
 
+check_cap("auth") -> put(can_auth, yes);
+check_cap(_) -> ok.
+
+check_caps([]) -> ok;
+check_caps([H|T]) -> check_cap(H), check_caps(T).
+
 entry(Parent, Socket) ->
     link(Parent),
     put(socket, Socket),
     { ok, { Address, RemotePort } } = inet:peername(Socket),
     put(address, Address),
     receive
-        { line, [ "HAVER", Vers | _ ] } ->
-            haver_sendline(["HAVER", "localhost", "Erver/0.0"]),
+        { line, [ "HAVER", Vers | Capabilities ] } ->
+            put(can_auth, no),
+            check_caps(Capabilities),
+            % XXX configgable hostname
+            haver_sendline(["HAVER", "bd.beginyourfear.com", "Erver/0.0"]),
             haver_login();
         { line, _ } ->
             gen_tcp:close(Socket),
@@ -23,6 +67,62 @@
         Foo -> io:format("haver_entry unknown msg ~p~n", [Foo])
     end.
 
+check_auth(Uid, Cmd, Callback) ->
+    case get_userinfo(Uid) of
+        notfound -> Callback();
+        Info ->
+            do_auth(Uid, Cmd, Callback, Info)
+    end.
+
+make_nonce() -> make_nonce(10).
+make_nonce(0) -> "";
+make_nonce(N) ->
+    [$a + random:uniform(25) | make_nonce(N - 1)].
+
+do_auth(Uid, Cmd, Callback, Info) ->
+    case (get(can_auth)) of
+       no ->
+           haver_sendline(["FAIL", Cmd, "auth.impossible"]),
+           haver_login();
+       yes ->
+           haver_sendline(["AUTH:TYPES", "AUTH:BASIC"]),
+           receive
+               { line, [ "AUTH:TYPE", "AUTH:BASIC" ] } ->
+                   Passcode = Info#userattr.passcode,
+                   Nonce = make_nonce(),
+                   Expect = lists:append(Nonce, Passcode),
+                   haver_sendline(["AUTH:BASIC", Nonce | available_digests()]),
+                   do_wait_auth(Uid, Cmd, Callback, Info, Expect);
+               { line, [ "AUTH:TYPE", T ] } ->
+                   haver_sendline(["FAIL", "unknown.digest", T]),
+                   haver_login();
+               { line, [ "AUTH:CANT" ] } -> haver_login();
+               { line, _ } -> exit(unhandled_XXX)
+           end
+   end.
+
+do_wait_auth(Uid, Cmd, Callback, Info, Expect) ->
+    { _, Stripped } = lists:partition(fun(C) -> C == $= end, Expect),
+    receive
+       { line, [ "AUTH:BASIC", Digest, Response ] } ->
+           case digest(Digest, Expect) of
+               notfound ->
+                    haver_sendline(["FAIL", "AUTH:BASIC", "unknown.digest", 
Digest]),
+                    haver_login();
+               R ->
+                    ER = http_base_64:encode(binary_to_list(R)),
+                    SR = lists:takewhile(fun(C) -> C =/= $= end, ER),
+                    case SR of
+                        Response -> Callback();
+                        _ ->
+                            haver_sendline(["FAIL", "AUTH:BASIC", 
"auth.fail"]),
+                            haver_login()
+                    end
+               end;
+       { line, [ "AUTH:CANT" ] } -> haver_login();
+       { line, L } -> exit({unhandled_XXX, L})
+    end.
+
 try_ident(Uid) ->
     F = fun() ->
         Q = query
@@ -31,7 +131,7 @@
         R = mnemosyne:eval(Q),
         io:format("~p~n", [R]),
         case R of
-            [] -> mnesia:write(#users{name = Uid, pid = self(), ip = 
get(address)}),
+            [] -> mnesia:write(#users{name = Uid, pid = self(), ip = 
get(address), auth = no}),
                   ok;
             _  -> collision
         end
@@ -112,8 +212,10 @@
 
 haver_login() ->
     receive
-        { line, [ "IDENT", Uid ] } -> handle_ident(Uid);
-        { line, [ "GHOST", Uid ] } -> handle_ghost(Uid);
+        { line, [ "IDENT", Uid ] } ->
+            check_auth(Uid, "IDENT", fun() -> handle_ident(Uid) end);
+        { line, [ "GHOST", Uid ] } ->
+            check_auth(Uid, "GHOST", fun() -> handle_ghost(Uid) end);
         { line, _ } ->
             haver_sendline(["DIE", "wrong.command"]),
             gen_tcp:close(get(socket)),
@@ -141,13 +243,18 @@
         end.
 
 haver_open(Channel) ->
+    Uid = get(uid),
+    Owner = case is_authenticated(Uid) of
+        yes -> Uid;
+        no  -> ""
+        end,
     T = fun() ->
         Q = query
             [ C || C <- table(channels), C.name = Channel ]
             end,
         R = mnemosyne:eval(Q),
         case R of
-            [] -> mnesia:write(#channels{name = Channel, notused = Channel}),
+            [] -> mnesia:write(#channels{name = Channel, owner = Uid}),
                   ok;
             _  -> collision
         end end,
@@ -270,6 +377,22 @@
             chan_broadcast(Channel, { sendline, ["IN", Channel, Uid | Message] 
})
     end.
 
+haver_register(Email, Passcode) ->
+    Uid = get(uid),
+    T = fun() ->
+        [Userrec] = mnesia:read(users, Uid, write),
+        Attrrec_maybe = mnesia:read(userattr, Uid, write),
+        Admin = case Attrrec_maybe of
+            [ R ] -> R#userattr.admin;
+            _     -> no
+            end,
+        mnesia:write(Userrec#users{ auth = yes }),
+        Newrec = {userattr, Uid, Passcode, Email, Admin},
+        mnesia:write(Newrec),
+        ok end,
+    { atomic, ok } = mnesia:transaction(T),
+    haver_sendline(["REG:ACCOUNT", Uid, Email]).
+
 haver_mainloop() -> 
     V = receive
         { line, [ "OPEN", Channel ] } ->
@@ -303,6 +426,8 @@
             gen_tcp:close(get(socket)),
             exit({ quit, "bye", Detail});
         { line, [ "BYE" ] } -> self() ! { line, [ "BYE", "bye" ] };
+        { line, [ "REG:ACCOUNT", Email, Passcode ] } ->
+            haver_register(Email, Passcode);
         { line, [ L | T ] } ->
             io:format("unmatched line: ~p~n", [[L|T]]),
             haver_sendline(["FAIL", L, "unknown.cmd"]),

Modified: trunk/erlang/server/schema.hrl
===================================================================
--- trunk/erlang/server/schema.hrl      2005-12-10 02:46:45 UTC (rev 948)
+++ trunk/erlang/server/schema.hrl      2005-12-10 04:49:14 UTC (rev 949)
@@ -1,4 +1,8 @@
 % vim: set ft=erlang :
--record(users, {name, pid, ip}).
--record(channels, {name, notused}).
+-record(users, {name, pid, ip, auth}).
+-record(channels, {name, owner}).
 -record(chanjoin, {channel, user}).
+-record(userattr, {name,
+                   passcode,
+                   email,
+                   admin}).

Modified: trunk/erlang/server/server.erl
===================================================================
--- trunk/erlang/server/server.erl      2005-12-10 02:46:45 UTC (rev 948)
+++ trunk/erlang/server/server.erl      2005-12-10 04:49:14 UTC (rev 949)
@@ -22,6 +22,12 @@
                         {attributes, record_info(fields, channels)},
                         {disc_copies, [node()]},
                         {type, set}
+                        ]),
+    mnesia:delete_table(userattr),
+    mnesia:create_table(userattr, [
+                        {attributes, record_info(fields, userattr)},
+                        {disc_copies, [node()]},
+                        {type, set}
                         ]).
 
 clear_transients() ->
@@ -51,7 +57,11 @@
     end.
 
 do_start(Port) ->
+    io:format("Starting mnesia...~n", []),
     mnesia:start(),
+    io:format("Starting crypto...~n", []),
+    crypto:start(),
+    io:format("Starting mnemosyne...~n", []),
     application:start(mnemosyne),
     case init_tables() of
         ok -> { ok, spawn(?MODULE, cstart, [Port]) };
@@ -155,6 +165,6 @@
         { L, Rem} ->
             Pid ! {line, haver_protocol:decode(L) },
             Rem2 = lists:dropwhile(fun(C) -> ((C == $\r) or (C == $\n)) end, 
Rem),
-            Rem2
+            peer_proc(Rem2, Pid)
     end.
 


Reply via email to