%% end.
check_credentials(_State, Props) ->
- User = proplists:get_value(username, Props, <<>>),
+ User = proplists:get_value(authzid, Props, <<>>),
- case jlib:nodeprep(User) of
+ case jid:nodeprep(User) of
error -> {error, <<"not-authorized">>};
<<"">> -> {error, <<"not-authorized">>};
_LUser -> ok
[<<"">>, UserMaybeDomain, Password] ->
case parse_domain(UserMaybeDomain) of
%% <NUL>login@domain<NUL>pwd
- [User, _Domain] -> [UserMaybeDomain, User, Password];
+ [User, _Domain] -> [User, User, Password];
%% <NUL>login<NUL>pwd
- [User] -> [<<"">>, User, Password]
+ [User] -> [User, User, Password]
end;
- %% login@domain<NUL>login<NUL>pwd
+ [AuthzId, User, Password] ->
+ case parse_domain(AuthzId) of
- [AuthzId, User, Password] -> [AuthzId, User, Password];
+ %% login@domain<NUL>login<NUL>pwd
+ [AuthzUser, _Domain] -> [AuthzUser, User, Password];
+ %% login<NUL>login<NUL>pwd
+ [AuthzUser] -> [AuthzUser, User, Password]
+ end;
_ -> error
end.
end.
%% @doc Check if the user and password can login in server.
- %% @spec (User::string(), Server::string(), Password::string(),
+ %% @spec (User::string(), AuthzId::string(), Server::string(), Password::string(),
%% Digest::string(), DigestGen::function()) ->
%% true | false
- -spec check_password(binary(), binary(), binary(), binary(),
+ -spec check_password(binary(), binary(), binary(), binary(), binary(),
fun((binary()) -> binary())) -> boolean().
-
+
- check_password(User, Server, Password, Digest,
+ check_password(User, AuthzId, Server, Password, Digest,
DigestGen) ->
- case check_password_with_authmodule(User, Server,
+ case check_password_with_authmodule(User, AuthzId, Server,
Password, Digest, DigestGen)
of
{true, _AuthModule} -> true;
unregister_connection/3
]).
- -export([login/2, set_password/3, check_password/3,
- check_password/5, try_register/3,
-
-%% Function used by ejabberd_auth:
+ -export([login/2, set_password/3, check_password/4,
+ check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
- get_vh_registered_users/2, get_vh_registered_users_number/1,
- get_vh_registered_users_number/2, get_password_s/2,
+ get_vh_registered_users/2,
+ get_vh_registered_users_number/1,
+ get_vh_registered_users_number/2, get_password_s/2,
get_password/2, get_password/3, is_user_exists/2,
remove_user/2, remove_user/3, store_type/0,
- plain_password_required/0]).
+ plain_password_required/0, opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
-behaviour(ejabberd_auth).
- -export([start/1, set_password/3, check_password/3,
- check_password/5, try_register/3,
-%% External exports
+ -export([start/1, set_password/3, check_password/4,
+ check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
store_type() -> external.
- check_password(User, Server, Password) ->
+ check_password(User, AuthzId, Server, Password) ->
+ if AuthzId /= <<>> andalso AuthzId /= User ->
+ false;
+ true ->
- case get_cache_option(Server) of
+ case get_cache_option(Server) of
- false -> check_password_extauth(User, Server, Password);
+ false -> check_password_extauth(User, AuthzId, Server, Password);
- {true, CacheTime} ->
+ {true, CacheTime} ->
- check_password_cache(User, Server, Password, CacheTime)
+ check_password_cache(User, AuthzId, Server, Password, CacheTime)
+ end
end.
- check_password(User, Server, Password, _Digest,
+ check_password(User, AuthzId, Server, Password, _Digest,
_DigestGen) ->
- check_password(User, Server, Password).
+ check_password(User, AuthzId, Server, Password).
set_password(User, Server, Password) ->
case extauth:set_password(User, Server, Password) of
-behaviour(ejabberd_auth).
- -export([start/1, set_password/3, check_password/3,
- check_password/5, try_register/3,
-%% External exports
+ -export([start/1, set_password/3, check_password/4,
+ check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
true -> scram %% allows: PLAIN SCRAM
end.
- check_password(User, Server, Password) ->
- LUser = jid:nodeprep(User),
- LServer = jid:nameprep(Server),
+ check_password(User, AuthzId, Server, Password) ->
+ if AuthzId /= <<>> andalso AuthzId /= User ->
+ false;
+ true ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
- US = {LUser, LServer},
- case catch mnesia:dirty_read({passwd, US}) of
- [#passwd{password = Password}]
- when is_binary(Password) ->
- Password /= <<"">>;
- [#passwd{password = Scram}]
- when is_record(Scram, scram) ->
- is_password_scram_valid(Password, Scram);
- _ -> false
++ LUser = jid:nodeprep(User),
++ LServer = jid:nameprep(Server),
+ US = {LUser, LServer},
+ case catch mnesia:dirty_read({passwd, US}) of
+ [#passwd{password = Password}]
+ when is_binary(Password) ->
+ Password /= <<"">>;
+ [#passwd{password = Scram}]
+ when is_record(Scram, scram) ->
+ is_password_scram_valid(Password, Scram);
+ _ -> false
+ end
end.
- check_password(User, Server, Password, Digest,
+ check_password(User, AuthzId, Server, Password, Digest,
DigestGen) ->
- LUser = jid:nodeprep(User),
- LServer = jid:nameprep(Server),
+ if AuthzId /= <<>> andalso AuthzId /= User ->
+ false;
+ true ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
- US = {LUser, LServer},
- case catch mnesia:dirty_read({passwd, US}) of
- [#passwd{password = Passwd}] when is_binary(Passwd) ->
- DigRes = if Digest /= <<"">> ->
- Digest == DigestGen(Passwd);
- true -> false
- end,
- if DigRes -> true;
- true -> (Passwd == Password) and (Password /= <<"">>)
- end;
- [#passwd{password = Scram}]
- when is_record(Scram, scram) ->
- Passwd = jlib:decode_base64(Scram#scram.storedkey),
- DigRes = if Digest /= <<"">> ->
- Digest == DigestGen(Passwd);
- true -> false
- end,
- if DigRes -> true;
- true -> (Passwd == Password) and (Password /= <<"">>)
- end;
- _ -> false
++ LUser = jid:nodeprep(User),
++ LServer = jid:nameprep(Server),
+ US = {LUser, LServer},
+ case catch mnesia:dirty_read({passwd, US}) of
+ [#passwd{password = Passwd}] when is_binary(Passwd) ->
+ DigRes = if Digest /= <<"">> ->
+ Digest == DigestGen(Passwd);
+ true -> false
+ end,
+ if DigRes -> true;
+ true -> (Passwd == Password) and (Password /= <<"">>)
+ end;
+ [#passwd{password = Scram}]
+ when is_record(Scram, scram) ->
+ Passwd = jlib:decode_base64(Scram#scram.storedkey),
+ DigRes = if Digest /= <<"">> ->
+ Digest == DigestGen(Passwd);
+ true -> false
+ end,
+ if DigRes -> true;
+ true -> (Passwd == Password) and (Password /= <<"">>)
+ end;
+ _ -> false
+ end
end.
%% @spec (User::string(), Server::string(), Password::string()) ->
-export([init/1, handle_info/2, handle_call/3,
handle_cast/2, terminate/2, code_change/3]).
-%% External exports
-export([start/1, stop/1, start_link/1, set_password/3,
- check_password/3, check_password/5, try_register/3,
+ check_password/4, check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
- get_vh_registered_users/2,
- get_vh_registered_users_number/1,
+ get_vh_registered_users/2,
+ get_vh_registered_users_number/1,
get_vh_registered_users_number/2, get_password/2,
get_password_s/2, is_user_exists/2, remove_user/2,
- remove_user/3, store_type/0,
- plain_password_required/0]).
+ remove_user/3, store_type/0, plain_password_required/0,
+ opt_type/1]).
-include("ejabberd.hrl").
-include("logger.hrl").
store_type() -> external.
- check_password(User, Server, Password) ->
+ check_password(User, AuthzId, Server, Password) ->
+ if AuthzId /= <<>> andalso AuthzId /= User ->
+ false;
+ true ->
- if Password == <<"">> -> false;
- true ->
- case catch check_password_ldap(User, Server, Password)
- of
- {'EXIT', _} -> false;
- Result -> Result
- end
+ if Password == <<"">> -> false;
+ true ->
+ case catch check_password_ldap(User, Server, Password)
+ of
+ {'EXIT', _} -> false;
+ Result -> Result
+ end
+ end
end.
- check_password(User, Server, Password, _Digest,
+ check_password(User, AuthzId, Server, Password, _Digest,
_DigestGen) ->
- check_password(User, Server, Password).
+ check_password(User, AuthzId, Server, Password).
set_password(User, Server, Password) ->
{ok, State} = eldap_utils:get_state(Server, ?MODULE),
-behaviour(ejabberd_auth).
- -export([start/1, set_password/3, check_password/3,
- check_password/5, try_register/3,
-%% External exports
+ -export([start/1, set_password/3, check_password/4,
+ check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
get_vh_registered_users/2,
get_vh_registered_users_number/1,
true -> scram %% allows: PLAIN SCRAM
end.
- %% @spec (User, Server, Password) -> true | false | {error, Error}
- check_password(User, Server, Password) ->
- LServer = jid:nameprep(Server),
- LUser = jid:nodeprep(User),
+ %% @spec (User, AuthzId, Server, Password) -> true | false | {error, Error}
+ check_password(User, AuthzId, Server, Password) ->
+ if AuthzId /= <<>> andalso AuthzId /= User ->
+ false;
+ true ->
- LServer = jlib:nameprep(Server),
- LUser = jlib:nodeprep(User),
- if (LUser == error) or (LServer == error) ->
- false;
- (LUser == <<>>) or (LServer == <<>>) ->
- false;
- true ->
- Username = ejabberd_odbc:escape(LUser),
- case is_scrammed() of
- true ->
- try odbc_queries:get_password_scram(LServer, Username) of
- {selected, [<<"password">>, <<"serverkey">>,
- <<"salt">>, <<"iterationcount">>],
- [[StoredKey, ServerKey, Salt, IterationCount]]} ->
- Scram =
- #scram{storedkey = StoredKey,
- serverkey = ServerKey,
- salt = Salt,
- iterationcount = jlib:binary_to_integer(
- IterationCount)},
- is_password_scram_valid(Password, Scram);
- {selected, [<<"password">>, <<"serverkey">>,
- <<"salt">>, <<"iterationcount">>], []} ->
- false; %% Account does not exist
- {error, _Error} ->
- false %% Typical error is that table doesn't exist
- catch
- _:_ ->
- false %% Typical error is database not accessible
- end;
- false ->
- try odbc_queries:get_password(LServer, Username) of
- {selected, [<<"password">>], [[Password]]} ->
- Password /= <<"">>;
- {selected, [<<"password">>], [[_Password2]]} ->
- false; %% Password is not correct
- {selected, [<<"password">>], []} ->
- false; %% Account does not exist
- {error, _Error} ->
- false %% Typical error is that table doesn't exist
- catch
- _:_ ->
- false %% Typical error is database not accessible
- end
- end
++ LServer = jid:nameprep(Server),
++ LUser = jid:nodeprep(User),
+ if (LUser == error) or (LServer == error) ->
+ false;
+ (LUser == <<>>) or (LServer == <<>>) ->
+ false;
+ true ->
+ case is_scrammed() of
+ true ->
+ try odbc_queries:get_password_scram(LServer, LUser) of
+ {selected,
+ [{StoredKey, ServerKey, Salt, IterationCount}]} ->
+ Scram =
+ #scram{storedkey = StoredKey,
+ serverkey = ServerKey,
+ salt = Salt,
+ iterationcount = IterationCount},
+ is_password_scram_valid(Password, Scram);
+ {selected, []} ->
+ false; %% Account does not exist
+ {error, _Error} ->
+ false %% Typical error is that table doesn't exist
+ catch
+ _:_ ->
+ false %% Typical error is database not accessible
+ end;
+ false ->
+ try odbc_queries:get_password(LServer, LUser) of
+ {selected, [{Password}]} ->
+ Password /= <<"">>;
+ {selected, [{_Password2}]} ->
+ false; %% Password is not correct
+ {selected, []} ->
+ false; %% Account does not exist
+ {error, _Error} ->
+ false %% Typical error is that table doesn't exist
+ catch
+ _:_ ->
+ false %% Typical error is database not accessible
+ end
+ end
+ end
end.
- %% @spec (User, Server, Password, Digest, DigestGen) -> true | false | {error, Error}
- check_password(User, Server, Password, Digest,
+ %% @spec (User, AuthzId, Server, Password, Digest, DigestGen) -> true | false | {error, Error}
+ check_password(User, AuthzId, Server, Password, Digest,
DigestGen) ->
- LServer = jid:nameprep(Server),
- LUser = jid:nodeprep(User),
+ if AuthzId /= <<>> andalso AuthzId /= User ->
+ false;
+ true ->
- LServer = jlib:nameprep(Server),
- LUser = jlib:nodeprep(User),
- if (LUser == error) or (LServer == error) ->
- false;
- (LUser == <<>>) or (LServer == <<>>) ->
- false;
- true ->
- case is_scrammed() of
- false ->
- Username = ejabberd_odbc:escape(LUser),
- try odbc_queries:get_password(LServer, Username) of
- %% Account exists, check if password is valid
- {selected, [<<"password">>], [[Passwd]]} ->
- DigRes = if Digest /= <<"">> ->
- Digest == DigestGen(Passwd);
- true -> false
- end,
- if DigRes -> true;
- true -> (Passwd == Password) and (Password /= <<"">>)
- end;
- {selected, [<<"password">>], []} ->
- false; %% Account does not exist
- {error, _Error} ->
- false %% Typical error is that table doesn't exist
- catch
- _:_ ->
- false %% Typical error is database not accessible
- end;
- true ->
- false
- end
++ LServer = jid:nameprep(Server),
++ LUser = jid:nodeprep(User),
+ if (LUser == error) or (LServer == error) ->
+ false;
+ (LUser == <<>>) or (LServer == <<>>) ->
+ false;
+ true ->
+ case is_scrammed() of
+ false ->
+ try odbc_queries:get_password(LServer, LUser) of
+ %% Account exists, check if password is valid
+ {selected, [{Passwd}]} ->
+ DigRes = if Digest /= <<"">> ->
+ Digest == DigestGen(Passwd);
+ true -> false
+ end,
+ if DigRes -> true;
+ true -> (Passwd == Password) and (Password /= <<"">>)
+ end;
+ {selected, []} ->
+ false; %% Account does not exist
+ {error, _Error} ->
+ false %% Typical error is that table doesn't exist
+ catch
+ _:_ ->
+ false %% Typical error is database not accessible
+ end;
+ true ->
+ false
+ end
+ end
end.
%% @spec (User::string(), Server::string(), Password::string()) ->
-behaviour(ejabberd_auth).
- -export([start/1, set_password/3, check_password/3,
- check_password/5, try_register/3,
-%% External exports
-%%====================================================================
-%% API
-%%====================================================================
+ -export([start/1, set_password/3, check_password/4,
+ check_password/6, try_register/3,
dirty_get_registered_users/0, get_vh_registered_users/1,
- get_vh_registered_users/2, get_vh_registered_users_number/1,
- get_vh_registered_users_number/2,
- get_password/2, get_password_s/2, is_user_exists/2,
- remove_user/2, remove_user/3, store_type/0,
- plain_password_required/0]).
+ get_vh_registered_users/2,
+ get_vh_registered_users_number/1,
+ get_vh_registered_users_number/2, get_password/2,
+ get_password_s/2, is_user_exists/2, remove_user/2,
+ remove_user/3, store_type/0, plain_password_required/0,
+ opt_type/1]).
start(_Host) ->
ejabberd:start_app(p1_pam).
set_password(_User, _Server, _Password) ->
{error, not_allowed}.
- check_password(User, Server, Password, _Digest,
+ check_password(User, AuthzId, Server, Password, _Digest,
_DigestGen) ->
- check_password(User, Server, Password).
+ check_password(User, AuthzId, Server, Password).
- check_password(User, Host, Password) ->
+ check_password(User, AuthzId, Host, Password) ->
+ if AuthzId /= <<>> andalso AuthzId /= User ->
+ false;
+ true ->
- Service = get_pam_service(Host),
- UserInfo = case get_pam_userinfotype(Host) of
- username -> User;
- jid -> <<User/binary, "@", Host/binary>>
- end,
- case catch epam:authenticate(Service, UserInfo,
- Password)
- of
- true -> true;
- _ -> false
+ Service = get_pam_service(Host),
+ UserInfo = case get_pam_userinfotype(Host) of
+ username -> User;
+ jid -> <<User/binary, "@", Host/binary>>
+ end,
+ case catch epam:authenticate(Service, UserInfo,
+ Password)
+ of
+ true -> true;
+ _ -> false
+ end
end.
try_register(_User, _Server, _Password) ->
passwd_schema() ->
{record_info(fields, passwd), #passwd{}}.
- check_password(User, Server, Password) ->
- LUser = jid:nodeprep(User),
- LServer = jid:nameprep(Server),
+ check_password(User, AuthzId, Server, Password) ->
+ if AuthzId /= <<>> andalso AuthzId /= User ->
+ false;
+ true ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
- case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
- {ok, #passwd{password = Password}} when is_binary(Password) ->
- Password /= <<"">>;
- {ok, #passwd{password = Scram}} when is_record(Scram, scram) ->
- is_password_scram_valid(Password, Scram);
- _ ->
- false
++ LUser = jid:nodeprep(User),
++ LServer = jid:nameprep(Server),
+ case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
+ {ok, #passwd{password = Password}} when is_binary(Password) ->
+ Password /= <<"">>;
+ {ok, #passwd{password = Scram}} when is_record(Scram, scram) ->
+ is_password_scram_valid(Password, Scram);
+ _ ->
+ false
+ end
end.
- check_password(User, Server, Password, Digest,
+ check_password(User, AuthzId, Server, Password, Digest,
DigestGen) ->
- LUser = jid:nodeprep(User),
- LServer = jid:nameprep(Server),
+ if AuthzId /= <<>> andalso AuthzId /= User ->
+ false;
+ true ->
- LUser = jlib:nodeprep(User),
- LServer = jlib:nameprep(Server),
- case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
- {ok, #passwd{password = Passwd}} when is_binary(Passwd) ->
- DigRes = if Digest /= <<"">> ->
- Digest == DigestGen(Passwd);
- true -> false
- end,
- if DigRes -> true;
- true -> (Passwd == Password) and (Password /= <<"">>)
- end;
- {ok, #passwd{password = Scram}}
- when is_record(Scram, scram) ->
- Passwd = jlib:decode_base64(Scram#scram.storedkey),
- DigRes = if Digest /= <<"">> ->
- Digest == DigestGen(Passwd);
- true -> false
- end,
- if DigRes -> true;
- true -> (Passwd == Password) and (Password /= <<"">>)
- end;
- _ -> false
++ LUser = jid:nodeprep(User),
++ LServer = jid:nameprep(Server),
+ case ejabberd_riak:get(passwd, passwd_schema(), {LUser, LServer}) of
+ {ok, #passwd{password = Passwd}} when is_binary(Passwd) ->
+ DigRes = if Digest /= <<"">> ->
+ Digest == DigestGen(Passwd);
+ true -> false
+ end,
+ if DigRes -> true;
+ true -> (Passwd == Password) and (Password /= <<"">>)
+ end;
+ {ok, #passwd{password = Scram}}
+ when is_record(Scram, scram) ->
+ Passwd = jlib:decode_base64(Scram#scram.storedkey),
+ DigRes = if Digest /= <<"">> ->
+ Digest == DigestGen(Passwd);
+ true -> false
+ end,
+ if DigRes -> true;
+ true -> (Passwd == Password) and (Password /= <<"">>)
+ end;
+ _ -> false
+ end
end.
set_password(User, Server, Password) ->
TLS = StateData#state.tls,
TLSEnabled = StateData#state.tls_enabled,
TLSRequired = StateData#state.tls_required,
- SASLState =
- cyrsasl:server_new(
- <<"jabber">>, Server, <<"">>, [],
- fun(U) ->
- ejabberd_auth:get_password_with_authmodule(
- U, Server)
- end,
+ SASLState = cyrsasl:server_new(
+ <<"jabber">>, Server, <<"">>, [],
+ fun (U) ->
+ ejabberd_auth:get_password_with_authmodule(
+ U, Server)
+ end,
- fun (U, P) ->
+ fun(U, AuthzId, P) ->
- ejabberd_auth:check_password_with_authmodule(
+ ejabberd_auth:check_password_with_authmodule(
- U, Server, P)
+ U, AuthzId, Server, P)
- end,
+ end,
- fun (U, P, D, DG) ->
+ fun(U, AuthzId, P, D, DG) ->
- ejabberd_auth:check_password_with_authmodule(
+ ejabberd_auth:check_password_with_authmodule(
- U, Server, P, D, DG)
+ U, AuthzId, Server, P, D, DG)
- end),
+ end),
Mechs =
case TLSEnabled or not TLSRequired of
- true ->
- Ms = lists:map(fun (S) ->
- #xmlel{name = <<"mechanism">>,
- attrs = [],
- children = [{xmlcdata, S}]}
- end,
- cyrsasl:listmech(Server)),
- [#xmlel{name = <<"mechanisms">>,
- attrs = [{<<"xmlns">>, ?NS_SASL}],
- children = Ms}];
- false ->
- []
- end,
+ true ->
+ Ms = lists:map(fun (S) ->
+ #xmlel{name = <<"mechanism">>,
+ attrs = [],
+ children = [{xmlcdata, S}]}
+ end,
+ cyrsasl:listmech(Server)),
+ [#xmlel{name = <<"mechanisms">>,
+ attrs = [{<<"xmlns">>, ?NS_SASL}],
+ children = Ms}];
+ false ->
+ []
+ end,
SockMod =
- (StateData#state.sockmod):get_sockmod(
- StateData#state.socket),
+ (StateData#state.sockmod):get_sockmod(StateData#state.socket),
Zlib = StateData#state.zlib,
- CompressFeature =
- case Zlib andalso
- ((SockMod == gen_tcp) orelse
- (SockMod == p1_tls)) of
- true ->
- [#xmlel{name = <<"compression">>,
- attrs = [{<<"xmlns">>, ?NS_FEATURE_COMPRESS}],
- children = [#xmlel{name = <<"method">>,
- attrs = [],
- children = [{xmlcdata, <<"zlib">>}]}]}];
- _ ->
- []
- end,
+ CompressFeature = case Zlib andalso
+ ((SockMod == gen_tcp) orelse (SockMod == fast_tls)) of
+ true ->
+ [#xmlel{name = <<"compression">>,
+ attrs = [{<<"xmlns">>, ?NS_FEATURE_COMPRESS}],
+ children = [#xmlel{name = <<"method">>,
+ attrs = [],
+ children = [{xmlcdata, <<"zlib">>}]}]}];
+ _ ->
+ []
+ end,
TLSFeature =
case (TLS == true) andalso
- (TLSEnabled == false) andalso
- (SockMod == gen_tcp) of
- true ->
- case TLSRequired of
- true ->
- [#xmlel{name = <<"starttls">>,
- attrs = [{<<"xmlns">>, ?NS_TLS}],
- children = [#xmlel{name = <<"required">>,
- attrs = [],
- children = []}]}];
- _ ->
- [#xmlel{name = <<"starttls">>,
- attrs = [{<<"xmlns">>, ?NS_TLS}],
- children = []}]
- end;
- false ->
- []
- end,
+ (TLSEnabled == false) andalso
+ (SockMod == gen_tcp) of
+ true ->
+ case TLSRequired of
+ true ->
+ [#xmlel{name = <<"starttls">>,
+ attrs = [{<<"xmlns">>, ?NS_TLS}],
+ children = [#xmlel{name = <<"required">>,
+ attrs = [],
+ children = []}]}];
+ _ ->
+ [#xmlel{name = <<"starttls">>,
+ attrs = [{<<"xmlns">>, ?NS_TLS}],
+ children = []}]
+ end;
+ false ->
+ []
+ end,
+ StreamFeatures1 = TLSFeature ++ CompressFeature ++ Mechs,
+ StreamFeatures = ejabberd_hooks:run_fold(c2s_stream_features,
+ Server, StreamFeatures1, [Server]),
send_element(StateData,
- #xmlel{name = <<"stream:features">>,
- attrs = [],
- children =
- TLSFeature ++ CompressFeature ++ Mechs
- ++
- ejabberd_hooks:run_fold(c2s_stream_features,
- Server, [], [Server])}),
+ #xmlel{name = <<"stream:features">>,
+ attrs = [],
+ children = StreamFeatures}),
fsm_next_state(wait_for_feature_request,
- StateData#state{
- server = Server,
- sasl_state = SASLState,
- lang = Lang});
+ StateData#state{server = Server,
+ sasl_state = SASLState,
+ lang = Lang});
_ ->
case StateData#state.resource of
- <<"">> ->
- RosterVersioningFeature =
- ejabberd_hooks:run_fold(roster_get_versioning_feature,
- Server, [],
- [Server]),
- StreamManagementFeature =
- case stream_mgmt_enabled(StateData) of
- true ->
- [#xmlel{name = <<"sm">>,
- attrs = [{<<"xmlns">>, ?NS_STREAM_MGMT_2}],
- children = []},
- #xmlel{name = <<"sm">>,
- attrs = [{<<"xmlns">>, ?NS_STREAM_MGMT_3}],
- children = []}];
- false ->
- []
+ <<"">> ->
+ RosterVersioningFeature =
+ ejabberd_hooks:run_fold(roster_get_versioning_feature,
+ Server, [],
+ [Server]),
+ StreamManagementFeature =
+ case stream_mgmt_enabled(StateData) of
+ true ->
+ [#xmlel{name = <<"sm">>,
+ attrs = [{<<"xmlns">>, ?NS_STREAM_MGMT_2}],
+ children = []},
+ #xmlel{name = <<"sm">>,
+ attrs = [{<<"xmlns">>, ?NS_STREAM_MGMT_3}],
+ children = []}];
+ false ->
+ []
end,
- StreamFeatures = [#xmlel{name = <<"bind">>,
- attrs = [{<<"xmlns">>, ?NS_BIND}],
- children = []},
- #xmlel{name = <<"session">>,
- attrs = [{<<"xmlns">>, ?NS_SESSION}],
- children = []}]
- ++
- RosterVersioningFeature ++
- StreamManagementFeature ++
- ejabberd_hooks:run_fold(c2s_post_auth_features,
- Server, [], [Server]) ++
- ejabberd_hooks:run_fold(c2s_stream_features,
- Server, [], [Server]),
- send_element(StateData,
- #xmlel{name = <<"stream:features">>,
- attrs = [],
- children = StreamFeatures}),
- fsm_next_state(wait_for_bind,
- StateData#state{server = Server, lang = Lang});
- _ ->
- send_element(StateData,
- #xmlel{name = <<"stream:features">>,
- attrs = [],
- children = []}),
- fsm_next_state(wait_for_session,
- StateData#state{server = Server, lang = Lang})
+ SockMod =
+ (StateData#state.sockmod):get_sockmod(
+ StateData#state.socket),
+ Zlib = StateData#state.zlib,
+ CompressFeature =
+ case Zlib andalso
+ ((SockMod == gen_tcp) orelse (SockMod == fast_tls)) of
+ true ->
+ [#xmlel{name = <<"compression">>,
+ attrs = [{<<"xmlns">>, ?NS_FEATURE_COMPRESS}],
+ children = [#xmlel{name = <<"method">>,
+ attrs = [],
+ children = [{xmlcdata, <<"zlib">>}]}]}];
+ _ ->
+ []
+ end,
+ StreamFeatures1 = [#xmlel{name = <<"bind">>,
+ attrs = [{<<"xmlns">>, ?NS_BIND}],
+ children = []},
+ #xmlel{name = <<"session">>,
+ attrs = [{<<"xmlns">>, ?NS_SESSION}],
+ children =
+ [#xmlel{name = <<"optional">>}]}]
+ ++
+ RosterVersioningFeature ++
+ StreamManagementFeature ++
+ CompressFeature ++
+ ejabberd_hooks:run_fold(c2s_post_auth_features,
+ Server, [], [Server]),
+ StreamFeatures = ejabberd_hooks:run_fold(c2s_stream_features,
+ Server, StreamFeatures1, [Server]),
+ send_element(StateData,
+ #xmlel{name = <<"stream:features">>,
+ attrs = [],
+ children = StreamFeatures}),
+ fsm_next_state(wait_for_bind,
+ StateData#state{server = Server, lang = Lang});
+ _ ->
+ send_element(StateData,
+ #xmlel{name = <<"stream:features">>,
+ attrs = [],
+ children = []}),
+ fsm_next_state(session_established,
+ StateData#state{server = Server, lang = Lang})
end
end;
- _ ->
- send_header(StateData, Server, <<"">>, DefaultLang),
- if not StateData#state.tls_enabled and
- StateData#state.tls_required ->
- send_element(StateData,
+ _ ->
+ send_header(StateData, Server, <<"">>, DefaultLang),
+ if not StateData#state.tls_enabled and
+ StateData#state.tls_required ->
+ send_element(StateData,
?POLICY_VIOLATION_ERR(Lang,
- <<"Use of STARTTLS required">>)),
- send_trailer(StateData),
- {stop, normal, StateData};
- true ->
- fsm_next_state(wait_for_auth,
+ <<"Use of STARTTLS required">>)),
+ send_trailer(StateData),
+ {stop, normal, StateData};
+ true ->
+ fsm_next_state(wait_for_auth,
StateData#state{server = Server,
- lang = Lang})
- end
+ lang = Lang})
+ end
+ end;
+ true ->
+ IP = StateData#state.ip,
+ {true, LogReason, ReasonT} = IsBlacklistedIP,
+ ?INFO_MSG("Connection attempt from blacklisted IP ~s: ~s",
+ [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;
- true ->
- IP = StateData#state.ip,
- {true, LogReason, ReasonT} = IsBlacklistedIP,
- ?INFO_MSG("Connection attempt from blacklisted IP ~s: ~s",
- [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_element(StateData, ?INVALID_NS_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};
fsm_next_state(wait_for_auth, dispatch_stream_mgmt(El, StateData));
wait_for_auth({xmlstreamelement, El}, StateData) ->
case is_auth_packet(El) of
- {auth, _ID, get, {U, _, _, _}} ->
- #xmlel{name = Name, attrs = Attrs} =
- jlib:make_result_iq_reply(El),
- case U of
- <<"">> -> UCdata = [];
- _ -> UCdata = [{xmlcdata, U}]
- end,
- Res = case
- ejabberd_auth:plain_password_required(StateData#state.server)
- of
- false ->
- #xmlel{name = Name, attrs = Attrs,
- children =
- [#xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>, ?NS_AUTH}],
- children =
- [#xmlel{name = <<"username">>,
- attrs = [],
- children = UCdata},
- #xmlel{name = <<"password">>,
- attrs = [], children = []},
- #xmlel{name = <<"digest">>,
- attrs = [], children = []},
- #xmlel{name = <<"resource">>,
- attrs = [],
- children = []}]}]};
- true ->
- #xmlel{name = Name, attrs = Attrs,
- children =
- [#xmlel{name = <<"query">>,
- attrs = [{<<"xmlns">>, ?NS_AUTH}],
- children =
- [#xmlel{name = <<"username">>,
- attrs = [],
- children = UCdata},
- #xmlel{name = <<"password">>,
- attrs = [], children = []},
- #xmlel{name = <<"resource">>,
- attrs = [],
- children = []}]}]}
- end,
- send_element(StateData, Res),
- fsm_next_state(wait_for_auth, StateData);
- {auth, _ID, set, {_U, _P, _D, <<"">>}} ->
- Err = jlib:make_error_reply(El,
- ?ERR_AUTH_NO_RESOURCE_PROVIDED((StateData#state.lang))),
- send_element(StateData, Err),
- fsm_next_state(wait_for_auth, StateData);
- {auth, _ID, set, {U, P, D, R}} ->
- JID = jlib:make_jid(U, StateData#state.server, R),
- case JID /= error andalso
- acl:match_rule(StateData#state.server,
- StateData#state.access, JID)
- == allow
- of
- true ->
- DGen = fun (PW) ->
- p1_sha:sha(<<(StateData#state.streamid)/binary, PW/binary>>)
- end,
+ {auth, _ID, get, {U, _, _, _}} ->
+ #xmlel{name = Name, attrs = Attrs} = jlib:make_result_iq_reply(El),
+ case U of
+ <<"">> -> UCdata = [];
+ _ -> UCdata = [{xmlcdata, U}]
+ end,
+ Res = case
+ ejabberd_auth:plain_password_required(StateData#state.server)
+ of
+ false ->
+ #xmlel{name = Name, attrs = Attrs,
+ children =
+ [#xmlel{name = <<"query">>,
+ attrs = [{<<"xmlns">>, ?NS_AUTH}],
+ children =
+ [#xmlel{name = <<"username">>,
+ attrs = [],
+ children = UCdata},
+ #xmlel{name = <<"password">>,
+ attrs = [], children = []},
+ #xmlel{name = <<"digest">>,
+ attrs = [], children = []},
+ #xmlel{name = <<"resource">>,
+ attrs = [],
+ children = []}]}]};
+ true ->
+ #xmlel{name = Name, attrs = Attrs,
+ children =
+ [#xmlel{name = <<"query">>,
+ attrs = [{<<"xmlns">>, ?NS_AUTH}],
+ children =
+ [#xmlel{name = <<"username">>,
+ attrs = [],
+ children = UCdata},
+ #xmlel{name = <<"password">>,
+ attrs = [], children = []},
+ #xmlel{name = <<"resource">>,
+ attrs = [],
+ children = []}]}]}
+ end,
+ send_element(StateData, Res),
+ fsm_next_state(wait_for_auth, StateData);
+ {auth, _ID, set, {_U, _P, _D, <<"">>}} ->
+ Err = jlib:make_error_reply(El,
+ ?ERR_AUTH_NO_RESOURCE_PROVIDED((StateData#state.lang))),
+ send_element(StateData, Err),
+ fsm_next_state(wait_for_auth, StateData);
+ {auth, _ID, set, {U, P, D, R}} ->
+ JID = jid:make(U, StateData#state.server, R),
+ case JID /= error andalso
+ acl:match_rule(StateData#state.server,
+ StateData#state.access, JID)
+ == allow
+ of
+ true ->
+ DGen = fun (PW) ->
+ p1_sha:sha(<<(StateData#state.streamid)/binary, PW/binary>>)
+ end,
- case ejabberd_auth:check_password_with_authmodule(U,
+ case ejabberd_auth:check_password_with_authmodule(U, U,
- StateData#state.server,
- P, D, DGen)
+ StateData#state.server,
+ P, D, DGen)
of
- {true, AuthModule} ->
- ?INFO_MSG("(~w) Accepted legacy authentication for ~s by ~p from ~s",
- [StateData#state.socket,
- jlib:jid_to_string(JID), AuthModule,
- jlib:ip_to_list(StateData#state.ip)]),
- ejabberd_hooks:run(c2s_auth_result, StateData#state.server,
- [true, U, StateData#state.server,
- StateData#state.ip]),
- Conn = get_conn_type(StateData),
- Info = [{ip, StateData#state.ip}, {conn, Conn},
+ {true, AuthModule} ->
+ ?INFO_MSG("(~w) Accepted legacy authentication for ~s by ~p from ~s",
+ [StateData#state.socket,
+ jid:to_string(JID), AuthModule,
+ ejabberd_config:may_hide_data(jlib:ip_to_list(StateData#state.ip))]),
+ ejabberd_hooks:run(c2s_auth_result, StateData#state.server,
+ [true, U, StateData#state.server,
+ StateData#state.ip]),
+ Conn = get_conn_type(StateData),
+ Info = [{ip, StateData#state.ip}, {conn, Conn},
{auth_module, AuthModule}],
- Res = jlib:make_result_iq_reply(
- El#xmlel{children = []}),
- send_element(StateData, Res),
- ejabberd_sm:open_session(StateData#state.sid, U,
- StateData#state.server, R,
- Info),
- change_shaper(StateData, JID),
- {Fs, Ts} =
- ejabberd_hooks:run_fold(roster_get_subscription_lists,
- StateData#state.server,
- {[], []},
- [U,
- StateData#state.server]),
- LJID =
- jlib:jid_tolower(jlib:jid_remove_resource(JID)),
- Fs1 = [LJID | Fs],
- Ts1 = [LJID | Ts],
- PrivList = ejabberd_hooks:run_fold(privacy_get_user_list,
- StateData#state.server,
- #userlist{},
- [U, StateData#state.server]),
- NewStateData = StateData#state{user = U,
- resource = R,
- jid = JID,
- conn = Conn,
- auth_module = AuthModule,
- pres_f = (?SETS):from_list(Fs1),
- pres_t = (?SETS):from_list(Ts1),
- privacy_list = PrivList},
- fsm_next_state(session_established, NewStateData);
- _ ->
- ?INFO_MSG("(~w) Failed legacy authentication for ~s from ~s",
- [StateData#state.socket,
- jlib:jid_to_string(JID),
- jlib:ip_to_list(StateData#state.ip)]),
- ejabberd_hooks:run(c2s_auth_result, StateData#state.server,
- [false, U, StateData#state.server,
- StateData#state.ip]),
- Err = jlib:make_error_reply(El, ?ERR_NOT_AUTHORIZED),
- send_element(StateData, Err),
- fsm_next_state(wait_for_auth, StateData)
- end;
- _ ->
- if JID == error ->
- ?INFO_MSG("(~w) Forbidden legacy authentication "
- "for username '~s' with resource '~s'",
- [StateData#state.socket, U, R]),
- Err = jlib:make_error_reply(El, ?ERR_JID_MALFORMED),
- send_element(StateData, Err),
- fsm_next_state(wait_for_auth, StateData);
- true ->
- ?INFO_MSG("(~w) Forbidden legacy authentication "
- "for ~s from ~s",
- [StateData#state.socket,
- jlib:jid_to_string(JID),
- jlib:ip_to_list(StateData#state.ip)]),
- ejabberd_hooks:run(c2s_auth_result, StateData#state.server,
- [false, U, StateData#state.server,
- StateData#state.ip]),
- Err = jlib:make_error_reply(El, ?ERR_NOT_ALLOWED),
- send_element(StateData, Err),
- fsm_next_state(wait_for_auth, StateData)
- end
- end;
- _ ->
- process_unauthenticated_stanza(StateData, El),
- fsm_next_state(wait_for_auth, StateData)
+ Res = jlib:make_result_iq_reply(
+ El#xmlel{children = []}),
+ send_element(StateData, Res),
+ ejabberd_sm:open_session(StateData#state.sid, U,
+ StateData#state.server, R,
+ Info),
+ change_shaper(StateData, JID),
+ {Fs, Ts} =
+ ejabberd_hooks:run_fold(roster_get_subscription_lists,
+ StateData#state.server,
+ {[], []},
+ [U, StateData#state.server]),
+ LJID = jid:tolower(jid:remove_resource(JID)),
+ Fs1 = [LJID | Fs],
+ Ts1 = [LJID | Ts],
+ PrivList = ejabberd_hooks:run_fold(privacy_get_user_list,
+ StateData#state.server,
+ #userlist{},
+ [U, StateData#state.server]),
+ NewStateData = StateData#state{user = U,
+ resource = R,
+ jid = JID,
+ conn = Conn,
+ auth_module = AuthModule,
+ pres_f = (?SETS):from_list(Fs1),
+ pres_t = (?SETS):from_list(Ts1),
+ privacy_list = PrivList},
+ fsm_next_state(session_established, NewStateData);
+ _ ->
+ ?INFO_MSG("(~w) Failed legacy authentication for ~s from ~s",
+ [StateData#state.socket,
+ jid:to_string(JID),
+ ejabberd_config:may_hide_data(jlib:ip_to_list(StateData#state.ip))]),
+ ejabberd_hooks:run(c2s_auth_result, StateData#state.server,
+ [false, U, StateData#state.server,
+ StateData#state.ip]),
+ Err = jlib:make_error_reply(El, ?ERR_NOT_AUTHORIZED),
+ send_element(StateData, Err),
+ fsm_next_state(wait_for_auth, StateData)
+ end;
+ _ ->
+ if JID == error ->
+ ?INFO_MSG("(~w) Forbidden legacy authentication "
+ "for username '~s' with resource '~s'",
+ [StateData#state.socket, U, R]),
+ Err = jlib:make_error_reply(El, ?ERR_JID_MALFORMED),
+ send_element(StateData, Err),
+ fsm_next_state(wait_for_auth, StateData);
+ true ->
+ ?INFO_MSG("(~w) Forbidden legacy authentication "
+ "for ~s from ~s",
+ [StateData#state.socket,
+ jid:to_string(JID),
+ ejabberd_config:may_hide_data(jlib:ip_to_list(StateData#state.ip))]),
+ ejabberd_hooks:run(c2s_auth_result, StateData#state.server,
+ [false, U, StateData#state.server,
+ StateData#state.ip]),
+ Err = jlib:make_error_reply(El, ?ERR_NOT_ALLOWED),
+ send_element(StateData, Err),
+ fsm_next_state(wait_for_auth, StateData)
+ end
+ end;
+ _ ->
+ process_unauthenticated_stanza(StateData, El),
+ fsm_next_state(wait_for_auth, StateData)
end;
wait_for_auth(timeout, StateData) ->
{stop, normal, StateData};
transform_listen_option(Opt, Opts) ->
[Opt|Opts].
- case proplists:get_value(authzid, Props, <<>>) of
- <<>> -> proplists:get_value(username, Props, <<>>);
- AuthzId -> AuthzId
- end.
+ identity(Props) ->
++ case proplists:get_value(authzid, Props, <<>>) of
++ <<>> -> proplists:get_value(username, Props, <<>>);
++ AuthzId -> AuthzId
++ end.
++
+opt_type(domain_certfile) -> fun iolist_to_binary/1;
+opt_type(max_fsm_queue) ->
+ fun (I) when is_integer(I), I > 0 -> I end;
+opt_type(resource_conflict) ->
+ fun (setresource) -> setresource;
+ (closeold) -> closeold;
+ (closenew) -> closenew;
+ (acceptnew) -> acceptnew
+ end;
+opt_type(_) ->
+ [domain_certfile, max_fsm_queue, resource_conflict].
L when is_list(L) -> ok
end.
--spec check_auth(noauth) -> noauth_provided;
- ({binary(), binary(), binary()}) -> {ok, binary(), binary()}.
+-spec check_auth(ejabberd_commands(), noauth) -> noauth_provided;
+ (ejabberd_commands(),
+ {binary(), binary(), binary(), boolean()}) ->
+ {ok, binary(), binary()}.
-check_auth(noauth) ->
+check_auth(_Command, noauth) ->
no_auth_provided;
-check_auth({User, Server, Password}) ->
+check_auth(Command, {User, Server, {oauth, Token}, _}) ->
+ Scope = erlang:atom_to_binary(Command#ejabberd_commands.name, utf8),
+ case ejabberd_oauth:check_token(User, Server, Scope, Token) of
+ true ->
+ {ok, User, Server};
+ false ->
+ throw({error, invalid_account_data})
+ end;
+check_auth(_Command, {User, Server, Password, _}) when is_binary(Password) ->
%% Check the account exists and password is valid
- case ejabberd_auth:check_password(User, Server, Password) of
+ case ejabberd_auth:check_password(User, <<"">>, Server, Password) of
- true -> {ok, User, Server};
- _ -> throw({error, invalid_account_data})
+ true -> {ok, User, Server};
+ _ -> throw({error, invalid_account_data})
end.
-check_access(all, _) ->
+check_access(Command, ?POLICY_ACCESS, _)
+ when Command#ejabberd_commands.policy == open ->
true;
-check_access(Access, Auth) ->
- case check_auth(Auth) of
+check_access(_Command, _Access, admin) ->
+ true;
+check_access(_Command, _Access, {_User, _Server, _, true}) ->
+ false;
+check_access(Command, Access, Auth)
+ when Access =/= ?POLICY_ACCESS;
+ Command#ejabberd_commands.policy == open;
+ Command#ejabberd_commands.policy == user ->
+ case check_auth(Command, Auth) of
{ok, User, Server} ->
- check_access(Access, User, Server);
+ check_access2(Access, User, Server);
_ ->
false
- end.
+ end;
+check_access(_Command, _Access, _Auth) ->
+ false.
-check_access(Access, User, Server) ->
+check_access2(?POLICY_ACCESS, _User, _Server) ->
+ true;
+check_access2(Access, User, Server) ->
%% Check this user has access permission
- case acl:match_rule(Server, Access, jlib:make_jid(User, Server, <<"">>)) of
+ case acl:match_rule(Server, Access, jid:make(User, Server, <<"">>)) of
allow -> true;
deny -> false
end.
--- /dev/null
- case ejabberd_auth:check_password(User, Server, Password) of
+%%%-------------------------------------------------------------------
+%%% File : ejabberd_oauth.erl
+%%% Author : Alexey Shchepin <alexey@process-one.net>
+%%% Purpose : OAUTH2 support
+%%% Created : 20 Mar 2015 by Alexey Shchepin <alexey@process-one.net>
+%%%
+%%%
+%%% 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., 59 Temple Place, Suite 330, Boston, MA
+%%% 02111-1307 USA
+%%%
+%%%-------------------------------------------------------------------
+
+-module(ejabberd_oauth).
+
+-behaviour(gen_server).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2,
+ handle_info/2, terminate/2, code_change/3]).
+
+-export([start/0,
+ start_link/0,
+ get_client_identity/2,
+ verify_redirection_uri/3,
+ authenticate_user/2,
+ authenticate_client/2,
+ verify_resowner_scope/3,
+ associate_access_code/3,
+ associate_access_token/3,
+ associate_refresh_token/3,
+ check_token/4,
+ check_token/2,
+ process/2,
+ opt_type/1]).
+
+-include("jlib.hrl").
+
+-include("ejabberd.hrl").
+-include("logger.hrl").
+
+-include("ejabberd_http.hrl").
+-include("ejabberd_web_admin.hrl").
+
+-record(oauth_token, {
+ token = {<<"">>, <<"">>} :: {binary(), binary()},
+ us = {<<"">>, <<"">>} :: {binary(), binary()},
+ scope = [] :: [binary()],
+ expire :: integer()
+ }).
+
+-define(EXPIRE, 3600).
+
+start() ->
+ init_db(mnesia, ?MYNAME),
+ Expire = expire(),
+ application:set_env(oauth2, backend, ejabberd_oauth),
+ application:set_env(oauth2, expiry_time, Expire),
+ application:start(oauth2),
+ ChildSpec = {?MODULE, {?MODULE, start_link, []},
+ temporary, 1000, worker, [?MODULE]},
+ supervisor:start_child(ejabberd_sup, ChildSpec),
+ ok.
+
+start_link() ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+
+
+init([]) ->
+ erlang:send_after(expire() * 1000, self(), clean),
+ {ok, ok}.
+
+handle_call(_Request, _From, State) ->
+ {reply, bad_request, State}.
+
+handle_cast(_Msg, State) -> {noreply, State}.
+
+handle_info(clean, State) ->
+ {MegaSecs, Secs, MiniSecs} = os:timestamp(),
+ TS = 1000000 * MegaSecs + Secs,
+ F = fun() ->
+ Ts = mnesia:select(
+ oauth_token,
+ [{#oauth_token{expire = '$1', _ = '_'},
+ [{'<', '$1', TS}],
+ ['$_']}]),
+ lists:foreach(fun mnesia:delete_object/1, Ts)
+ end,
+ mnesia:async_dirty(F),
+ erlang:send_after(trunc(expire() * 1000 * (1 + MiniSecs / 1000000)),
+ self(), clean),
+ {noreply, State};
+handle_info(_Info, State) -> {noreply, State}.
+
+terminate(_Reason, _State) -> ok.
+
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
+
+
+init_db(mnesia, _Host) ->
+ mnesia:create_table(oauth_token,
+ [{disc_copies, [node()]},
+ {attributes,
+ record_info(fields, oauth_token)}]),
+ mnesia:add_table_copy(oauth_token, node(), disc_copies);
+init_db(_, _) ->
+ ok.
+
+
+get_client_identity(Client, Ctx) -> {ok, {Ctx, {client, Client}}}.
+
+verify_redirection_uri(_, _, Ctx) -> {ok, Ctx}.
+
+authenticate_user({User, Server}, {password, Password} = Ctx) ->
+ case jid:make(User, Server, <<"">>) of
+ #jid{} = JID ->
+ Access =
+ ejabberd_config:get_option(
+ {oauth_access, JID#jid.lserver},
+ fun(A) when is_atom(A) -> A end,
+ none),
+ case acl:match_rule(JID#jid.lserver, Access, JID) of
+ allow ->
++ case ejabberd_auth:check_password(User, <<"">>, Server, Password) of
+ true ->
+ {ok, {Ctx, {user, User, Server}}};
+ false ->
+ {error, badpass}
+ end;
+ deny ->
+ {error, badpass}
+ end;
+ error ->
+ {error, badpass}
+ end.
+
+authenticate_client(Client, Ctx) -> {ok, {Ctx, {client, Client}}}.
+
+verify_resowner_scope({user, _User, _Server}, Scope, Ctx) ->
+ Cmds = ejabberd_commands:get_commands(),
+ Cmds1 = [sasl_auth | Cmds],
+ RegisteredScope = [atom_to_binary(C, utf8) || C <- Cmds1],
+ case oauth2_priv_set:is_subset(oauth2_priv_set:new(Scope),
+ oauth2_priv_set:new(RegisteredScope)) of
+ true ->
+ {ok, {Ctx, Scope}};
+ false ->
+ {error, badscope}
+ end;
+verify_resowner_scope(_, _, _) ->
+ {error, badscope}.
+
+
+associate_access_code(_AccessCode, _Context, AppContext) ->
+ %put(?ACCESS_CODE_TABLE, AccessCode, Context),
+ {ok, AppContext}.
+
+associate_access_token(AccessToken, Context, AppContext) ->
+ {user, User, Server} =
+ proplists:get_value(<<"resource_owner">>, Context, <<"">>),
+ Scope = proplists:get_value(<<"scope">>, Context, []),
+ Expire = proplists:get_value(<<"expiry_time">>, Context, 0),
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ R = #oauth_token{
+ token = AccessToken,
+ us = {LUser, LServer},
+ scope = Scope,
+ expire = Expire
+ },
+ mnesia:dirty_write(R),
+ {ok, AppContext}.
+
+associate_refresh_token(_RefreshToken, _Context, AppContext) ->
+ %put(?REFRESH_TOKEN_TABLE, RefreshToken, Context),
+ {ok, AppContext}.
+
+
+check_token(User, Server, Scope, Token) ->
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ case catch mnesia:dirty_read(oauth_token, Token) of
+ [#oauth_token{us = {LUser, LServer},
+ scope = TokenScope,
+ expire = Expire}] ->
+ {MegaSecs, Secs, _} = os:timestamp(),
+ TS = 1000000 * MegaSecs + Secs,
+ oauth2_priv_set:is_member(
+ Scope, oauth2_priv_set:new(TokenScope)) andalso
+ Expire > TS;
+ _ ->
+ false
+ end.
+
+check_token(Scope, Token) ->
+ case catch mnesia:dirty_read(oauth_token, Token) of
+ [#oauth_token{us = {LUser, LServer},
+ scope = TokenScope,
+ expire = Expire}] ->
+ {MegaSecs, Secs, _} = os:timestamp(),
+ TS = 1000000 * MegaSecs + Secs,
+ case oauth2_priv_set:is_member(
+ Scope, oauth2_priv_set:new(TokenScope)) andalso
+ Expire > TS of
+ true -> {ok, LUser, LServer};
+ false -> false
+ end;
+ _ ->
+ false
+ end.
+
+
+expire() ->
+ ejabberd_config:get_option(
+ oauth_expire,
+ fun(I) when is_integer(I) -> I end,
+ ?EXPIRE).
+
+-define(DIV(Class, Els),
+ ?XAE(<<"div">>, [{<<"class">>, Class}], Els)).
+-define(INPUTID(Type, Name, Value),
+ ?XA(<<"input">>,
+ [{<<"type">>, Type}, {<<"name">>, Name},
+ {<<"value">>, Value}, {<<"id">>, Name}])).
+-define(LABEL(ID, Els),
+ ?XAE(<<"label">>, [{<<"for">>, ID}], Els)).
+
+process(_Handlers,
+ #request{method = 'GET', q = Q, lang = Lang,
+ path = [_, <<"authorization_token">>]}) ->
+ ResponseType = proplists:get_value(<<"response_type">>, Q, <<"">>),
+ ClientId = proplists:get_value(<<"client_id">>, Q, <<"">>),
+ RedirectURI = proplists:get_value(<<"redirect_uri">>, Q, <<"">>),
+ Scope = proplists:get_value(<<"scope">>, Q, <<"">>),
+ State = proplists:get_value(<<"state">>, Q, <<"">>),
+ Form =
+ ?XAE(<<"form">>,
+ [{<<"action">>, <<"authorization_token">>},
+ {<<"method">>, <<"post">>}],
+ [?LABEL(<<"username">>, [?CT(<<"User">>), ?C(<<": ">>)]),
+ ?INPUTID(<<"text">>, <<"username">>, <<"">>),
+ ?BR,
+ ?LABEL(<<"server">>, [?CT(<<"Server">>), ?C(<<": ">>)]),
+ ?INPUTID(<<"text">>, <<"server">>, <<"">>),
+ ?BR,
+ ?LABEL(<<"password">>, [?CT(<<"Password">>), ?C(<<": ">>)]),
+ ?INPUTID(<<"password">>, <<"password">>, <<"">>),
+ ?INPUT(<<"hidden">>, <<"response_type">>, ResponseType),
+ ?INPUT(<<"hidden">>, <<"client_id">>, ClientId),
+ ?INPUT(<<"hidden">>, <<"redirect_uri">>, RedirectURI),
+ ?INPUT(<<"hidden">>, <<"scope">>, Scope),
+ ?INPUT(<<"hidden">>, <<"state">>, State),
+ ?BR,
+ ?INPUTT(<<"submit">>, <<"">>, <<"Accept">>)
+ ]),
+ Top =
+ ?DIV(<<"section">>,
+ [?DIV(<<"block">>,
+ [?A(<<"https://www.ejabberd.im">>,
+ [?XA(<<"img">>,
+ [{<<"height">>, <<"32">>},
+ {<<"src">>, logo()}])]
+ )])]),
+ Middle =
+ ?DIV(<<"white section">>,
+ [?DIV(<<"block">>,
+ [?XC(<<"h1">>, <<"Authorization request">>),
+ ?XE(<<"p">>,
+ [?C(<<"Application ">>),
+ ?XC(<<"em">>, ClientId),
+ ?C(<<" wants to access scope ">>),
+ ?XC(<<"em">>, Scope)]),
+ Form
+ ])]),
+ Bottom =
+ ?DIV(<<"section">>,
+ [?DIV(<<"block">>,
+ [?XAC(<<"a">>,
+ [{<<"href">>, <<"https://www.ejabberd.im">>},
+ {<<"title">>, <<"ejabberd XMPP server">>}],
+ <<"ejabberd">>),
+ ?C(" is maintained by "),
+ ?XAC(<<"a">>,
+ [{<<"href">>, <<"https://www.process-one.net">>},
+ {<<"title">>, <<"ProcessOne - Leader in Instant Messaging and Push Solutions">>}],
+ <<"ProcessOne">>)
+ ])]),
+ Body = ?DIV(<<"container">>, [Top, Middle, Bottom]),
+ ejabberd_web:make_xhtml(web_head(), [Body]);
+process(_Handlers,
+ #request{method = 'POST', q = Q, lang = _Lang,
+ path = [_, <<"authorization_token">>]}) ->
+ _ResponseType = proplists:get_value(<<"response_type">>, Q, <<"">>),
+ ClientId = proplists:get_value(<<"client_id">>, Q, <<"">>),
+ RedirectURI = proplists:get_value(<<"redirect_uri">>, Q, <<"">>),
+ SScope = proplists:get_value(<<"scope">>, Q, <<"">>),
+ Username = proplists:get_value(<<"username">>, Q, <<"">>),
+ Server = proplists:get_value(<<"server">>, Q, <<"">>),
+ Password = proplists:get_value(<<"password">>, Q, <<"">>),
+ State = proplists:get_value(<<"state">>, Q, <<"">>),
+ Scope = str:tokens(SScope, <<" ">>),
+ case oauth2:authorize_password({Username, Server},
+ ClientId,
+ RedirectURI,
+ Scope,
+ {password, Password}) of
+ {ok, {_AppContext, Authorization}} ->
+ {ok, {_AppContext2, Response}} =
+ oauth2:issue_token(Authorization, none),
+ {ok, AccessToken} = oauth2_response:access_token(Response),
+ {ok, Type} = oauth2_response:token_type(Response),
+ {ok, Expires} = oauth2_response:expires_in(Response),
+ {ok, VerifiedScope} = oauth2_response:scope(Response),
+ %oauth2_wrq:redirected_access_token_response(ReqData,
+ % RedirectURI,
+ % AccessToken,
+ % Type,
+ % Expires,
+ % VerifiedScope,
+ % State,
+ % Context);
+ {302, [{<<"Location">>,
+ <<RedirectURI/binary,
+ "?access_token=", AccessToken/binary,
+ "&token_type=", Type/binary,
+ "&expires_in=", (integer_to_binary(Expires))/binary,
+ "&scope=", (str:join(VerifiedScope, <<" ">>))/binary,
+ "&state=", State/binary>>
+ }],
+ ejabberd_web:make_xhtml([?XC(<<"h1">>, <<"302 Found">>)])};
+ {error, Error} when is_atom(Error) ->
+ %oauth2_wrq:redirected_error_response(
+ % ReqData, RedirectURI, Error, State, Context)
+ {302, [{<<"Location">>,
+ <<RedirectURI/binary,
+ "?error=", (atom_to_binary(Error, utf8))/binary,
+ "&state=", State/binary>>
+ }],
+ ejabberd_web:make_xhtml([?XC(<<"h1">>, <<"302 Found">>)])}
+ end;
+process(_Handlers, _Request) ->
+ ejabberd_web:error(not_found).
+
+
+
+
+web_head() ->
+ [?XA(<<"meta">>, [{<<"http-equiv">>, <<"X-UA-Compatible">>},
+ {<<"content">>, <<"IE=edge">>}]),
+ ?XA(<<"meta">>, [{<<"name">>, <<"viewport">>},
+ {<<"content">>,
+ <<"width=device-width, initial-scale=1">>}]),
+ ?XC(<<"title">>, <<"Authorization request">>),
+ ?XC(<<"style">>, css())
+ ].
+
+css() ->
+ <<"
+ body {
+ margin: 0;
+ padding: 0;
+
+ font-family: sans-serif;
+ color: #fff;
+ }
+
+ h1 {
+ font-size: 3em;
+ color: #444;
+ }
+
+ p {
+ line-height: 1.5em;
+ color: #888;
+ }
+
+ a {
+ color: #fff;
+ }
+ a:hover,
+ a:active {
+ text-decoration: underline;
+ }
+
+ em {
+ display: inline-block;
+ padding: 0 5px;
+
+ background: #f4f4f4;
+ border-radius: 5px;
+
+ font-style: normal;
+ font-weight: bold;
+ color: #444;
+ }
+
+ form {
+ color: #444;
+ }
+ label {
+ display: block;
+ font-weight: bold;
+ }
+
+ input[type=text],
+ input[type=password] {
+ margin-bottom: 1em;
+ padding: 0.4em;
+
+ max-width: 330px;
+ width: 100%;
+
+ border: 1px solid #c4c4c4;
+ border-radius: 5px;
+ outline: 0;
+
+ font-size: 1.2em;
+ }
+ input[type=text]:focus,
+ input[type=password]:focus,
+ input[type=text]:active,
+ input[type=password]:active {
+ border-color: #41AFCA;
+ }
+
+ input[type=submit] {
+ font-size: 1em;
+ }
+
+ .container {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+
+ background: #424A55;
+ background-image: -webkit-linear-gradient(270deg, rgba(48,52,62,0) 24%, #30353e 100%);
+ background-image: linear-gradient(-180deg, rgba(48,52,62,0) 24%, #30353e 100%);
+ }
+
+ .section {
+ padding: 3em;
+ }
+ .white.section {
+ background: #fff;
+ border-bottom: 4px solid #41AFCA;
+ }
+
+ .white.section a {
+ text-decoration: none;
+ color: #41AFCA;
+ }
+ .white.section a:hover,
+ .white.section a:active {
+ text-decoration: underline;
+ }
+
+ .container > .section {
+ background: #424A55;
+ }
+
+ .block {
+ margin: 0 auto;
+ max-width: 900px;
+ width: 100%;
+ }
+">>.
+
+logo() ->
+ <<"data:image/png;base64,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">>.
+
+opt_type(oauth_expire) ->
+ fun(I) when is_integer(I), I >= 0 -> I end;
+opt_type(oauth_access) ->
+ fun(A) when is_atom(A) -> A end;
+opt_type(_) -> [oauth_expire, oauth_access].
get_auth_account(HostOfRule, AccessRule, User, Server,
Pass) ->
- case ejabberd_auth:check_password(User, Server, Pass) of
+ case ejabberd_auth:check_password(User, <<"">>, Server, Pass) of
true ->
case is_acl_match(HostOfRule, AccessRule,
- jlib:make_jid(User, Server, <<"">>))
+ jid:make(User, Server, <<"">>))
of
false -> {unauthorized, <<"unprivileged-account">>};
true -> {ok, {User, Server}}
--- /dev/null
- case ejabberd_auth:check_password(User, Server, Pass) of
+%%%----------------------------------------------------------------------
+%%% File : mod_http_api.erl
+%%% Author : Christophe romain <christophe.romain@process-one.net>
+%%% Purpose : Implements REST API for ejabberd using JSON data
+%%% Created : 15 Sep 2014 by Christophe Romain <christophe.romain@process-one.net>
+%%%
+%%%
+%%% 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.
+%%%
+%%%----------------------------------------------------------------------
+
+%% Example config:
+%%
+%% in ejabberd_http listener
+%% request_handlers:
+%% "/api": mod_http_api
+%%
+%% Access rights are defined with:
+%% commands_admin_access: configure
+%% commands:
+%% - add_commands: user
+%%
+%%
+%% add_commands allow exporting a class of commands, from
+%% open: methods is not risky and can be called by without any access check
+%% restricted (default): the same, but will appear only in ejabberdctl list.
+%% admin – auth is required with XMLRPC and HTTP API and checked for admin priviledges, works as usual in ejabberdctl.
+%% user - can be used through XMLRPC and HTTP API, even by user. Only admin can use the commands for other users.
+%%
+%% Then to perform an action, send a POST request to the following URL:
+%% http://localhost:5280/api/<call_name>
+
+-module(mod_http_api).
+
+-author('cromain@process-one.net').
+
+-behaviour(gen_mod).
+
+-export([start/2, stop/1, process/2, mod_opt_type/1]).
+
+-include("ejabberd.hrl").
+-include("jlib.hrl").
+-include("logger.hrl").
+-include("ejabberd_http.hrl").
+
+-define(CT_PLAIN,
+ {<<"Content-Type">>, <<"text/plain">>}).
+
+-define(CT_XML,
+ {<<"Content-Type">>, <<"text/xml; charset=utf-8">>}).
+
+-define(CT_JSON,
+ {<<"Content-Type">>, <<"application/json">>}).
+
+-define(AC_ALLOW_ORIGIN,
+ {<<"Access-Control-Allow-Origin">>, <<"*">>}).
+
+-define(AC_ALLOW_METHODS,
+ {<<"Access-Control-Allow-Methods">>,
+ <<"GET, POST, OPTIONS">>}).
+
+-define(AC_ALLOW_HEADERS,
+ {<<"Access-Control-Allow-Headers">>,
+ <<"Content-Type">>}).
+
+-define(AC_MAX_AGE,
+ {<<"Access-Control-Max-Age">>, <<"86400">>}).
+
+-define(OPTIONS_HEADER,
+ [?CT_PLAIN, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_METHODS,
+ ?AC_ALLOW_HEADERS, ?AC_MAX_AGE]).
+
+-define(HEADER(CType),
+ [CType, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_HEADERS]).
+
+%% -------------------
+%% Module control
+%% -------------------
+
+start(_Host, _Opts) ->
+ ok.
+
+stop(_Host) ->
+ ok.
+
+
+%% ----------
+%% basic auth
+%% ----------
+
+check_permissions(#request{auth = HTTPAuth, headers = Headers}, Command)
+ when HTTPAuth /= undefined ->
+ case catch binary_to_existing_atom(Command, utf8) of
+ Call when is_atom(Call) ->
+ Admin =
+ case lists:keysearch(<<"X-Admin">>, 1, Headers) of
+ {value, {_, <<"true">>}} -> true;
+ _ -> false
+ end,
+ Auth =
+ case HTTPAuth of
+ {SJID, Pass} ->
+ case jid:from_string(SJID) of
+ #jid{user = User, server = Server} ->
++ case ejabberd_auth:check_password(User, <<"">>, Server, Pass) of
+ true -> {ok, {User, Server, Pass, Admin}};
+ false -> false
+ end;
+ _ ->
+ false
+ end;
+ {oauth, Token, _} ->
+ case ejabberd_oauth:check_token(Command, Token) of
+ {ok, User, Server} ->
+ {ok, {User, Server, {oauth, Token}, Admin}};
+ false ->
+ false
+ end;
+ _ ->
+ false
+ end,
+ case Auth of
+ {ok, A} -> {allowed, Call, A};
+ _ -> unauthorized_response()
+ end;
+ _ ->
+ unauthorized_response()
+ end;
+check_permissions(_, _Command) ->
+ unauthorized_response().
+
+%% ------------------
+%% command processing
+%% ------------------
+
+process(_, #request{method = 'POST', data = <<>>}) ->
+ ?DEBUG("Bad Request: no data", []),
+ badrequest_response();
+process([Call], #request{method = 'POST', data = Data, ip = IP} = Req) ->
+ try
+ Args = case jiffy:decode(Data) of
+ List when is_list(List) -> List;
+ {List} when is_list(List) -> List;
+ Other -> [Other]
+ end,
+ log(Call, Args, IP),
+ case check_permissions(Req, Call) of
+ {allowed, Cmd, Auth} ->
+ {Code, Result} = handle(Cmd, Auth, Args),
+ json_response(Code, jiffy:encode(Result));
+ ErrorResponse ->
+ ErrorResponse
+ end
+ catch _:Error ->
+ ?DEBUG("Bad Request: ~p", [Error]),
+ badrequest_response()
+ end;
+process([Call], #request{method = 'GET', q = Data, ip = IP} = Req) ->
+ try
+ Args = case Data of
+ [{nokey, <<>>}] -> [];
+ _ -> Data
+ end,
+ log(Call, Args, IP),
+ case check_permissions(Req, Call) of
+ {allowed, Cmd, Auth} ->
+ {Code, Result} = handle(Cmd, Auth, Args),
+ json_response(Code, jiffy:encode(Result));
+ ErrorResponse ->
+ ErrorResponse
+ end
+ catch _:Error ->
+ ?DEBUG("Bad Request: ~p", [Error]),
+ badrequest_response()
+ end;
+process([], #request{method = 'OPTIONS', data = <<>>}) ->
+ {200, ?OPTIONS_HEADER, []};
+process(_Path, Request) ->
+ ?DEBUG("Bad Request: no handler ~p", [Request]),
+ badrequest_response().
+
+%% ----------------
+%% command handlers
+%% ----------------
+
+% generic ejabberd command handler
+handle(Call, Auth, Args) when is_atom(Call), is_list(Args) ->
+ case ejabberd_commands:get_command_format(Call, Auth) of
+ {ArgsSpec, _} when is_list(ArgsSpec) ->
+ Args2 = [{jlib:binary_to_atom(Key), Value} || {Key, Value} <- Args],
+ Spec = lists:foldr(
+ fun ({Key, binary}, Acc) ->
+ [{Key, <<>>}|Acc];
+ ({Key, string}, Acc) ->
+ [{Key, <<>>}|Acc];
+ ({Key, integer}, Acc) ->
+ [{Key, 0}|Acc];
+ ({Key, {list, _}}, Acc) ->
+ [{Key, []}|Acc];
+ ({Key, atom}, Acc) ->
+ [{Key, undefined}|Acc]
+ end, [], ArgsSpec),
+ handle2(Call, Auth, match(Args2, Spec));
+ {error, Msg} ->
+ {400, Msg};
+ _Error ->
+ {400, <<"Error">>}
+ end.
+
+handle2(Call, Auth, Args) when is_atom(Call), is_list(Args) ->
+ {ArgsF, _ResultF} = ejabberd_commands:get_command_format(Call, Auth),
+ ArgsFormatted = format_args(Args, ArgsF),
+ case ejabberd_command(Auth, Call, ArgsFormatted, 400) of
+ 0 -> {200, <<"OK">>};
+ 1 -> {500, <<"500 Internal server error">>};
+ 400 -> {400, <<"400 Bad Request">>};
+ 404 -> {404, <<"404 Not found">>};
+ Res -> format_command_result(Call, Auth, Res)
+ end.
+
+get_elem_delete(A, L) ->
+ case proplists:get_all_values(A, L) of
+ [Value] -> {Value, proplists:delete(A, L)};
+ [_, _ | _] ->
+ %% Crash reporting the error
+ exit({duplicated_attribute, A, L});
+ [] ->
+ %% Report the error and then force a crash
+ exit({attribute_not_found, A, L})
+ end.
+
+format_args(Args, ArgsFormat) ->
+ {ArgsRemaining, R} = lists:foldl(fun ({ArgName,
+ ArgFormat},
+ {Args1, Res}) ->
+ {ArgValue, Args2} =
+ get_elem_delete(ArgName,
+ Args1),
+ Formatted = format_arg(ArgValue,
+ ArgFormat),
+ {Args2, Res ++ [Formatted]}
+ end,
+ {Args, []}, ArgsFormat),
+ case ArgsRemaining of
+ [] -> R;
+ L when is_list(L) -> exit({additional_unused_args, L})
+ end.
+
+format_arg({array, Elements},
+ {list, {ElementDefName, ElementDefFormat}})
+ when is_list(Elements) ->
+ lists:map(fun ({struct, [{ElementName, ElementValue}]}) when
+ ElementDefName == ElementName ->
+ format_arg(ElementValue, ElementDefFormat)
+ end,
+ Elements);
+format_arg({array, [{struct, Elements}]},
+ {list, {ElementDefName, ElementDefFormat}})
+ when is_list(Elements) ->
+ lists:map(fun ({ElementName, ElementValue}) ->
+ true = ElementDefName == ElementName,
+ format_arg(ElementValue, ElementDefFormat)
+ end,
+ Elements);
+format_arg({array, [{struct, Elements}]},
+ {tuple, ElementsDef})
+ when is_list(Elements) ->
+ FormattedList = format_args(Elements, ElementsDef),
+ list_to_tuple(FormattedList);
+format_arg({array, Elements}, {list, ElementsDef})
+ when is_list(Elements) and is_atom(ElementsDef) ->
+ [format_arg(Element, ElementsDef)
+ || Element <- Elements];
+format_arg(Arg, integer) when is_integer(Arg) -> Arg;
+format_arg(Arg, binary) when is_list(Arg) -> process_unicode_codepoints(Arg);
+format_arg(Arg, binary) when is_binary(Arg) -> Arg;
+format_arg(Arg, string) when is_list(Arg) -> process_unicode_codepoints(Arg);
+format_arg(Arg, string) when is_binary(Arg) -> Arg;
+format_arg(undefined, binary) -> <<>>;
+format_arg(undefined, string) -> <<>>;
+format_arg(Arg, Format) ->
+ ?ERROR_MSG("don't know how to format Arg ~p for format ~p", [Arg, Format]),
+ error.
+
+process_unicode_codepoints(Str) ->
+ iolist_to_binary(lists:map(fun(X) when X > 255 -> unicode:characters_to_binary([X]);
+ (Y) -> Y
+ end, Str)).
+
+%% ----------------
+%% internal helpers
+%% ----------------
+
+match(Args, Spec) ->
+ [{Key, proplists:get_value(Key, Args, Default)} || {Key, Default} <- Spec].
+
+ejabberd_command(Auth, Cmd, Args, Default) ->
+ case catch ejabberd_commands:execute_command(undefined, Auth, Cmd, Args) of
+ {'EXIT', _} -> Default;
+ {error, _} -> Default;
+ Result -> Result
+ end.
+
+format_command_result(Cmd, Auth, Result) ->
+ {_, ResultFormat} = ejabberd_commands:get_command_format(Cmd, Auth),
+ case {ResultFormat, Result} of
+ {{_, rescode}, V} when V == true; V == ok ->
+ {200, <<"">>};
+ {{_, rescode}, _} ->
+ {500, <<"">>};
+ {{_, restuple}, {V1, Text1}} when V1 == true; V1 == ok ->
+ {200, iolist_to_binary(Text1)};
+ {{_, restuple}, {_, Text2}} ->
+ {500, iolist_to_binary(Text2)};
+ {{_, {list, _}}, _V} ->
+ {_, L} = format_result(Result, ResultFormat),
+ {200, L};
+ {{_, {tuple, _}}, _V} ->
+ {_, T} = format_result(Result, ResultFormat),
+ {200, T};
+ _ ->
+ {200, {[format_result(Result, ResultFormat)]}}
+ end.
+
+format_result(Atom, {Name, atom}) ->
+ {jlib:atom_to_binary(Name), jlib:atom_to_binary(Atom)};
+
+format_result(Int, {Name, integer}) ->
+ {jlib:atom_to_binary(Name), Int};
+
+format_result(String, {Name, string}) ->
+ {jlib:atom_to_binary(Name), iolist_to_binary(String)};
+
+format_result(Code, {Name, rescode}) ->
+ {jlib:atom_to_binary(Name), Code == true orelse Code == ok};
+
+format_result({Code, Text}, {Name, restuple}) ->
+ {jlib:atom_to_binary(Name),
+ {[{<<"res">>, Code == true orelse Code == ok},
+ {<<"text">>, iolist_to_binary(Text)}]}};
+
+format_result(Els, {Name, {list, {_, {tuple, [{_, atom}, _]}} = Fmt}}) ->
+ {jlib:atom_to_binary(Name), {[format_result(El, Fmt) || El <- Els]}};
+
+format_result(Els, {Name, {list, Def}}) ->
+ {jlib:atom_to_binary(Name), [element(2, format_result(El, Def)) || El <- Els]};
+
+format_result(Tuple, {_Name, {tuple, [{_, atom}, ValFmt]}}) ->
+ {Name2, Val} = Tuple,
+ {_, Val2} = format_result(Val, ValFmt),
+ {jlib:atom_to_binary(Name2), Val2};
+
+format_result(Tuple, {Name, {tuple, Def}}) ->
+ Els = lists:zip(tuple_to_list(Tuple), Def),
+ {jlib:atom_to_binary(Name), {[format_result(El, ElDef) || {El, ElDef} <- Els]}};
+
+format_result(404, {_Name, _}) ->
+ "not_found".
+
+unauthorized_response() ->
+ {401, ?HEADER(?CT_XML),
+ #xmlel{name = <<"h1">>, attrs = [],
+ children = [{xmlcdata, <<"401 Unauthorized">>}]}}.
+
+badrequest_response() ->
+ {400, ?HEADER(?CT_XML),
+ #xmlel{name = <<"h1">>, attrs = [],
+ children = [{xmlcdata, <<"400 Bad Request">>}]}}.
+json_response(Code, Body) when is_integer(Code) ->
+ {Code, ?HEADER(?CT_JSON), Body}.
+
+log(Call, Args, {Addr, Port}) ->
+ AddrS = jlib:ip_to_list({Addr, Port}),
+ ?INFO_MSG("Admin call ~s ~p from ~s:~p", [Call, Args, AddrS, Port]).
+
+mod_opt_type(access) ->
+ fun(Access) when is_atom(Access) -> Access end;
+mod_opt_type(_) -> [access].