From cd24af6768f01ad1b9d05b91d5ee7e91348627ec Mon Sep 17 00:00:00 2001 From: Ery Lee Date: Fri, 27 Mar 2015 21:05:30 +0800 Subject: [PATCH] acl --- apps/emqttd/include/emqttd.hrl | 2 +- apps/emqttd/src/emqttd_access.erl | 155 +++++++++++++++++++++++++ apps/emqttd/src/emqttd_access_rule.erl | 142 ---------------------- 3 files changed, 156 insertions(+), 143 deletions(-) create mode 100644 apps/emqttd/src/emqttd_access.erl delete mode 100644 apps/emqttd/src/emqttd_access_rule.erl diff --git a/apps/emqttd/include/emqttd.hrl b/apps/emqttd/include/emqttd.hrl index a32732af1..a5ee399e2 100644 --- a/apps/emqttd/include/emqttd.hrl +++ b/apps/emqttd/include/emqttd.hrl @@ -71,7 +71,7 @@ %%------------------------------------------------------------------------------ -record(mqtt_user, { clientid :: binary(), - peername :: list(), + ipaddr :: inet:ip_address(), username :: binary(), password :: binary() }). diff --git a/apps/emqttd/src/emqttd_access.erl b/apps/emqttd/src/emqttd_access.erl new file mode 100644 index 000000000..c422bba86 --- /dev/null +++ b/apps/emqttd/src/emqttd_access.erl @@ -0,0 +1,155 @@ +%%%----------------------------------------------------------------------------- +%%% @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 access rule. +%%% +%%% @end +%%%----------------------------------------------------------------------------- +-module(emqttd_access). + +-author('feng@emqtt.io'). + +-include("emqttd.hrl"). + +-export([compile/1, match/3]). + +-type pubsub() :: subscribe | publish | pubsub. + +-type who() :: all | binary() | + {ipaddr, esockd_access:cidr()} | + {client, binary()} | + {user, binary()}. + +-type rule() :: {allow, all} | + {allow, who(), pubsub(), list(binary())} | + {deny, all} | + {deny, who(), pubsub(), list(binary())}. + +-define('allow|deny'(A), (A =:= allow) orelse (A =:= deny)). + +%%%----------------------------------------------------------------------------- +%% @doc +%% Compile rule. +%% +%% @end +%%%----------------------------------------------------------------------------- +compile({A, all}) when ?'allow|deny'(A) -> + {A, all}; + +compile({A, Who, PubSub, TopicFilters}) when ?'allow|deny'(A) -> + {A, compile(who, Who), PubSub, [compile(topic, bin(Topic)) || Topic <- TopicFilters]}. + +compile(who, all) -> + all; +compile(who, {ipaddr, CIDR}) -> + {Start, End} = esockd_access:range(CIDR), + {ipaddr, {CIDR, Start, End}}; +compile(who, {client, all}) -> + {client, all}; +compile(who, {client, ClientId}) -> + {client, bin(ClientId)}; +compile(who, {user, all}) -> + {user, all}; +compile(who, {user, Username}) -> + {user, bin(Username)}; + +compile(topic, Topic) -> + Words = emqttd_topic:words(Topic), + case pattern(Words) of + true -> {pattern, Words}; + false -> Words + end. + +pattern(Words) -> + lists:member(<<"$u">>, Words) + orelse lists:member(<<"$c">>, Words). + +bin(L) when is_list(L) -> + list_to_binary(L); +bin(B) when is_binary(B) -> + B. + +%%%----------------------------------------------------------------------------- +%% @doc +%% Match rule. +%% +%% @end +%%%----------------------------------------------------------------------------- +-spec match(mqtt_user(), binary(), rule()) -> {matched, allow} | {matched, deny} | nomatch. +match(_User, _Topic, {AllowDeny, all}) when ?'allow|deny'(AllowDeny) -> + {matched, AllowDeny}; +match(User, Topic, {AllowDeny, Who, _PubSub, TopicFilters}) + when ?'allow|deny'(AllowDeny) -> + case match_who(User, Who) andalso match_topics(User, Topic, TopicFilters) of + true -> AllowDeny; + false -> nomatch + end. + +match_who(_User, all) -> + true; +match_who(_User, {user, all}) -> + true; +match_who(_User, {client, all}) -> + true; +match_who(#mqtt_user{clientid = ClientId}, {client, ClientId}) -> + true; +match_who(#mqtt_user{username = Username}, {user, Username}) -> + true; +match_who(#mqtt_user{ipaddr = IP}, {_CDIR, Start, End}) -> + I = esockd_access:atoi(IP), + I >= Start andalso I =< End; +match_who(_User, _Who) -> + false. + +match_topics(_User, _Topic, []) -> + false; +match_topics(User, Topic, [{pattern, PatternFilter}|Filters]) -> + TopicFilter = feed_user(User, PatternFilter), + case match_topic(emqttd_topic:words(Topic), TopicFilter) of + true -> true; + false -> match_topics(User, Topic, Filters) + end; +match_topics(User, Topic, [TopicFilter|Filters]) -> + case match_topic(emqttd_topic:words(Topic), TopicFilter) of + true -> true; + false -> match_topics(User, Topic, Filters) + end. + +match_topic(Topic, TopicFilter) -> + emqttd_topic:match(Topic, TopicFilter). + +feed_user(User, Pattern) -> + feed_user(User, Pattern, []). +feed_user(_User, [], Acc) -> + lists:reverse(Acc); +feed_user(User = #mqtt_user{clientid = undefined}, [<<"$c">>|Words], Acc) -> + feed_user(User, Words, [<<"$c">>|Acc]); +feed_user(User = #mqtt_user{clientid = ClientId}, [<<"$c">>|Words], Acc) -> + feed_user(User, Words, [ClientId |Acc]); +feed_user(User = #mqtt_user{username = undefined}, [<<"$u">>|Words], Acc) -> + feed_user(User, Words, [<<"$u">>|Acc]); +feed_user(User = #mqtt_user{username = Username}, [<<"$u">>|Words], Acc) -> + feed_user(User, Words, [Username|Acc]); +feed_user(User, [W|Words], Acc) -> + feed_user(User, Words, [W|Acc]). + + diff --git a/apps/emqttd/src/emqttd_access_rule.erl b/apps/emqttd/src/emqttd_access_rule.erl deleted file mode 100644 index 13d54b637..000000000 --- a/apps/emqttd/src/emqttd_access_rule.erl +++ /dev/null @@ -1,142 +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 access rule -%%% -%%% @end -%%%----------------------------------------------------------------------------- --module(emqttd_access_rule). - --author('feng@emqtt.io'). - --include("emqttd.hrl"). - --export([match/3, encode/1, decode/1]). - --type who() :: all | binary() | - {ipaddr, esockd_access:cidr()} | - {clientid, binary()} | - {clientid, {regexp, binary()}} | - {username, binary()} | - {username, {regexp, binary()}}. - --type rule() :: {allow, all} | - {allow, who(), binary()} | - {deny, all} | - {deny, who(), binary()}. - --opaque enc_who() :: all | binary() | - {ipaddr, esockd_access:range()} | - {clientid, binary()} | - {clientid, {regexp, binary(), re:mp()}} | - {username, binary()} | - {username, {regexp, binary(), re:mp()}}. - --opaque enc_rule() :: {allow, all} | - {allow, enc_who(), binary()} | - {deny, all} | - {deny, enc_who(), binary()}. - --export_type([who/0, rule/0, enc_who/0, enc_rule/0]). - --spec match(mqtt_user(), binary(), enc_rule()) -> {matched, allow} | {matched, deny} | nomatch. -match(_User, _Topic, []) -> - nomatch; -match(_User, _Topic, [{AllowDeny, all}|_]) -> - AllowDeny; -match(User, Topic, [{AllowDeny, all, TopicFilter}|Rules]) -> - case emqttd_topic:match(Topic, TopicFilter) of - true -> AllowDeny; - false -> match(User, Topic, Rules) - end; -match(User = #mqtt_user{clientid = ClientId}, Topic, [{AllowDeny, ClientId, TopicFilter}|Rules]) when is_binary(ClientId) -> - case emqttd_topic:match(Topic, TopicFilter) of - true -> AllowDeny; - false -> match(User, Topic, Rules) - end; -match(User = #mqtt_user{peername = IpAddr}, Topic, [{AllowDeny, {peername, CIDR}, TopicFilter}|Rules]) -> - case {match_cidr(IpAddr, CIDR), emqttd_topic:match(Topic, TopicFilter)} of - {true, true} -> AllowDeny; - _ -> match(User, Topic, Rules) - end; -match(User = #mqtt_user{username = Username}, Topic, [{AllowDeny, {username, Username}, TopicFilter}|Rules]) -> - case emqttd_topic:match(Topic, TopicFilter) of - true -> AllowDeny; - false -> match(User, Topic, Rules) - end. - - -encode({allow, all}) -> - {allow, all}; -encode({allow, Who, Topic}) -> - {allow, encode(who, Who), Topic}; -encode({deny, all}) -> - {deny, all}; -encode({deny, Who, Topic}) -> - {deny, encode(who, Who), Topic}. - -encode(who, all) -> - all; -encode(who, ClientId) when is_binary(ClientId) -> - ClientId; -encode(who, {ip, CIDR}) -> - {Start, End} = esockd_access:range(CIDR), - {ip, {CIDR, Start, End}}; -encode(who, {clientid, ClientId}) -> - {clientid, compile(ClientId)}; -encode(who, {username, Username}) -> - {username, compile(Username)}. - -compile(Bin) when is_binary(Bin) -> - Bin; -compile({regexp, Regexp}) -> - {ok, MP} = re:compile(Regexp), - {regexp, Regexp, MP}. - -decode({allow, all}) -> - {allow, all}; -decode({allow, EncodedWho, Topic}) -> - {allow, decode(who, EncodedWho), Topic}; -decode({deny, all}) -> - {deny, all}; -decode({deny, EncodedWho, Topic}) -> - {allow, decode(who, EncodedWho), Topic}. - -decode(who, all) -> - all; -decode(who, ClientId) when is_binary(ClientId) -> - ClientId; -decode(who, {ip, {CIDR, _Start, _End}}) -> - {ip, CIDR}; -decode(who, {clientid, ClientId}) when is_binary(ClientId) -> - {clientid, uncompile(ClientId)}; -decode(who, {username, Username}) when is_binary(Username) -> - {username, uncompile(Username)}. - -uncompile(Bin) when is_binary(Bin) -> - Bin; -uncompile({regexp, Regexp, MP}) -> - {ok, MP} = re:compile(Regexp), - {regexp, Regexp}. - - -