There is patch for 2.1.12 version which can store password hashes instead plain passwords in mysql 'users' table.
This is optional. Additional option {password_type,hashed} should be set to enable this feature.
Also I added second option {auth_mechanisms, [digest]}. Which just starts only particular auth backend. Just I want to have exactly defined mechanisms. So for my purpose I use {auth_mechanisms, [digest,plain]}.
Note, mysql.sql schema is changed a bit. Thus to use patch on existent system ou need to alter 'users' table as it is in scheme.
Here is patch
Index: src/ejabberd_auth_odbc.erl
===================================================================
--- src/ejabberd_auth_odbc.erl (.../vendor/2.1.12) (revision 13)
+++ src/ejabberd_auth_odbc.erl (.../trunk) (revision 13)
@@ -70,11 +70,11 @@
Username = ejabberd_odbc:escape(LUser),
LServer = jlib:nameprep(Server),
try odbc_queries:get_password(LServer, Username) of
- {selected, ["password"], [{Password}]} ->
+ {selected, ["password","ha1"], [{Password,_Ha1}]} ->
Password /= ""; %% Password is correct, and not empty
- {selected, ["password"], [{_Password2}]} ->
+ {selected, ["password","ha1"], [{_Password2,_Ha1}]} ->
false; %% Password is not correct
- {selected, ["password"], []} ->
+ {selected, ["password","ha1"], []} ->
false; %% Account does not exist
{error, _Error} ->
false %% Typical error is that table doesn't exist
@@ -94,7 +94,7 @@
LServer = jlib:nameprep(Server),
try odbc_queries:get_password(LServer, Username) of
%% Account exists, check if password is valid
- {selected, ["password"], [{Passwd}]} ->
+ {selected, ["password","ha1"], [{Passwd,_Ha1}]} ->
DigRes = if
Digest /= "" ->
Digest == DigestGen(Passwd);
@@ -106,7 +106,7 @@
true ->
(Passwd == Password) and (Password /= "")
end;
- {selected, ["password"], []} ->
+ {selected, ["password","ha1"], []} ->
false; %% Account does not exist
{error, _Error} ->
false %% Typical error is that table doesn't exist
@@ -200,11 +200,19 @@
LUser ->
Username = ejabberd_odbc:escape(LUser),
LServer = jlib:nameprep(Server),
- case catch odbc_queries:get_password(LServer, Username) of
- {selected, ["password"], [{Password}]} ->
- Password;
+ {P,H} = case catch odbc_queries:get_password(LServer, Username) of
+ {selected, ["password","ha1"], [{Password,Ha1}]} ->
+ {Password,Ha1};
_ ->
- false
+ {null,null}
+ end,
+ case {P,H} of
+ {null,null} ->
+ false;
+ {_,null} ->
+ P;
+ _ ->
+ {P,H}
end
end.
@@ -215,11 +223,19 @@
LUser ->
Username = ejabberd_odbc:escape(LUser),
LServer = jlib:nameprep(Server),
- case catch odbc_queries:get_password(LServer, Username) of
- {selected, ["password"], [{Password}]} ->
- Password;
+ {P,H} = case catch odbc_queries:get_password(LServer, Username) of
+ {selected, ["password", "ha1"], [{Password,Ha1}]} ->
+ {Password,Ha1};
_ ->
- ""
+ {null,null}
+ end,
+ case {P,H} of
+ {null,null} ->
+ "";
+ {_,null} ->
+ P;
+ _ ->
+ {P,H}
end
end.
@@ -232,9 +248,9 @@
Username = ejabberd_odbc:escape(LUser),
LServer = jlib:nameprep(Server),
try odbc_queries:get_password(LServer, Username) of
- {selected, ["password"], [{_Password}]} ->
+ {selected, ["password","ha1"], [{_Password,_Ha1}]} ->
true; %% Account exists
- {selected, ["password"], []} ->
+ {selected, ["password","ha1"], []} ->
false; %% Account does not exist
{error, Error} ->
{error, Error} %% Typical error is that table doesn't exist
Index: src/cyrsasl_digest.erl
===================================================================
--- src/cyrsasl_digest.erl (.../vendor/2.1.12) (revision 13)
+++ src/cyrsasl_digest.erl (.../trunk) (revision 13)
@@ -78,15 +78,37 @@
case (State#state.get_password)(UserName) of
{false, _} ->
{error, "not-authorized", UserName};
+ {{Passwd, Ha1}, AuthModule} ->
+ ?DEBUG("Funney-Hash ~p, ~p, ~p",[UserName,Passwd,Ha1]),
+ case (State#state.check_password)(UserName, "",
+ xml:get_attr_s("response", KeyVals),
+ fun(PW) -> response(KeyVals, UserName, PW, Nonce, AuthzId,
+ "AUTHENTICATE",Ha1) end) of
+ {true, _} ->
+
+ RspAuth = response(KeyVals,
+ UserName, Passwd,
+ Nonce, AuthzId, "", Ha1),
+ {continue,
+ "rspauth=" ++ RspAuth,
+ State#state{step = 5,
+ auth_module = AuthModule,
+ username = UserName,
+ authzid = AuthzId}};
+ false ->
+ {error, "not-authorized", UserName};
+ {false, _} ->
+ {error, "not-authorized", UserName}
+ end;
{Passwd, AuthModule} ->
case (State#state.check_password)(UserName, "",
xml:get_attr_s("response", KeyVals),
fun(PW) -> response(KeyVals, UserName, PW, Nonce, AuthzId,
- "AUTHENTICATE") end) of
+ "AUTHENTICATE","") end) of
{true, _} ->
RspAuth = response(KeyVals,
UserName, Passwd,
- Nonce, AuthzId, ""),
+ Nonce, AuthzId, "", ""),
{continue,
"rspauth=" ++ RspAuth,
State#state{step = 5,
@@ -214,7 +236,7 @@
digit_to_xchar(N div 16) | Res]).
-response(KeyVals, User, Passwd, Nonce, AuthzId, A2Prefix) ->
+response(KeyVals, User, Passwd, Nonce, AuthzId, A2Prefix, Ha1) ->
Realm = xml:get_attr_s("realm", KeyVals),
CNonce = xml:get_attr_s("cnonce", KeyVals),
DigestURI = xml:get_attr_s("digest-uri", KeyVals),
@@ -222,13 +244,27 @@
QOP = xml:get_attr_s("qop", KeyVals),
A1 = case AuthzId of
"" ->
- binary_to_list(
- crypto:md5(User ++ ":" ++ Realm ++ ":" ++ Passwd)) ++
- ":" ++ Nonce ++ ":" ++ CNonce;
+ case Ha1 of
+ "" ->
+ binary_to_list(
+ crypto:md5(User ++ ":" ++ Realm ++ ":" ++ Passwd)) ++
+ ":" ++ Nonce ++ ":" ++ CNonce;
+ _ ->
+ binary_to_list(
+ hex:hexstr_to_bin(Ha1)) ++
+ ":" ++ Nonce ++ ":" ++ CNonce
+ end;
_ ->
- binary_to_list(
- crypto:md5(User ++ ":" ++ Realm ++ ":" ++ Passwd)) ++
- ":" ++ Nonce ++ ":" ++ CNonce ++ ":" ++ AuthzId
+ case Ha1 of
+ "" ->
+ binary_to_list(
+ crypto:md5(User ++ ":" ++ Realm ++ ":" ++ Passwd)) ++
+ ":" ++ Nonce ++ ":" ++ CNonce ++ ":" ++ AuthzId;
+ _ ->
+ binary_to_list(
+ hex:hexstr_to_bin(Ha1)) ++
+ ":" ++ Nonce ++ ":" ++ CNonce ++ ":" ++ AuthzId
+ end
end,
A2 = case QOP of
"auth" ->
Index: src/cyrsasl.erl
===================================================================
--- src/cyrsasl.erl (.../vendor/2.1.12) (revision 13)
+++ src/cyrsasl.erl (.../trunk) (revision 13)
@@ -52,12 +52,50 @@
ets:new(sasl_mechanism, [named_table,
public,
{keypos, #sasl_mechanism.mechanism}]),
+
+ Mechs = ejabberd_config:get_local_option(auth_mechanisms),
+ case Mechs of
+ undefined ->
+ start_all_mechs();
+ _ ->
+ case is_atom(Mechs) of
+ true ->
+ start_all_mechs();
+ false ->
+ case lists:flatlength(Mechs) of
+ 0 ->
+ start_all_mechs();
+ _ ->
+ start_mechs(Mechs)
+ end
+ end
+ end,
+ ok.
+
+start_all_mechs() ->
cyrsasl_plain:start([]),
cyrsasl_digest:start([]),
cyrsasl_scram:start([]),
- cyrsasl_anonymous:start([]),
- ok.
+ cyrsasl_anonymous:start([]).
+start_mechs([M | Mechs]) ->
+ ?DEBUG("Starting: ~p",[M]),
+ case M of
+ digest ->
+ cyrsasl_digest:start([]);
+ plain ->
+ cyrsasl_plain:start([]);
+ scram ->
+ cyrsasl_scram:start([]);
+ anon ->
+ cyrsasl_anonymous:start([])
+ end,
+ start_mechs(Mechs);
+
+start_mechs([]) ->
+ [].
+
+
register_mechanism(Mechanism, Module, PasswordType) ->
ets:insert(sasl_mechanism,
#sasl_mechanism{mechanism = Mechanism,
Index: src/odbc/mysql.sql
===================================================================
--- src/odbc/mysql.sql (.../vendor/2.1.12) (revision 13)
+++ src/odbc/mysql.sql (.../trunk) (revision 13)
@@ -23,6 +23,7 @@
CREATE TABLE users (
username varchar(250) PRIMARY KEY,
password text NOT NULL,
+ ha1 text,
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) CHARACTER SET utf8;
Index: src/odbc/odbc_queries.erl
===================================================================
--- src/odbc/odbc_queries.erl (.../vendor/2.1.12) (revision 13)
+++ src/odbc/odbc_queries.erl (.../trunk) (revision 13)
@@ -167,23 +167,46 @@
get_password(LServer, Username) ->
ejabberd_odbc:sql_query(
LServer,
- ["select password from users "
+ ["select password, ha1 from users "
"where username='", Username, "';"]).
set_password_t(LServer, Username, Pass) ->
- ejabberd_odbc:sql_transaction(
- LServer,
- fun() ->
- update_t("users", ["username", "password"],
- [Username, Pass],
- ["username='", Username ,"'"])
- end).
+ PS = ejabberd_config:get_local_option(password_type),
+ case PS of
+ hashed ->
+ Ha1 = hex:bin_to_hexstr(crypto:md5(Username ++ ":" ++ LServer ++ ":" ++ Pass)),
+ ejabberd_odbc:sql_transaction(
+ LServer,
+ fun() ->
+ update_t("users", ["username", "ha1"],
+ [Username, Ha1],
+ ["username='", Username ,"'"])
+ end);
+ _ ->
+ ejabberd_odbc:sql_transaction(
+ LServer,
+ fun() ->
+ update_t("users", ["username", "password"],
+ [Username, Pass],
+ ["username='", Username ,"'"])
+ end)
+ end.
add_user(LServer, Username, Pass) ->
- ejabberd_odbc:sql_query(
- LServer,
- ["insert into users(username, password) "
- "values ('", Username, "', '", Pass, "');"]).
+ PS = ejabberd_config:get_local_option(password_type),
+ case PS of
+ hashed ->
+ Ha1 = hex:bin_to_hexstr(crypto:md5(Username ++ ":" ++ LServer ++ ":" ++ Pass)),
+ ejabberd_odbc:sql_query(
+ LServer,
+ ["insert into users(username, ha1) "
+ "values ('", Username, "', '", Ha1, "');"]);
+ _ ->
+ ejabberd_odbc:sql_query(
+ LServer,
+ ["insert into users(username, password) "
+ "values ('", Username, "', '", Pass, "');"])
+ end.
del_user(LServer, Username) ->
ejabberd_odbc:sql_query(
@@ -191,12 +214,24 @@
["delete from users where username='", Username ,"';"]).
del_user_return_password(_LServer, Username, Pass) ->
- P = ejabberd_odbc:sql_query_t(
- ["select password from users where username='",
- Username, "';"]),
- ejabberd_odbc:sql_query_t(["delete from users "
- "where username='", Username,
- "' and password='", Pass, "';"]),
+ PS = ejabberd_config:get_local_option(password_type),
+ P = case PS of
+ hashed ->
+ Ha1 = hex:bin_to_hexstr(crypto:md5(Username ++ ":" ++ _LServer ++ ":" ++ Pass)),
+ ejabberd_odbc:sql_query_t(
+ ["select ha1 from users where username='",
+ Username, "';"]),
+ ejabberd_odbc:sql_query_t(["delete from users "
+ "where username='", Username,
+ "' and ha1='", Ha1, "';"]);
+ _ ->
+ ejabberd_odbc:sql_query_t(
+ ["select password from users where username='",
+ Username, "';"]),
+ ejabberd_odbc:sql_query_t(["delete from users "
+ "where username='", Username,
+ "' and password='", Pass, "';"])
+ end,
P.
list_users(LServer) ->
Index: src/ejabberd.cfg.example
===================================================================
--- src/ejabberd.cfg.example (.../vendor/2.1.12) (revision 13)
+++ src/ejabberd.cfg.example (.../trunk) (revision 13)
@@ -90,6 +90,9 @@
%%
{hosts, ["localhost"]}.
+%%{auth_mechanisms,[digest,plain]}.
+%%{password_type,hashed}.
+
%%
%% route_subdomains: Delegate subdomains to other XMPP servers.
%% For example, if this ejabberd serves example.org and you want
Index: src/ejabberd_config.erl
===================================================================
--- src/ejabberd_config.erl (.../vendor/2.1.12) (revision 13)
+++ src/ejabberd_config.erl (.../trunk) (revision 13)
@@ -443,6 +443,10 @@
State;
{max_fsm_queue, N} ->
add_option(max_fsm_queue, N, State);
+ {password_type, PasswordType} ->
+ add_option(password_type, PasswordType, State);
+ {auth_mechanisms, AuthMechs} ->
+ add_option(auth_mechanisms, AuthMechs, State);
{_Opt, _Val} ->
lists:foldl(fun(Host, S) -> process_host_term(Term, Host, S) end,
State, State#state.hosts)
Index: src/hex.erl
===================================================================
--- src/hex.erl (.../vendor/2.1.12) (revision 0)
+++ src/hex.erl (.../trunk) (revision 13)
@@ -0,0 +1,33 @@
+-module(hex).
+-export([bin_to_hexstr/1,hexstr_to_bin/1]).
+
+hex(N) when N < 10 ->
+ $0+N;
+hex(N) when N >= 10, N < 16 ->
+ $a+(N-10).
+
+int(C) when $0 =< C, C =< $9 ->
+ C - $0;
+int(C) when $A =< C, C =< $F ->
+ C - $A + 10;
+int(C) when $a =< C, C =< $f ->
+ C - $a + 10.
+
+to_hex(N) when N < 256 ->
+ [hex(N div 16), hex(N rem 16)].
+
+list_to_hexstr([]) ->
+ [];
+list_to_hexstr([H|T]) ->
+ to_hex(H) ++ list_to_hexstr(T).
+
+bin_to_hexstr(Bin) ->
+ list_to_hexstr(binary_to_list(Bin)).
+
+hexstr_to_bin(S) ->
+ list_to_binary(hexstr_to_list(S)).
+
+hexstr_to_list([X,Y|T]) ->
+ [int(X)*16 + int(Y) | hexstr_to_list(T)];
+hexstr_to_list([]) ->
+ [].