Synchronizing master changes

This commit is contained in:
Mickael Remond 2016-04-12 10:34:24 +02:00
parent 127342449e
commit cd2e2b1a88
No known key found for this signature in database
GPG Key ID: E6F6045D79965AA3
14 changed files with 194 additions and 50 deletions

View File

@ -8,7 +8,7 @@
%%%-------------------------------------------------------------------
{deps, [{lager, ".*", {git, "https://github.com/basho/lager", {tag, "3.0.2"}}},
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.3"}}},
{p1_utils, ".*", {git, "https://github.com/processone/p1_utils", {tag, "1.0.4"}}},
{cache_tab, ".*", {git, "https://github.com/processone/cache_tab", {tag, "1.0.2"}}},
{fast_tls, ".*", {git, "https://github.com/processone/fast_tls", {tag, "1.0.1"}}},
{stringprep, ".*", {git, "https://github.com/processone/stringprep", {tag, "1.0.3"}}},

View File

@ -362,8 +362,8 @@ WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW
CREATE TABLE [dbo].[users] (
[username] [varchar] (250) NOT NULL,
[password] [text] NOT NULL,
[serverkey] [text] NOT NULL,
[salt] [text] NOT NULL,
[serverkey] [text] NOT NULL DEFAULT '',
[salt] [text] NOT NULL DEFAULT '',
[iterationcount] [smallint] NOT NULL DEFAULT 0,
[created_at] [datetime] NOT NULL DEFAULT GETDATE(),
CONSTRAINT [users_PRIMARY] PRIMARY KEY CLUSTERED

View File

@ -19,15 +19,15 @@
CREATE TABLE users (
username varchar(191) PRIMARY KEY,
password text NOT NULL,
serverkey text NOT NULL DEFAULT '',
salt text NOT NULL DEFAULT '',
serverkey varchar(64) NOT NULL DEFAULT '',
salt varchar(64) NOT NULL DEFAULT '',
iterationcount integer NOT NULL DEFAULT 0,
created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- Add support for SCRAM auth to a database created before ejabberd 16.03:
-- ALTER TABLE users ADD COLUMN serverkey text NOT NULL DEFAULT '';
-- ALTER TABLE users ADD COLUMN salt text NOT NULL DEFAULT '';
-- ALTER TABLE users ADD COLUMN serverkey varchar(64) NOT NULL DEFAULT '';
-- ALTER TABLE users ADD COLUMN salt varchar(64) NOT NULL DEFAULT '';
-- ALTER TABLE users ADD COLUMN iterationcount integer NOT NULL DEFAULT 0;
CREATE TABLE last (

View File

@ -45,9 +45,8 @@ mech_new(Host, _GetPassword, _CheckPassword, _CheckPasswordDigest) ->
mech_step(#state{server = Server} = S, ClientIn) ->
User = iolist_to_binary([randoms:get_string(),
randoms:get_string(),
randoms:get_string()]),
jlib:integer_to_binary(p1_time_compat:unique_integer([positive]))]),
case ejabberd_auth:is_user_exists(User, Server) of
true -> mech_step(S, ClientIn);
false -> {ok, [{username, User}, {auth_module, ejabberd_auth_anonymous}]}
false -> {ok, [{username, User}, {authzid, User}, {auth_module, ejabberd_auth_anonymous}]}
end.

View File

@ -56,7 +56,7 @@
%% Create the anonymous table if at least one virtual host has anonymous features enabled
%% Register to login / logout events
-record(anonymous, {us = {<<"">>, <<"">>} :: {binary(), binary()},
sid = {p1_time_compat:timestamp(), self()} :: ejabberd_sm:sid()}).
sid = ejabberd_sm:make_sid() :: ejabberd_sm:sid()}).
start(Host) ->
%% TODO: Check cluster mode

View File

@ -327,7 +327,7 @@ init([{SockMod, Socket}, Opts]) ->
xml_socket = XMLSocket, zlib = Zlib, tls = TLS,
tls_required = StartTLSRequired,
tls_enabled = TLSEnabled, tls_options = TLSOpts,
sid = {p1_time_compat:timestamp(), self()}, streamid = new_id(),
sid = ejabberd_sm:make_sid(), streamid = new_id(),
access = Access, shaper = Shaper, ip = IP,
mgmt_state = StreamMgmtState,
mgmt_max_queue = MaxAckQueue,
@ -522,7 +522,6 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
send_element(StateData,
?POLICY_VIOLATION_ERR(Lang,
<<"Use of STARTTLS required">>)),
send_trailer(StateData),
{stop, normal, StateData};
true ->
fsm_next_state(wait_for_auth,
@ -537,34 +536,28 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) ->
[jlib:ip_to_list(IP), LogReason]),
send_header(StateData, Server, <<"">>, DefaultLang),
send_element(StateData, ?POLICY_VIOLATION_ERR(Lang, ReasonT)),
send_trailer(StateData),
{stop, normal, StateData};
_ ->
send_header(StateData, ?MYNAME, <<"">>, DefaultLang),
send_element(StateData, ?HOST_UNKNOWN_ERR),
send_trailer(StateData),
{stop, normal, StateData}
end;
_ ->
send_header(StateData, ?MYNAME, <<"">>, DefaultLang),
send_element(StateData, ?INVALID_NS_ERR),
send_trailer(StateData),
{stop, normal, StateData}
end;
wait_for_stream(timeout, StateData) ->
{stop, normal, StateData};
wait_for_stream({xmlstreamelement, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
wait_for_stream({xmlstreamend, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
wait_for_stream({xmlstreamerror, _}, StateData) ->
send_header(StateData, ?MYNAME, <<"1.0">>, <<"">>),
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
wait_for_stream(closed, StateData) ->
{stop, normal, StateData};
@ -724,10 +717,9 @@ wait_for_auth({xmlstreamelement, El}, StateData) ->
wait_for_auth(timeout, StateData) ->
{stop, normal, StateData};
wait_for_auth({xmlstreamend, _Name}, StateData) ->
send_trailer(StateData), {stop, normal, StateData};
{stop, normal, StateData};
wait_for_auth({xmlstreamerror, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
wait_for_auth(closed, StateData) ->
{stop, normal, StateData};
@ -843,7 +835,6 @@ wait_for_feature_request({xmlstreamelement, El},
send_element(StateData,
?POLICY_VIOLATION_ERR(Lang,
<<"Use of STARTTLS required">>)),
send_trailer(StateData),
{stop, normal, StateData};
true ->
process_unauthenticated_stanza(StateData, El),
@ -854,11 +845,10 @@ wait_for_feature_request(timeout, StateData) ->
{stop, normal, StateData};
wait_for_feature_request({xmlstreamend, _Name},
StateData) ->
send_trailer(StateData), {stop, normal, StateData};
{stop, normal, StateData};
wait_for_feature_request({xmlstreamerror, _},
StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
wait_for_feature_request(closed, StateData) ->
{stop, normal, StateData};
@ -963,11 +953,10 @@ wait_for_sasl_response(timeout, StateData) ->
{stop, normal, StateData};
wait_for_sasl_response({xmlstreamend, _Name},
StateData) ->
send_trailer(StateData), {stop, normal, StateData};
{stop, normal, StateData};
wait_for_sasl_response({xmlstreamerror, _},
StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
wait_for_sasl_response(closed, StateData) ->
{stop, normal, StateData};
@ -1092,10 +1081,9 @@ wait_for_bind({xmlstreamelement, El}, StateData) ->
wait_for_bind(timeout, StateData) ->
{stop, normal, StateData};
wait_for_bind({xmlstreamend, _Name}, StateData) ->
send_trailer(StateData), {stop, normal, StateData};
{stop, normal, StateData};
wait_for_bind({xmlstreamerror, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
wait_for_bind(closed, StateData) ->
{stop, normal, StateData};
@ -1168,7 +1156,6 @@ session_established({xmlstreamelement, El},
case check_from(El, FromJID) of
'invalid-from' ->
send_element(StateData, ?INVALID_FROM),
send_trailer(StateData),
{stop, normal, StateData};
_NewEl ->
session_established2(El, StateData)
@ -1181,17 +1168,15 @@ session_established(timeout, StateData) ->
[?MODULE, Options, session_established, StateData]),
fsm_next_state(session_established, StateData);
session_established({xmlstreamend, _Name}, StateData) ->
send_trailer(StateData), {stop, normal, StateData};
{stop, normal, StateData};
session_established({xmlstreamerror,
<<"XML stanza is too big">> = E},
StateData) ->
send_element(StateData,
?POLICY_VIOLATION_ERR((StateData#state.lang), E)),
send_trailer(StateData),
{stop, normal, StateData};
session_established({xmlstreamerror, _}, StateData) ->
send_element(StateData, ?INVALID_XML_ERR),
send_trailer(StateData),
{stop, normal, StateData};
session_established(closed, #state{mgmt_state = active} = StateData) ->
catch (StateData#state.sockmod):close(StateData#state.socket),
@ -1346,7 +1331,6 @@ handle_info(kick, StateName, StateData) ->
handle_info({kick, kicked_by_admin, Xmlelement}, StateName, StateData);
handle_info({kick, Reason, Xmlelement}, _StateName, StateData) ->
send_element(StateData, Xmlelement),
send_trailer(StateData),
{stop, normal,
StateData#state{authenticated = Reason}};
handle_info({route, _From, _To, {broadcast, Data}},
@ -1359,7 +1343,6 @@ handle_info({route, _From, _To, {broadcast, Data}},
{exit, Reason} ->
Lang = StateData#state.lang,
send_element(StateData, ?SERRT_CONFLICT(Lang, Reason)),
catch send_trailer(StateData),
{stop, normal, StateData};
{privacy_list, PrivList, PrivListName} ->
case ejabberd_hooks:run_fold(privacy_updated_list,
@ -1673,11 +1656,9 @@ handle_info(system_shutdown, StateName, StateData) ->
wait_for_stream ->
send_header(StateData, ?MYNAME, <<"1.0">>, <<"en">>),
send_element(StateData, ?SERR_SYSTEM_SHUTDOWN),
send_trailer(StateData),
ok;
_ ->
send_element(StateData, ?SERR_SYSTEM_SHUTDOWN),
send_trailer(StateData),
ok
end,
{stop, normal, StateData};
@ -1809,6 +1790,7 @@ terminate(_Reason, StateName, StateData) ->
ok
end
end,
catch send_trailer(StateData),
(StateData#state.sockmod):close(StateData#state.socket),
ok.
@ -2431,7 +2413,6 @@ fsm_next_state(session_established, #state{mgmt_max_queue = exceeded} =
Err = ?SERRT_POLICY_VIOLATION(StateData#state.lang,
<<"Too many unacked stanzas">>),
send_element(StateData, Err),
send_trailer(StateData),
{stop, normal, StateData#state{mgmt_resend = false}};
fsm_next_state(session_established, #state{mgmt_state = pending} = StateData) ->
fsm_next_state(wait_for_resume, StateData);

View File

@ -48,7 +48,7 @@
-behaviour(ejabberd_config).
-author('alexey@process-one.net').
-export([start/0, init/0, process/1,
-export([start/0, init/0, process/1, process2/2,
register_commands/3, unregister_commands/3,
opt_type/1]).
@ -231,6 +231,10 @@ process(Args, Version) ->
Code.
%% @spec (Args::[string()], AccessCommands) -> {String::string(), Code::integer()}
process2(Args, AccessCommands) ->
process2(Args, AccessCommands, ?DEFAULT_VERSION).
%% @spec (Args::[string()], AccessCommands, Version) -> {String::string(), Code::integer()}
process2(["--auth", User, Server, Pass | Args], AccessCommands, Version) ->
process2(Args, AccessCommands, {list_to_binary(User), list_to_binary(Server),
list_to_binary(Pass), true}, Version);

View File

@ -325,7 +325,7 @@ wait_for_feature_request({xmlstreamelement, El},
{s2s_tls_compression, StateData#state.server},
fun(true) -> true;
(false) -> false
end, true) of
end, false) of
true -> lists:delete(compression_none, TLSOpts1);
false -> [compression_none | TLSOpts1]
end,

View File

@ -192,7 +192,7 @@ init([From, Server, Type]) ->
{s2s_tls_compression, From},
fun(true) -> true;
(false) -> false
end, true) of
end, false) of
false -> [compression_none | TLSOpts4];
true -> TLSOpts4
end,

View File

@ -66,7 +66,8 @@
get_max_user_sessions/2,
get_all_pids/0,
is_existing_resource/3,
get_commands_spec/0
get_commands_spec/0,
make_sid/0
]).
-export([init/1, handle_call/3, handle_cast/2,
@ -806,6 +807,9 @@ kick_user(User, Server) ->
end, Resources),
length(Resources).
make_sid() ->
{p1_time_compat:unique_timestamp(), self()}.
opt_type(sm_db_type) ->
fun (mnesia) -> mnesia;
(internal) -> mnesia;

View File

@ -31,7 +31,7 @@
-include("logger.hrl").
-export([start/2, stop/1, compile/1, get_cookie/0,
remove_node/1, set_password/3,
remove_node/1, set_password/3, check_password/3,
check_password_hash/4, delete_old_users/1,
delete_old_users_vhost/2, ban_account/3,
num_active_users/2, num_resources/2, resource_num/3,
@ -162,7 +162,7 @@ get_commands_spec() ->
result_desc = "Status code: 0 on success, 1 otherwise"},
#ejabberd_commands{name = check_password, tags = [accounts],
desc = "Check if a password is correct",
module = ejabberd_auth, function = check_password,
module = ?MODULE, function = check_password,
args = [{user, binary}, {host, binary}, {password, binary}],
args_example = [<<"peter">>, <<"myserver.com">>, <<"secret">>],
args_desc = ["User name to check", "Server to check", "Password to check"],
@ -593,6 +593,9 @@ set_password(User, Host, Password) ->
Fun = fun () -> ejabberd_auth:set_password(User, Host, Password) end,
user_action(User, Host, Fun, ok).
check_password(User, Host, Password) ->
ejabberd_auth:check_password(User, <<>>, Host, Password).
%% Copied some code from ejabberd_commands.erl
check_password_hash(User, Host, PasswordHash, HashMethod) ->
AccountPass = ejabberd_auth:get_password_s(User, Host),

View File

@ -375,7 +375,7 @@ try_set_password(User, Server, Password, IQ, SubEl,
Txt = <<"Empty password">>,
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_BAD_REQUEST(Lang, Txt)]};
{error, not_allowed} ->
Txt = <<"Chaning password is not allowed">>,
Txt = <<"Changing password is not allowed">>,
IQ#iq{type = error, sub_el = [SubEl, ?ERRT_NOT_ALLOWED(Lang, Txt)]};
{error, invalid_jid} ->
IQ#iq{type = error,

View File

@ -0,0 +1,153 @@
# ----------------------------------------------------------------------
#
# ejabberd, Copyright (C) 2002-2016 ProcessOne
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# ----------------------------------------------------------------------
defmodule EjabberdCyrsaslTest do
@author "pawel@process-one.net"
use ExUnit.Case, async: true
setup_all do
:p1_sha.load_nif()
:mnesia.start
:ok = start_module(:stringprep)
:ok = start_module(:jid)
:ok = :ejabberd_config.start(["domain1"], [])
:ok = :cyrsasl.start
cyrstate = :cyrsasl.server_new("domain1", "domain1", "domain1", :ok, &get_password/1,
&check_password/3, &check_password_digest/5)
{:ok, cyrstate: cyrstate}
end
test "Plain text (correct user and pass)", context do
step1 = :cyrsasl.server_start(context[:cyrstate], "PLAIN", <<0,"user1",0,"pass">>)
assert {:ok, _} = step1
{:ok, kv} = step1
assert kv[:authzid] == "user1", "got correct user"
end
test "Plain text (correct user wrong pass)", context do
step1 = :cyrsasl.server_start(context[:cyrstate], "PLAIN", <<0,"user1",0,"badpass">>)
assert step1 == {:error, "not-authorized", "user1"}, "got error response"
end
test "Plain text (wrong user wrong pass)", context do
step1 = :cyrsasl.server_start(context[:cyrstate], "PLAIN", <<0,"nouser1",0,"badpass">>)
assert step1 == {:error, "not-authorized", "nouser1"}, "got error response"
end
test "Anonymous", context do
setup_anonymous_mocks()
step1 = :cyrsasl.server_start(context[:cyrstate], "ANONYMOUS", "domain1")
assert {:ok, _} = step1
end
test "Digest-MD5 (correct user and pass)", context do
assert {:continue, init_str, state1} = :cyrsasl.server_start(context[:cyrstate], "DIGEST-MD5", "")
assert [_, nonce] = Regex.run(~r/nonce="(.*?)"/, init_str)
user = "user1"
domain = "domain1"
digest_uri = "xmpp/#{domain}"
pass = "pass"
cnonce = "abcd"
nc = "00000001"
response_hash = calc_digest_sha(user, domain, pass, nc, nonce, cnonce)
response = "username=\"#{user}\",realm=\"#{domain}\",nonce=\"#{nonce}\",cnonce=\"#{cnonce}\"," <>
"nc=\"#{nc}\",qop=auth,digest-uri=\"#{digest_uri}\",response=\"#{response_hash}\"," <>
"charset=utf-8,algorithm=md5-sess"
assert {:continue, calc_str, state3} = :cyrsasl.server_step(state1, response)
assert {:ok, list} = :cyrsasl.server_step(state3, "")
end
defp calc_digest_sha(user, domain, pass, nc, nonce, cnonce) do
digest_uri = "xmpp/#{domain}"
a0 = "#{user}:#{domain}:#{pass}"
a1 = "#{str_md5(a0)}:#{nonce}:#{cnonce}"
a2 = "AUTHENTICATE:#{digest_uri}"
hex_md5("#{hex_md5(a1)}:#{nonce}:#{nc}:#{cnonce}:auth:#{hex_md5(a2)}")
end
defp str_md5(str) do
:erlang.md5(str)
end
defp hex_md5(str) do
:p1_sha.to_hexlist(:erlang.md5(str))
end
defp setup_anonymous_mocks() do
:meck.unload
mock(:ejabberd_auth_anonymous, :is_sasl_anonymous_enabled,
fn (host) ->
true
end)
mock(:ejabberd_auth, :is_user_exists,
fn (user, domain) ->
domain == "domain1" and get_password(user) != false
end)
end
defp start_module(module) do
case apply(module, :start, []) do
:ok -> :ok
{:error, {:already_started, _}} -> :ok
other -> other
end
end
defp get_password(user) do
if user == "user1" or user == "user2" do
{"pass", :internal}
else
:false
end
end
defp check_password(user, authzid, pass) do
case get_password(authzid) do
{^pass, mod} ->
{true, mod}
_ ->
false
end
end
defp check_password_digest(user, authzid, pass, digest, digest_gen) do
case get_password(authzid) do
{spass, mod} ->
v = digest_gen.(spass)
if v == digest do
{true, mod}
else
false
end
_ ->
false
end
end
defp mock(module, function, fun) do
try do
:meck.new(module, [:non_strict])
catch
:error, {:already_started, _pid} -> :ok
end
:meck.expect(module, function, fun)
end
end

View File

@ -73,13 +73,13 @@ defmodule EjabberdModAdminExtraTest do
EjabberdAuthMock.create_user @user, @domain, @password
assert :ejabberd_commands.execute_command(:check_password,
[@user, <<"">>, @domain, @password])
[@user, @domain, @password])
refute :ejabberd_commands.execute_command(:check_password,
[@user, <<"">>, @domain, "bad_password"])
[@user, @domain, "bad_password"])
refute :ejabberd_commands.execute_command(:check_password,
[@user, <<"">>, "bad_domain", @password])
[@user, "bad_domain", @password])
refute :ejabberd_commands.execute_command(:check_password,
["bad_user", <<"">>, @domain, @password])
["bad_user", @domain, @password])
assert :meck.validate :ejabberd_auth
@ -117,9 +117,9 @@ defmodule EjabberdModAdminExtraTest do
assert :ejabberd_commands.execute_command(:change_password,
[@user, @domain, "new_password"])
refute :ejabberd_commands.execute_command(:check_password,
[@user, <<"">>, @domain, @password])
[@user, @domain, @password])
assert :ejabberd_commands.execute_command(:check_password,
[@user, <<"">>, @domain, "new_password"])
[@user, @domain, "new_password"])
assert {:not_found, 'unknown_user'} ==
catch_throw :ejabberd_commands.execute_command(:change_password,
["bad_user", @domain,