From ec48b186c6f6ec63036ef12d91db0fe48a97506e Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Thu, 16 Apr 2015 23:08:32 +0800 Subject: [PATCH] access_control to replace acl, auth --- apps/emqttd/src/emqttd_access_control.erl | 243 ++++++++++++++++++++++ apps/emqttd/src/emqttd_access_rule.erl | 2 + apps/emqttd/src/emqttd_acl.erl | 207 ------------------ apps/emqttd/src/emqttd_acl_mod.erl | 60 ++++++ apps/emqttd/src/emqttd_app.erl | 12 +- apps/emqttd/src/emqttd_auth.erl | 187 ----------------- apps/emqttd/src/emqttd_auth_mod.erl | 56 +++++ apps/emqttd/src/emqttd_opts.erl | 1 + 8 files changed, 366 insertions(+), 402 deletions(-) create mode 100644 apps/emqttd/src/emqttd_access_control.erl delete mode 100644 apps/emqttd/src/emqttd_acl.erl create mode 100644 apps/emqttd/src/emqttd_acl_mod.erl delete mode 100644 apps/emqttd/src/emqttd_auth.erl create mode 100644 apps/emqttd/src/emqttd_auth_mod.erl diff --git a/apps/emqttd/src/emqttd_access_control.erl b/apps/emqttd/src/emqttd_access_control.erl new file mode 100644 index 000000000..4cc9aa71f --- /dev/null +++ b/apps/emqttd/src/emqttd_access_control.erl @@ -0,0 +1,243 @@ +%%%----------------------------------------------------------------------------- +%%% @Copyright (C) 2012-2015, Feng Lee +%%% +%%% Permission is hereby granted, free of charge, to any person obtaining a copy +%%% of this software and associated documentation files (the "Software"), to deal +%%% in the Software without restriction, including without limitation the rights +%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%%% copies of the Software, and to permit persons to whom the Software is +%%% furnished to do so, subject to the following conditions: +%%% +%%% The above copyright notice and this permission notice shall be included in all +%%% copies or substantial portions of the Software. +%%% +%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +%%% SOFTWARE. +%%%----------------------------------------------------------------------------- +%%% @doc +%%% emqttd authentication and ACL server. +%%% +%%% @end +%%%----------------------------------------------------------------------------- +-module(emqttd_access_control). + +-author('feng@emqtt.io'). + +-include("emqttd.hrl"). + +-behaviour(gen_server). + +-define(SERVER, ?MODULE). + +%% API Function Exports +-export([start_link/1, + auth/2, % authentication + check_acl/3, % acl check + reload_acl/0, % reload acl + register_mod/3, + unregister_mod/2, + lookup_mods/1, + stop/0]). + +%% gen_server callbacks +-export([init/1, handle_call/3, handle_cast/2, handle_info/2, + terminate/2, code_change/3]). + +-define(ACCESS_CONTROL_TAB, mqtt_access_control). + +%%%============================================================================= +%%% API +%%%============================================================================= + +%%------------------------------------------------------------------------------ +%% @doc +%% Start access control server. +%% +%% @end +%%------------------------------------------------------------------------------ +-spec start_link(AcOpts :: list()) -> {ok, pid()} | ignore | {error, any()}. +start_link(AcOpts) -> + gen_server:start_link({local, ?SERVER}, ?MODULE, [AcOpts], []). + +%%------------------------------------------------------------------------------ +%% @doc +%% Authenticate client. +%% +%% @end +%%------------------------------------------------------------------------------ +-spec auth(mqtt_client(), undefined | binary()) -> ok | {error, string()}. +auth(Client, Password) when is_record(Client, mqtt_client) -> + auth(Client, Password, lookup_mods(auth)). +auth(_Client, _Password, []) -> + {error, "No auth module to check!"}; +auth(Client, Password, [{Mod, State} | Mods]) -> + case Mod:check(Client, Password, State) of + ok -> ok; + {error, Reason} -> {error, Reason}; + ignore -> auth(Client, Password, Mods) + end. + +%%------------------------------------------------------------------------------ +%% @doc +%% Check ACL. +%% +%% @end +%%------------------------------------------------------------------------------ +-spec check_acl(Client, PubSub, Topic) -> allow | deny when + Client :: mqtt_client(), + PubSub :: pubsub(), + Topic :: binary(). +check_acl(Client, PubSub, Topic) when PubSub =:= publish orelse PubSub =:= subscribe -> + case lookup_mods(acl) of + [] -> allow; + [{_, AclMods}] -> check_acl(Client, PubSub, Topic, AclMods) + end. +check_acl(#mqtt_client{clientid = ClientId}, PubSub, Topic, []) -> + lager:error("ACL: nomatch when ~s ~s ~s", [ClientId, PubSub, Topic]), + allow; +check_acl(Client, PubSub, Topic, [{M, State}|AclMods]) -> + case M:check_acl({Client, PubSub, Topic}, State) of + allow -> allow; + deny -> deny; + ignore -> check_acl(Client, PubSub, Topic, AclMods) + end. + +%%------------------------------------------------------------------------------ +%% @doc +%% Reload ACL. +%% +%% @end +%%------------------------------------------------------------------------------ +-spec reload_acl() -> list() | {error, any()}. +reload_acl() -> + [M:reload_acl(State) || {M, State} <- lookup_mods(acl)]. + +%%------------------------------------------------------------------------------ +%% @doc +%% Register auth or ACL module. +%% +%% @end +%%------------------------------------------------------------------------------ +-spec register_mod(Type :: auth | acl, Mod :: atom(), Opts :: list()) -> ok | {error, any()}. +register_mod(Type, Mod, Opts) -> + gen_server:call(?SERVER, {register_mod, Type, Mod, Opts}). + +%%------------------------------------------------------------------------------ +%% @doc +%% Unregister auth or ACL module. +%% +%% @end +%%------------------------------------------------------------------------------ +-spec unregister_mod(Type :: auth | acl, Mod :: atom()) -> ok | {error, any()}. +unregister_mod(Type, Mod) -> + gen_server:call(?SERVER, {unregister_mod, Type, Mod}). + +%%------------------------------------------------------------------------------ +%% @doc +%% Lookup authentication or ACL modules. +%% +%% @end +%%------------------------------------------------------------------------------ +-spec lookup_mods(auth | acl) -> list(). +lookup_mods(Type) -> + case ets:lookup(?ACCESS_CONTROL_TAB, tab_key(Type)) of + [] -> []; + [{_, Mods}] -> Mods + end. +tab_key(auth) -> + auth_modules; +tab_key(acl) -> + acl_modules. + +%%------------------------------------------------------------------------------ +%% @doc +%% Stop access control server. +%% +%% @end +%%------------------------------------------------------------------------------ +stop() -> + gen_server:call(?MODULE, stop). + +%%%============================================================================= +%%% gen_server callbacks +%%%============================================================================= + +init([AcOpts]) -> + ets:new(?ACCESS_CONTROL_TAB, [set, named_table, protected, {read_concurrency, true}]), + ets:insert(?ACCESS_CONTROL_TAB, init_mods(auth, proplists:get_value(auth, AcOpts))), + ets:insert(?ACCESS_CONTROL_TAB, init_mods(acl, proplists:get_value(acl, AcOpts))), + {ok, state}. + +init_mods(auth, AuthMods) -> + [init_mod(fun authmod/1, Name, Opts) || {Name, Opts} <- AuthMods]; + +init_mods(acl, AclMods) -> + [init_mod(fun aclmod/1, Name, Opts) || {Name, Opts} <- AclMods]. + +init_mod(Fun, Name, Opts) -> + Module = Fun(Name), + {ok, State} = Module:init(Opts), + {Module, State}. + +handle_call({register_mod, Type, Mod, Opts}, _From, State) -> + Mods = lookup_mods(Type), + Reply = + case lists:keyfind(Mod, 1, Mods) of + false -> + case catch Mod:init(Opts) of + {ok, ModState} -> + ets:insert(?ACCESS_CONTROL_TAB, {tab_key(Type), [{Mod, ModState}|Mods]}), + ok; + {'EXIT', Error} -> + {error, Error} + end; + _ -> + {error, existed} + end, + {reply, Reply, State}; + +handle_call({unregister_mod, Type, Mod}, _From, State) -> + Mods = lookup_mods(Type), + Reply = + case lists:keyfind(Mod, 1, Mods) of + false -> + {error, not_found}; + _ -> + ets:insert(?ACCESS_CONTROL_TAB, {tab_key(Type), lists:keydelete(Mod, 1, Mods)}), ok + end, + {reply, Reply, State}; + +handle_call(stop, _From, State) -> + {stop, normal, ok, State}; + +handle_call(Req, _From, State) -> + lager:error("Bad Request: ~p", [Req]), + {reply, {error, badreq}, State}. + +handle_cast(_Msg, State) -> + {noreply, State}. + +handle_info(_Info, State) -> + {noreply, State}. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. + +%%%============================================================================= +%%% Internal functions +%%%============================================================================= + +authmod(Name) when is_atom(Name) -> + list_to_atom(lists:concat(["emqttd_auth_", Name])). + +aclmod(Name) when is_atom(Name) -> + list_to_atom(lists:concat(["emqttd_acl_", Name])). + diff --git a/apps/emqttd/src/emqttd_access_rule.erl b/apps/emqttd/src/emqttd_access_rule.erl index 2ad23b56b..990b7e5ff 100644 --- a/apps/emqttd/src/emqttd_access_rule.erl +++ b/apps/emqttd/src/emqttd_access_rule.erl @@ -26,6 +26,8 @@ %%%----------------------------------------------------------------------------- -module(emqttd_access_rule). +-author('feng@emqtt.io'). + -include("emqttd.hrl"). -type who() :: all | binary() | diff --git a/apps/emqttd/src/emqttd_acl.erl b/apps/emqttd/src/emqttd_acl.erl deleted file mode 100644 index a8f984f88..000000000 --- a/apps/emqttd/src/emqttd_acl.erl +++ /dev/null @@ -1,207 +0,0 @@ -%%%----------------------------------------------------------------------------- -%%% @Copyright (C) 2012-2015, Feng Lee -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc -%%% emqttd ACL. -%%% -%%% @end -%%%----------------------------------------------------------------------------- --module(emqttd_acl). - --author('feng@emqtt.io'). - --include("emqttd.hrl"). - --behaviour(gen_server). - --define(SERVER, ?MODULE). - -%% API Function Exports --export([start_link/1, check/1, reload/0, - register_mod/2, unregister_mod/1, all_modules/0, - stop/0]). - -%% gen_server callbacks --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). - --define(ACL_TABLE, mqtt_acl). - -%%%============================================================================= -%%% ACL behavihour -%%%============================================================================= - --ifdef(use_specs). - --callback init(AclOpts :: list()) -> {ok, State :: any()}. - --callback check_acl({Client, PubSub, Topic}, State :: any()) -> allow | deny | ignore when - Client :: mqtt_client(), - PubSub :: pubsub(), - Topic :: binary(). - --callback reload_acl(State :: any()) -> ok | {error, any()}. - --callback description() -> string(). - --else. - --export([behaviour_info/1]). - -behaviour_info(callbacks) -> - [{init, 1}, {check_acl, 2}, {reload_acl, 1}, {description, 0}]; -behaviour_info(_Other) -> - undefined. - --endif. - -%%%============================================================================= -%%% API -%%%============================================================================= - -%% @doc Start ACL Server. --spec start_link(AclMods :: list()) -> {ok, pid()} | ignore | {error, any()}. -start_link(AclMods) -> - gen_server:start_link({local, ?SERVER}, ?MODULE, [AclMods], []). - -%% @doc Check ACL. --spec check({Client, PubSub, Topic}) -> allow | deny when - Client :: mqtt_client(), - PubSub :: pubsub(), - Topic :: binary(). -check({Client, PubSub, Topic}) when PubSub =:= publish orelse PubSub =:= subscribe -> - case all_modules() of - [] -> allow; - [{_, AclMods}] -> check({Client, PubSub, Topic}, AclMods) - end. - -check({#mqtt_client{clientid = ClientId}, PubSub, Topic}, []) -> - lager:error("ACL: nomatch when ~s ~s ~s", [ClientId, PubSub, Topic]), - allow; - -check({Client, PubSub, Topic}, [{M, State}|AclMods]) -> - case M:check_acl({Client, PubSub, Topic}, State) of - allow -> allow; - deny -> deny; - ignore -> check({Client, PubSub, Topic}, AclMods) - end. - -%% @doc Reload ACL. --spec reload() -> list() | {error, any()}. -reload() -> - case ets:lookup(?ACL_TABLE, acl_modules) of - [] -> - {error, "No ACL modules!"}; - [{_, AclMods}] -> - [M:reload_acl(State) || {M, State} <- AclMods] - end. - -%% @doc Register ACL Module. --spec register_mod(AclMod :: atom(), Opts :: list()) -> ok | {error, any()}. -register_mod(AclMod, Opts) -> - gen_server:call(?SERVER, {register_mod, AclMod, Opts}). - -%% @doc Unregister ACL Module. --spec unregister_mod(AclMod :: atom()) -> ok | {error, any()}. -unregister_mod(AclMod) -> - gen_server:call(?SERVER, {unregister_mod, AclMod}). - -%% @doc All ACL Modules. --spec all_modules() -> list(). -all_modules() -> - case ets:lookup(?ACL_TABLE, acl_modules) of - [] -> []; - [{_, AclMods}] -> AclMods - end. - -%% @doc Stop ACL server. --spec stop() -> ok. -stop() -> - gen_server:call(?SERVER, stop). - -%%%============================================================================= -%%% gen_server callbacks. -%%%============================================================================= -init([AclMods]) -> - ets:new(?ACL_TABLE, [set, protected, named_table, {read_concurrency, true}]), - AclMods1 = lists:map( - fun({M, Opts}) -> - AclMod = aclmod(M), - {ok, State} = AclMod:init(Opts), - {AclMod, State} - end, AclMods), - ets:insert(?ACL_TABLE, {acl_modules, AclMods1}), - {ok, state}. - -handle_call({register_mod, Mod, Opts}, _From, State) -> - AclMods = all_modules(), - Reply = - case lists:keyfind(Mod, 1, AclMods) of - false -> - case catch Mod:init(Opts) of - {ok, ModState} -> - ets:insert(?ACL_TABLE, {acl_modules, [{Mod, ModState}|AclMods]}), - ok; - {'EXIT', Error} -> - {error, Error} - end; - _ -> - {error, existed} - end, - {reply, Reply, State}; - -handle_call({unregister_mod, Mod}, _From, State) -> - AclMods = all_modules(), - Reply = - case lists:keyfind(Mod, 1, AclMods) of - false -> - {error, not_found}; - _ -> - ets:insert(?ACL_TABLE, {acl_modules, lists:keydelete(Mod, 1, AclMods)}), ok - end, - {reply, Reply, State}; - -handle_call(stop, _From, State) -> - {stop, normal, ok, State}; - -handle_call(Req, _From, State) -> - lager:error("Bad Request: ~p", [Req]), - {reply, {error, badreq}, State}. - -handle_cast(_Msg, State) -> - {noreply, State}. - -handle_info(_Info, State) -> - {noreply, State}. - -terminate(_Reason, _State) -> - ok. - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -%%%============================================================================= -%%% Internal functions -%%%============================================================================= - -aclmod(Name) when is_atom(Name) -> - list_to_atom(lists:concat(["emqttd_acl_", Name])). - diff --git a/apps/emqttd/src/emqttd_acl_mod.erl b/apps/emqttd/src/emqttd_acl_mod.erl new file mode 100644 index 000000000..e6ca68e4c --- /dev/null +++ b/apps/emqttd/src/emqttd_acl_mod.erl @@ -0,0 +1,60 @@ +%%%----------------------------------------------------------------------------- +%%% @Copyright (C) 2012-2015, Feng Lee +%%% +%%% Permission is hereby granted, free of charge, to any person obtaining a copy +%%% of this software and associated documentation files (the "Software"), to deal +%%% in the Software without restriction, including without limitation the rights +%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%%% copies of the Software, and to permit persons to whom the Software is +%%% furnished to do so, subject to the following conditions: +%%% +%%% The above copyright notice and this permission notice shall be included in all +%%% copies or substantial portions of the Software. +%%% +%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +%%% SOFTWARE. +%%%----------------------------------------------------------------------------- +%%% @doc +%%% emqttd ACL behaviour. +%%% +%%% @end +%%%----------------------------------------------------------------------------- +-module(emqttd_acl_mod). + +-author('feng@emqtt.io'). + +-include("emqttd.hrl"). + +%%%============================================================================= +%%% ACL behavihour +%%%============================================================================= + +-ifdef(use_specs). + +-callback init(AclOpts :: list()) -> {ok, State :: any()}. + +-callback check_acl({Client, PubSub, Topic}, State :: any()) -> allow | deny | ignore when + Client :: mqtt_client(), + PubSub :: pubsub(), + Topic :: binary(). + +-callback reload_acl(State :: any()) -> ok | {error, any()}. + +-callback description() -> string(). + +-else. + +-export([behaviour_info/1]). + +behaviour_info(callbacks) -> + [{init, 1}, {check_acl, 2}, {reload_acl, 1}, {description, 0}]; +behaviour_info(_Other) -> + undefined. + +-endif. + diff --git a/apps/emqttd/src/emqttd_app.erl b/apps/emqttd/src/emqttd_app.erl index 3815edd5f..941c565e4 100644 --- a/apps/emqttd/src/emqttd_app.erl +++ b/apps/emqttd/src/emqttd_app.erl @@ -43,8 +43,7 @@ broker, metrics, bridge, - auth, - acl, + access_control, sysmon]). -define(PRINT_MSG(Msg), io:format(Msg)). @@ -121,12 +120,9 @@ server(metrics) -> {"emqttd metrics", emqttd_metrics, MetricOpts}; server(bridge) -> {"emqttd bridge supervisor", {supervisor, emqttd_bridge_sup}}; -server(auth) -> - {ok, AuthMods} = application:get_env(auth), - {"emqttd auth", emqttd_auth, AuthMods}; -server(acl) -> - {ok, AclOpts} = application:get_env(acl), - {"emqttd acl", emqttd_acl, AclOpts}; +server(access_control) -> + {ok, AcOpts} = application:get_env(access_control), + {"emqttd access control", emqttd_access_control, AcOpts}; server(sysmon) -> {"emqttd system monitor", emqttd_sysmon}. diff --git a/apps/emqttd/src/emqttd_auth.erl b/apps/emqttd/src/emqttd_auth.erl deleted file mode 100644 index b18fa1ab9..000000000 --- a/apps/emqttd/src/emqttd_auth.erl +++ /dev/null @@ -1,187 +0,0 @@ -%%%----------------------------------------------------------------------------- -%%% @Copyright (C) 2012-2015, Feng Lee -%%% -%%% Permission is hereby granted, free of charge, to any person obtaining a copy -%%% of this software and associated documentation files (the "Software"), to deal -%%% in the Software without restriction, including without limitation the rights -%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -%%% copies of the Software, and to permit persons to whom the Software is -%%% furnished to do so, subject to the following conditions: -%%% -%%% The above copyright notice and this permission notice shall be included in all -%%% copies or substantial portions of the Software. -%%% -%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -%%% SOFTWARE. -%%%----------------------------------------------------------------------------- -%%% @doc -%%% emqttd authentication. -%%% -%%% @end -%%%----------------------------------------------------------------------------- --module(emqttd_auth). - --author('feng@emqtt.io'). - --include("emqttd.hrl"). - --export([start_link/1, check/2, - register_mod/2, unregister_mod/1, all_modules/0, - stop/0]). - --behavior(gen_server). - --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). - --define(AUTH_TAB, mqtt_auth). - -%%%============================================================================= -%%% Auth behavihour -%%%============================================================================= - --ifdef(use_specs). - --callback check(Client, Password, State) -> ok | ignore | {error, string()} when - Client :: mqtt_client(), - Password :: binary(), - State :: any(). - --callback description() -> string(). - --else. - --export([behaviour_info/1]). - -behaviour_info(callbacks) -> - [{check, 3}, {description, 0}]; -behaviour_info(_Other) -> - undefined. - --endif. - -%%------------------------------------------------------------------------------ -%% @doc -%% Start authentication server. -%% -%% @end -%%------------------------------------------------------------------------------ --spec start_link(list()) -> {ok, pid()} | ignore | {error, any()}. -start_link(AuthMods) -> - gen_server:start_link({local, ?MODULE}, ?MODULE, [AuthMods], []). - -%%------------------------------------------------------------------------------ -%% @doc -%% Authenticate client. -%% -%% @end -%%------------------------------------------------------------------------------ --spec check(mqtt_client(), undefined | binary()) -> ok | {error, string()}. -check(Client, Password) when is_record(Client, mqtt_client) -> - check(Client, Password, all_modules()). - -check(_Client, _Password, []) -> - {error, "No auth module to check!"}; -check(Client, Password, [{Mod, State} | Mods]) -> - case Mod:check(Client, Password, State) of - ok -> ok; - {error, Reason} -> {error, Reason}; - ignore -> check(Client, Password, Mods) - end. - -%%------------------------------------------------------------------------------ -%% @doc -%% Register authentication module. -%% -%% @end -%%------------------------------------------------------------------------------ --spec register_mod(Mod :: atom(), Opts :: any()) -> ok | {error, any()}. -register_mod(Mod, Opts) -> - gen_server:call(?MODULE, {register_mod, Mod, Opts}). - -%%------------------------------------------------------------------------------ -%% @doc -%% Unregister authentication module. -%% -%% @end -%%------------------------------------------------------------------------------ --spec unregister_mod(Mod :: atom()) -> ok | {error, any()}. -unregister_mod(Mod) -> - gen_server:call(?MODULE, {unregister_mod, Mod}). - -all_modules() -> - case ets:lookup(?AUTH_TAB, auth_modules) of - [] -> []; - [{_, AuthMods}] -> AuthMods - end. - -stop() -> - gen_server:call(?MODULE, stop). - -init([AuthMods]) -> - ets:new(?AUTH_TAB, [set, named_table, protected, {read_concurrency, true}]), - Modules = lists:map( - fun({Mod, Opts}) -> - AuthMod = authmod(Mod), - {ok, State} = AuthMod:init(Opts), - {AuthMod, State} - end, AuthMods), - ets:insert(?AUTH_TAB, {auth_modules, Modules}), - {ok, state}. - -handle_call({register_mod, Mod, Opts}, _From, State) -> - AuthMods = all_modules(), - Reply = - case lists:keyfind(Mod, 1, AuthMods) of - false -> - case catch Mod:init(Opts) of - {ok, ModState} -> - ets:insert(?AUTH_TAB, {auth_modules, [{Mod, ModState}|AuthMods]}), - ok; - {error, Reason} -> - {error, Reason}; - {'EXIT', Error} -> - {error, Error} - end; - _ -> - {error, existed} - end, - {reply, Reply, State}; - -handle_call({unregister_mod, Mod}, _From, State) -> - AuthMods = all_modules(), - Reply = - case lists:keyfind(Mod, 1, AuthMods) of - false -> - {error, not_found}; - _ -> - ets:insert(?AUTH_TAB, {auth_modules, lists:keydelete(Mod, 1, AuthMods)}), ok - end, - {reply, Reply, State}; - -handle_call(stop, _From, State) -> - {stop, normal, ok, State}. - -handle_cast(_Msg, State) -> - {noreply, State}. - -handle_info(_Info, State) -> - {noreply, State}. - -terminate(_Reason, _State) -> - ok. - -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -%%%============================================================================= -%%% Internal functions -%%%============================================================================= -authmod(Name) when is_atom(Name) -> - list_to_atom(lists:concat(["emqttd_auth_", Name])). - diff --git a/apps/emqttd/src/emqttd_auth_mod.erl b/apps/emqttd/src/emqttd_auth_mod.erl new file mode 100644 index 000000000..30f8f53b7 --- /dev/null +++ b/apps/emqttd/src/emqttd_auth_mod.erl @@ -0,0 +1,56 @@ +%%%----------------------------------------------------------------------------- +%%% @Copyright (C) 2012-2015, Feng Lee +%%% +%%% Permission is hereby granted, free of charge, to any person obtaining a copy +%%% of this software and associated documentation files (the "Software"), to deal +%%% in the Software without restriction, including without limitation the rights +%%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +%%% copies of the Software, and to permit persons to whom the Software is +%%% furnished to do so, subject to the following conditions: +%%% +%%% The above copyright notice and this permission notice shall be included in all +%%% copies or substantial portions of the Software. +%%% +%%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +%%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +%%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +%%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +%%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +%%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +%%% SOFTWARE. +%%%----------------------------------------------------------------------------- +%%% @doc +%%% emqttd authentication behaviour. +%%% +%%% @end +%%%----------------------------------------------------------------------------- +-module(emqttd_auth_mod). + +-author('feng@emqtt.io'). + +-include("emqttd.hrl"). + +%%%============================================================================= +%%% Auth behavihour +%%%============================================================================= + +-ifdef(use_specs). + +-callback check(Client, Password, State) -> ok | ignore | {error, string()} when + Client :: mqtt_client(), + Password :: binary(), + State :: any(). + +-callback description() -> string(). + +-else. + +-export([behaviour_info/1]). + +behaviour_info(callbacks) -> + [{check, 3}, {description, 0}]; +behaviour_info(_Other) -> + undefined. + +-endif. + diff --git a/apps/emqttd/src/emqttd_opts.erl b/apps/emqttd/src/emqttd_opts.erl index 7cd9cf33b..b67f55665 100644 --- a/apps/emqttd/src/emqttd_opts.erl +++ b/apps/emqttd/src/emqttd_opts.erl @@ -45,3 +45,4 @@ merge(Defaults, Options) -> end, Defaults, Options). +