From 7359f4ffa135003a37b79d2faa4379ff6e8191ca Mon Sep 17 00:00:00 2001 From: hippp Date: Mon, 13 Apr 2015 15:47:11 +0800 Subject: [PATCH 1/7] fix go script --- go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go b/go index cd18c5893..ac0709fd1 100755 --- a/go +++ b/go @@ -2,4 +2,4 @@ # -*- tab-width:4;indent-tabs-mode:nil -*- # ex: ts=4 sw=4 et -make && make dist && cd rel/emqtt && ./bin/emqtt console +make && make dist && cd rel/emqttd && ./bin/emqttd console From 67c5b08064b1245d9d2bf27d3358ab721cf7e4a2 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Thu, 16 Apr 2015 19:44:48 +0800 Subject: [PATCH 2/7] misc --- apps/emqttd/src/emqttd_acl.erl | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/emqttd/src/emqttd_acl.erl b/apps/emqttd/src/emqttd_acl.erl index 4e0d3540e..6cc23cf54 100644 --- a/apps/emqttd/src/emqttd_acl.erl +++ b/apps/emqttd/src/emqttd_acl.erl @@ -205,4 +205,3 @@ code_change(_OldVsn, State, _Extra) -> aclmod(Name) when is_atom(Name) -> list_to_atom(lists:concat(["emqttd_acl_", Name])). - From ec48b186c6f6ec63036ef12d91db0fe48a97506e Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Thu, 16 Apr 2015 23:08:32 +0800 Subject: [PATCH 3/7] 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). + From 2ed2426a33518762c9210512521ca6e41f68ccbb Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Thu, 16 Apr 2015 23:24:07 +0800 Subject: [PATCH 4/7] access_control --- apps/emqttd/src/emqttd_auth_anonymous.erl | 2 +- apps/emqttd/src/emqttd_auth_clientid.erl | 2 +- apps/emqttd/src/emqttd_auth_username.erl | 2 ++ apps/emqttd/src/emqttd_http.erl | 2 +- apps/emqttd/src/emqttd_protocol.erl | 10 ++++---- rel/files/app.config | 28 ++++++++++++----------- 6 files changed, 25 insertions(+), 21 deletions(-) diff --git a/apps/emqttd/src/emqttd_auth_anonymous.erl b/apps/emqttd/src/emqttd_auth_anonymous.erl index ef206a1ae..4e4285fee 100644 --- a/apps/emqttd/src/emqttd_auth_anonymous.erl +++ b/apps/emqttd/src/emqttd_auth_anonymous.erl @@ -28,7 +28,7 @@ -author('feng@emqtt.io'). --behaviour(emqttd_auth). +-behaviour(emqttd_auth_mod). -export([init/1, check/3, description/0]). diff --git a/apps/emqttd/src/emqttd_auth_clientid.erl b/apps/emqttd/src/emqttd_auth_clientid.erl index bde3ce398..2d3fab999 100644 --- a/apps/emqttd/src/emqttd_auth_clientid.erl +++ b/apps/emqttd/src/emqttd_auth_clientid.erl @@ -34,7 +34,7 @@ lookup_clientid/1, remove_clientid/1, all_clientids/0]). --behaviour(emqttd_auth). +-behaviour(emqttd_auth_mod). %% emqttd_auth callbacks -export([init/1, check/3, description/0]). diff --git a/apps/emqttd/src/emqttd_auth_username.erl b/apps/emqttd/src/emqttd_auth_username.erl index 884c66bc4..6407be5d7 100644 --- a/apps/emqttd/src/emqttd_auth_username.erl +++ b/apps/emqttd/src/emqttd_auth_username.erl @@ -30,6 +30,8 @@ -include("emqttd.hrl"). +-behaviour(emqttd_auth_mod). + -export([add_user/2, remove_user/1, lookup_user/1, all_users/0]). diff --git a/apps/emqttd/src/emqttd_http.erl b/apps/emqttd/src/emqttd_http.erl index 3983cd3e8..5dd17dc19 100644 --- a/apps/emqttd/src/emqttd_http.erl +++ b/apps/emqttd/src/emqttd_http.erl @@ -78,7 +78,7 @@ authorized(Req) -> false; "Basic " ++ BasicAuth -> {Username, Password} = user_passwd(BasicAuth), - case emqttd_auth:login(#mqtt_client{username = Username}, Password) of + case emqttd_access_control:auth(#mqtt_client{username = Username}, Password) of ok -> true; {error, Reason} -> diff --git a/apps/emqttd/src/emqttd_protocol.erl b/apps/emqttd/src/emqttd_protocol.erl index 694a6ec35..9dea71419 100644 --- a/apps/emqttd/src/emqttd_protocol.erl +++ b/apps/emqttd/src/emqttd_protocol.erl @@ -123,7 +123,7 @@ handle(Packet = ?CONNECT_PACKET(Var), State = #proto_state{peername = Peername = case validate_connect(Var, State) of ?CONNACK_ACCEPT -> Client = #mqtt_client{clientid = ClientId, username = Username, ipaddr = Addr}, - case emqttd_auth:login(Client, Password) of + case emqttd_access_control:auth(Client, Password) of ok -> ClientId1 = clientid(ClientId, State), start_keepalive(KeepAlive), @@ -146,7 +146,7 @@ handle(Packet = ?CONNECT_PACKET(Var), State = #proto_state{peername = Peername = handle(Packet = ?PUBLISH_PACKET(?QOS_0, Topic, _PacketId, _Payload), State = #proto_state{clientid = ClientId, session = Session}) -> - case emqttd_acl:check({client(State), publish, Topic}) of + case emqttd_access_control:check_acl(client(State), publish, Topic) of allow -> emqttd_session:publish(Session, ClientId, {?QOS_0, emqtt_message:from_packet(Packet)}); deny -> @@ -156,7 +156,7 @@ handle(Packet = ?PUBLISH_PACKET(?QOS_0, Topic, _PacketId, _Payload), handle(Packet = ?PUBLISH_PACKET(?QOS_1, Topic, PacketId, _Payload), State = #proto_state{clientid = ClientId, session = Session}) -> - case emqttd_acl:check({client(State), publish, Topic}) of + case emqttd_access_control:check_acl(client(State), publish, Topic) of allow -> emqttd_session:publish(Session, ClientId, {?QOS_1, emqtt_message:from_packet(Packet)}), send(?PUBACK_PACKET(?PUBACK, PacketId), State); @@ -167,7 +167,7 @@ handle(Packet = ?PUBLISH_PACKET(?QOS_1, Topic, PacketId, _Payload), handle(Packet = ?PUBLISH_PACKET(?QOS_2, Topic, PacketId, _Payload), State = #proto_state{clientid = ClientId, session = Session}) -> - case emqttd_acl:check({client(State), publish, Topic}) of + case emqttd_access_control:check_acl({client(State), publish, Topic}) of allow -> NewSession = emqttd_session:publish(Session, ClientId, {?QOS_2, emqtt_message:from_packet(Packet)}), send(?PUBACK_PACKET(?PUBREC, PacketId), State#proto_state{session = NewSession}); @@ -191,7 +191,7 @@ handle(?PUBACK_PACKET(Type, PacketId), State = #proto_state{session = Session}) {ok, NewState}; handle(?SUBSCRIBE_PACKET(PacketId, TopicTable), State = #proto_state{clientid = ClientId, session = Session}) -> - AllowDenies = [emqttd_acl:check({client(State), subscribe, Topic}) || {Topic, _Qos} <- TopicTable], + AllowDenies = [emqttd_access_control:check_acl(client(State), subscribe, Topic) || {Topic, _Qos} <- TopicTable], case lists:member(deny, AllowDenies) of true -> %%TODO: return 128 QoS when deny... diff --git a/rel/files/app.config b/rel/files/app.config index e0f811513..6e94bc81e 100644 --- a/rel/files/app.config +++ b/rel/files/app.config @@ -40,19 +40,21 @@ {logger, {lager, info}} ]}, {emqttd, [ - %% Authetication. , Anonymous Default - {auth, [ - %% authentication with username, password - %{username, []}, - %% authentication with clientid - %{clientid, [{password, no}, {file, "etc/clients.config"}]}, - %% allow all - {anonymous, []} - ]}, - %% ACL config - {acl, [ - %% User internal ACL module - {internal, [{file, "etc/acl.config"}, {nomatch, allow}]} + {access_control, [ + %% Authetication. , Anonymous Default + {auth, [ + %% authentication with username, password + %{username, []}, + %% authentication with clientid + %{clientid, [{password, no}, {file, "etc/clients.config"}]}, + %% allow all + {anonymous, []} + ]}, + %% ACL config + {acl, [ + %% User internal ACL module + {internal, [{file, "etc/acl.config"}, {nomatch, allow}]} + ]} ]}, %% Packet {packet, [ From 94dd3b042dffe812163e83061bbf3bb96b42b403 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Thu, 16 Apr 2015 23:51:37 +0800 Subject: [PATCH 5/7] fix check_acl --- apps/emqttd/src/emqttd_access_control.erl | 10 +++++----- apps/emqttd/src/emqttd_protocol.erl | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/emqttd/src/emqttd_access_control.erl b/apps/emqttd/src/emqttd_access_control.erl index 4cc9aa71f..94b8025a1 100644 --- a/apps/emqttd/src/emqttd_access_control.erl +++ b/apps/emqttd/src/emqttd_access_control.erl @@ -95,7 +95,7 @@ auth(Client, Password, [{Mod, State} | Mods]) -> check_acl(Client, PubSub, Topic) when PubSub =:= publish orelse PubSub =:= subscribe -> case lookup_mods(acl) of [] -> allow; - [{_, AclMods}] -> check_acl(Client, PubSub, Topic, AclMods) + 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]), @@ -124,7 +124,7 @@ reload_acl() -> %% @end %%------------------------------------------------------------------------------ -spec register_mod(Type :: auth | acl, Mod :: atom(), Opts :: list()) -> ok | {error, any()}. -register_mod(Type, Mod, Opts) -> +register_mod(Type, Mod, Opts) when Type =:= auth; Type =:= acl-> gen_server:call(?SERVER, {register_mod, Type, Mod, Opts}). %%------------------------------------------------------------------------------ @@ -134,7 +134,7 @@ register_mod(Type, Mod, Opts) -> %% @end %%------------------------------------------------------------------------------ -spec unregister_mod(Type :: auth | acl, Mod :: atom()) -> ok | {error, any()}. -unregister_mod(Type, Mod) -> +unregister_mod(Type, Mod) when Type =:= auth; Type =:= acl -> gen_server:call(?SERVER, {unregister_mod, Type, Mod}). %%------------------------------------------------------------------------------ @@ -169,8 +169,8 @@ stop() -> 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))), + ets:insert(?ACCESS_CONTROL_TAB, {auth_modules, init_mods(auth, proplists:get_value(auth, AcOpts))}), + ets:insert(?ACCESS_CONTROL_TAB, {acl_modules, init_mods(acl, proplists:get_value(acl, AcOpts))}), {ok, state}. init_mods(auth, AuthMods) -> diff --git a/apps/emqttd/src/emqttd_protocol.erl b/apps/emqttd/src/emqttd_protocol.erl index 9dea71419..6c6b29808 100644 --- a/apps/emqttd/src/emqttd_protocol.erl +++ b/apps/emqttd/src/emqttd_protocol.erl @@ -167,7 +167,7 @@ handle(Packet = ?PUBLISH_PACKET(?QOS_1, Topic, PacketId, _Payload), handle(Packet = ?PUBLISH_PACKET(?QOS_2, Topic, PacketId, _Payload), State = #proto_state{clientid = ClientId, session = Session}) -> - case emqttd_access_control:check_acl({client(State), publish, Topic}) of + case emqttd_access_control:check_acl(client(State), publish, Topic) of allow -> NewSession = emqttd_session:publish(Session, ClientId, {?QOS_2, emqtt_message:from_packet(Packet)}), send(?PUBACK_PACKET(?PUBREC, PacketId), State#proto_state{session = NewSession}); From 4bda2c5a43d5a7087981b624b8a4eec71ac97919 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Fri, 17 Apr 2015 00:48:57 +0800 Subject: [PATCH 6/7] fix issue #85 --- apps/emqttd/src/emqttd_session.erl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/emqttd/src/emqttd_session.erl b/apps/emqttd/src/emqttd_session.erl index 6f1a0960c..8458a0075 100644 --- a/apps/emqttd/src/emqttd_session.erl +++ b/apps/emqttd/src/emqttd_session.erl @@ -186,6 +186,7 @@ subscribe(SessState = #session_state{clientid = ClientId, submap = SubMap}, Topi end, SubMap1 = lists:foldl(fun({Name, Qos}, Acc) -> maps:put(Name, Qos, Acc) end, SubMap, Topics), {ok, GrantedQos} = emqttd_pubsub:subscribe(Topics), + lager:info("Client ~s subscribe ~p. Granted QoS: ~p", [ClientId, Topics, GrantedQos]), %%TODO: should be gen_event and notification... [emqttd_msg_store:redeliver(Name, self()) || {Name, _} <- Topics], {ok, SessState#session_state{submap = SubMap1}, GrantedQos}; @@ -209,6 +210,7 @@ unsubscribe(SessState = #session_state{clientid = ClientId, submap = SubMap}, To end, %%unsubscribe from topic tree ok = emqttd_pubsub:unsubscribe(Topics), + lager:info("Client ~s unsubscribe ~p.", [ClientId, Topics]), SubMap1 = lists:foldl(fun(Topic, Acc) -> maps:remove(Topic, Acc) end, SubMap, Topics), {ok, SessState#session_state{submap = SubMap1}}; From 7956b6d3094fb70e614a8ae8d9d6c7ce829155f4 Mon Sep 17 00:00:00 2001 From: Feng Lee Date: Fri, 17 Apr 2015 01:08:32 +0800 Subject: [PATCH 7/7] acl_mod behaviour --- apps/emqttd/src/emqttd_acl_internal.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/emqttd/src/emqttd_acl_internal.erl b/apps/emqttd/src/emqttd_acl_internal.erl index 8863248b1..5903a7f62 100644 --- a/apps/emqttd/src/emqttd_acl_internal.erl +++ b/apps/emqttd/src/emqttd_acl_internal.erl @@ -32,7 +32,7 @@ -export([all_rules/0]). --behaviour(emqttd_acl). +-behaviour(emqttd_acl_mod). %% ACL callbacks -export([init/1, check_acl/2, reload_acl/1, description/0]).