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.