|
|
@@ -57,7 +57,6 @@
|
|
|
will_msg,
|
|
|
keepalive,
|
|
|
mountpoint,
|
|
|
- is_super,
|
|
|
is_bridge,
|
|
|
enable_ban,
|
|
|
enable_acl,
|
|
|
@@ -68,7 +67,8 @@
|
|
|
connected_at,
|
|
|
ignore_loop,
|
|
|
topic_alias_maximum,
|
|
|
- conn_mod
|
|
|
+ conn_mod,
|
|
|
+ credentials
|
|
|
}).
|
|
|
|
|
|
-opaque(state() :: #pstate{}).
|
|
|
@@ -97,7 +97,6 @@ init(SocketOpts = #{peername := Peername, peercert := Peercert, sendfun := SendF
|
|
|
is_assigned = false,
|
|
|
conn_pid = self(),
|
|
|
username = init_username(Peercert, Options),
|
|
|
- is_super = false,
|
|
|
clean_start = false,
|
|
|
topic_aliases = #{},
|
|
|
packet_size = emqx_zone:get_env(Zone, max_packet_size),
|
|
|
@@ -111,7 +110,8 @@ init(SocketOpts = #{peername := Peername, peercert := Peercert, sendfun := SendF
|
|
|
connected = false,
|
|
|
ignore_loop = emqx_config:get_env(mqtt_ignore_loop_deliver, false),
|
|
|
topic_alias_maximum = #{to_client => 0, from_client => 0},
|
|
|
- conn_mod = maps:get(conn_mod, SocketOpts, undefined)}.
|
|
|
+ conn_mod = maps:get(conn_mod, SocketOpts, undefined),
|
|
|
+ credentials = #{}}.
|
|
|
|
|
|
init_username(Peercert, Options) ->
|
|
|
case proplists:get_value(peer_cert_as_username, Options) of
|
|
|
@@ -153,10 +153,10 @@ attrs(#pstate{zone = Zone,
|
|
|
proto_name = ProtoName,
|
|
|
keepalive = Keepalive,
|
|
|
mountpoint = Mountpoint,
|
|
|
- is_super = IsSuper,
|
|
|
is_bridge = IsBridge,
|
|
|
connected_at = ConnectedAt,
|
|
|
- conn_mod = ConnMod}) ->
|
|
|
+ conn_mod = ConnMod,
|
|
|
+ credentials = Credentials}) ->
|
|
|
[{zone, Zone},
|
|
|
{client_id, ClientId},
|
|
|
{username, Username},
|
|
|
@@ -167,10 +167,11 @@ attrs(#pstate{zone = Zone,
|
|
|
{clean_start, CleanStart},
|
|
|
{keepalive, Keepalive},
|
|
|
{mountpoint, Mountpoint},
|
|
|
- {is_super, IsSuper},
|
|
|
{is_bridge, IsBridge},
|
|
|
{connected_at, ConnectedAt},
|
|
|
- {conn_mod, ConnMod}].
|
|
|
+ {conn_mod, ConnMod},
|
|
|
+ {credentials, Credentials}
|
|
|
+ ].
|
|
|
|
|
|
attr(max_inflight, #pstate{proto_ver = ?MQTT_PROTO_V5, conn_props = ConnProps}) ->
|
|
|
get_property('Receive-Maximum', ConnProps, 65535);
|
|
|
@@ -200,6 +201,8 @@ caps(#pstate{zone = Zone}) ->
|
|
|
client_id(#pstate{client_id = ClientId}) ->
|
|
|
ClientId.
|
|
|
|
|
|
+credentials(#pstate{credentials = Credentials}) when map_size(Credentials) =/= 0 ->
|
|
|
+ Credentials;
|
|
|
credentials(#pstate{zone = Zone,
|
|
|
client_id = ClientId,
|
|
|
username = Username,
|
|
|
@@ -362,8 +365,7 @@ process(?CONNECT_PACKET(
|
|
|
|
|
|
%% TODO: Mountpoint...
|
|
|
%% Msg -> emqx_mountpoint:mount(MountPoint, Msg)
|
|
|
-
|
|
|
- PState1 = set_username(Username,
|
|
|
+ PState0 = set_username(Username,
|
|
|
PState#pstate{client_id = NewClientId,
|
|
|
proto_ver = ProtoVer,
|
|
|
proto_name = ProtoName,
|
|
|
@@ -372,20 +374,21 @@ process(?CONNECT_PACKET(
|
|
|
conn_props = ConnProps,
|
|
|
is_bridge = IsBridge,
|
|
|
connected_at = os:timestamp()}),
|
|
|
-
|
|
|
+ Credentials = credentials(PState0),
|
|
|
+ PState1 = PState0#pstate{credentials = Credentials},
|
|
|
connack(
|
|
|
case check_connect(ConnPkt, PState1) of
|
|
|
- {ok, PState2} ->
|
|
|
- case authenticate(credentials(PState2), Password) of
|
|
|
- {ok, IsSuper} ->
|
|
|
- %% Maybe assign a clientId
|
|
|
- PState3 = maybe_assign_client_id(PState2#pstate{is_super = IsSuper}),
|
|
|
+ ok ->
|
|
|
+ case emqx_access_control:authenticate(Credentials#{password => Password}) of
|
|
|
+ {ok, Credentials0} ->
|
|
|
+ PState3 = maybe_assign_client_id(PState1),
|
|
|
emqx_logger:set_metadata_client_id(PState3#pstate.client_id),
|
|
|
%% Open session
|
|
|
SessAttrs = #{will_msg => make_will_msg(ConnPkt)},
|
|
|
case try_open_session(SessAttrs, PState3) of
|
|
|
{ok, SPid, SP} ->
|
|
|
- PState4 = PState3#pstate{session = SPid, connected = true},
|
|
|
+ PState4 = PState3#pstate{session = SPid, connected = true,
|
|
|
+ credentials = maps:remove(password, Credentials0)},
|
|
|
ok = emqx_cm:register_connection(client_id(PState4)),
|
|
|
true = emqx_cm:set_conn_attrs(client_id(PState4), attrs(PState4)),
|
|
|
%% Start keepalive
|
|
|
@@ -394,11 +397,11 @@ process(?CONNECT_PACKET(
|
|
|
{?RC_SUCCESS, SP, PState4};
|
|
|
{error, Error} ->
|
|
|
?LOG(error, "Failed to open session: ~p", [Error]),
|
|
|
- {?RC_UNSPECIFIED_ERROR, PState1}
|
|
|
+ {?RC_UNSPECIFIED_ERROR, PState1#pstate{credentials = Credentials0}}
|
|
|
end;
|
|
|
{error, Reason} ->
|
|
|
- ?LOG(error, "Username '~s' login failed for ~p", [Username, Reason]),
|
|
|
- {?RC_NOT_AUTHORIZED, PState1}
|
|
|
+ ?LOG(error, "Client ~s (Username: '~s') login failed for ~p", [NewClientId, Username, Reason]),
|
|
|
+ {emqx_reason_codes:connack_error(Reason), PState1#pstate{credentials = Credentials}}
|
|
|
end;
|
|
|
{error, ReasonCode} ->
|
|
|
{ReasonCode, PState1}
|
|
|
@@ -406,8 +409,8 @@ process(?CONNECT_PACKET(
|
|
|
|
|
|
process(Packet = ?PUBLISH_PACKET(?QOS_0, Topic, _PacketId, _Payload), PState) ->
|
|
|
case check_publish(Packet, PState) of
|
|
|
- {ok, PState1} ->
|
|
|
- do_publish(Packet, PState1);
|
|
|
+ ok ->
|
|
|
+ do_publish(Packet, PState);
|
|
|
{error, ReasonCode} ->
|
|
|
?LOG(warning, "Cannot publish qos0 message to ~s for ~s",
|
|
|
[Topic, emqx_reason_codes:text(ReasonCode)]),
|
|
|
@@ -416,8 +419,8 @@ process(Packet = ?PUBLISH_PACKET(?QOS_0, Topic, _PacketId, _Payload), PState) ->
|
|
|
|
|
|
process(Packet = ?PUBLISH_PACKET(?QOS_1, Topic, PacketId, _Payload), PState) ->
|
|
|
case check_publish(Packet, PState) of
|
|
|
- {ok, PState1} ->
|
|
|
- do_publish(Packet, PState1);
|
|
|
+ ok ->
|
|
|
+ do_publish(Packet, PState);
|
|
|
{error, ReasonCode} ->
|
|
|
?LOG(warning, "Cannot publish qos1 message to ~s for ~s",
|
|
|
[Topic, emqx_reason_codes:text(ReasonCode)]),
|
|
|
@@ -430,8 +433,8 @@ process(Packet = ?PUBLISH_PACKET(?QOS_1, Topic, PacketId, _Payload), PState) ->
|
|
|
|
|
|
process(Packet = ?PUBLISH_PACKET(?QOS_2, Topic, PacketId, _Payload), PState) ->
|
|
|
case check_publish(Packet, PState) of
|
|
|
- {ok, PState1} ->
|
|
|
- do_publish(Packet, PState1);
|
|
|
+ ok ->
|
|
|
+ do_publish(Packet, PState);
|
|
|
{error, ReasonCode} ->
|
|
|
?LOG(warning, "Cannot publish qos2 message to ~s for ~s",
|
|
|
[Topic, emqx_reason_codes:text(ReasonCode)]),
|
|
|
@@ -480,16 +483,10 @@ process(Packet = ?SUBSCRIBE_PACKET(PacketId, Properties, RawTopicFilters),
|
|
|
case check_subscribe(
|
|
|
parse_topic_filters(?SUBSCRIBE, RawTopicFilters1), PState) of
|
|
|
{ok, TopicFilters} ->
|
|
|
- case emqx_hooks:run('client.subscribe', [credentials(PState)], TopicFilters) of
|
|
|
- {ok, TopicFilters1} ->
|
|
|
- ok = emqx_session:subscribe(SPid, PacketId, Properties,
|
|
|
- emqx_mountpoint:mount(Mountpoint, TopicFilters1)),
|
|
|
- {ok, PState};
|
|
|
- {stop, _} ->
|
|
|
- ReasonCodes = lists:duplicate(length(TopicFilters),
|
|
|
- ?RC_IMPLEMENTATION_SPECIFIC_ERROR),
|
|
|
- deliver({suback, PacketId, ReasonCodes}, PState)
|
|
|
- end;
|
|
|
+ TopicFilters0 = emqx_hooks:run_fold('client.subscribe', [credentials(PState)], TopicFilters),
|
|
|
+ ok = emqx_session:subscribe(SPid, PacketId, Properties,
|
|
|
+ emqx_mountpoint:mount(Mountpoint, TopicFilters0)),
|
|
|
+ {ok, PState};
|
|
|
{error, TopicFilters} ->
|
|
|
{SubTopics, ReasonCodes} =
|
|
|
lists:foldr(fun({Topic, #{rc := ?RC_SUCCESS}}, {Topics, Codes}) ->
|
|
|
@@ -509,17 +506,11 @@ process(Packet = ?SUBSCRIBE_PACKET(PacketId, Properties, RawTopicFilters),
|
|
|
|
|
|
process(?UNSUBSCRIBE_PACKET(PacketId, Properties, RawTopicFilters),
|
|
|
PState = #pstate{session = SPid, mountpoint = MountPoint}) ->
|
|
|
- case emqx_hooks:run('client.unsubscribe', [credentials(PState)],
|
|
|
- parse_topic_filters(?UNSUBSCRIBE, RawTopicFilters)) of
|
|
|
- {ok, TopicFilters} ->
|
|
|
- ok = emqx_session:unsubscribe(SPid, PacketId, Properties,
|
|
|
- emqx_mountpoint:mount(MountPoint, TopicFilters)),
|
|
|
- {ok, PState};
|
|
|
- {stop, _Acc} ->
|
|
|
- ReasonCodes = lists:duplicate(length(RawTopicFilters),
|
|
|
- ?RC_IMPLEMENTATION_SPECIFIC_ERROR),
|
|
|
- deliver({unsuback, PacketId, ReasonCodes}, PState)
|
|
|
- end;
|
|
|
+ TopicFilters = emqx_hooks:run_fold('client.unsubscribe', [credentials(PState)],
|
|
|
+ parse_topic_filters(?UNSUBSCRIBE, RawTopicFilters)),
|
|
|
+ ok = emqx_session:unsubscribe(SPid, PacketId, Properties,
|
|
|
+ emqx_mountpoint:mount(MountPoint, TopicFilters)),
|
|
|
+ {ok, PState};
|
|
|
|
|
|
process(?PACKET(?PINGREQ), PState) ->
|
|
|
send(?PACKET(?PINGRESP), PState);
|
|
|
@@ -547,11 +538,11 @@ process(?DISCONNECT_PACKET(_), PState) ->
|
|
|
%%------------------------------------------------------------------------------
|
|
|
|
|
|
connack({?RC_SUCCESS, SP, PState}) ->
|
|
|
- emqx_hooks:run('client.connected', [credentials(PState), ?RC_SUCCESS, attrs(PState)]),
|
|
|
+ ok = emqx_hooks:run('client.connected', [credentials(PState), ?RC_SUCCESS, attrs(PState)]),
|
|
|
deliver({connack, ?RC_SUCCESS, sp(SP)}, update_mountpoint(PState));
|
|
|
|
|
|
connack({ReasonCode, PState = #pstate{proto_ver = ProtoVer}}) ->
|
|
|
- emqx_hooks:run('client.connected', [credentials(PState), ReasonCode, attrs(PState)]),
|
|
|
+ ok = emqx_hooks:run('client.connected', [credentials(PState), ReasonCode, attrs(PState)]),
|
|
|
[ReasonCode1] = reason_codes_compat(connack, [ReasonCode], ProtoVer),
|
|
|
_ = deliver({connack, ReasonCode1}, PState),
|
|
|
{error, emqx_reason_codes:name(ReasonCode1, ProtoVer), PState}.
|
|
|
@@ -660,8 +651,8 @@ deliver({connack, ReasonCode, SP}, PState) ->
|
|
|
send(?CONNACK_PACKET(ReasonCode, SP), PState);
|
|
|
|
|
|
deliver({publish, PacketId, Msg}, PState = #pstate{mountpoint = MountPoint}) ->
|
|
|
- _ = emqx_hooks:run('message.delivered', [credentials(PState)], Msg),
|
|
|
- Msg1 = emqx_message:update_expiry(Msg),
|
|
|
+ Msg0 = emqx_hooks:run_fold('message.deliver', [credentials(PState)], Msg),
|
|
|
+ Msg1 = emqx_message:update_expiry(Msg0),
|
|
|
Msg2 = emqx_mountpoint:unmount(MountPoint, Msg1),
|
|
|
send(emqx_packet:from_message(PacketId, emqx_message:remove_topic_alias(Msg2)), PState);
|
|
|
|
|
|
@@ -744,17 +735,6 @@ try_open_session(SessAttrs, PState = #pstate{zone = Zone,
|
|
|
Other -> Other
|
|
|
end.
|
|
|
|
|
|
-authenticate(Credentials, Password) ->
|
|
|
- case emqx_access_control:authenticate(Credentials, Password) of
|
|
|
- ok -> {ok, false};
|
|
|
- {ok, IsSuper} when is_boolean(IsSuper) ->
|
|
|
- {ok, IsSuper};
|
|
|
- {ok, Result} when is_map(Result) ->
|
|
|
- {ok, maps:get(is_superuser, Result, false)};
|
|
|
- {error, Error} ->
|
|
|
- {error, Error}
|
|
|
- end.
|
|
|
-
|
|
|
set_property(Name, Value, ?NO_PROPS) ->
|
|
|
#{Name => Value};
|
|
|
set_property(Name, Value, Props) ->
|
|
|
@@ -855,25 +835,21 @@ check_pub_caps(#mqtt_packet{header = #mqtt_packet_header{qos = QoS, retain = Ret
|
|
|
#pstate{zone = Zone}) ->
|
|
|
emqx_mqtt_caps:check_pub(Zone, #{qos => QoS, retain => Retain}).
|
|
|
|
|
|
-check_pub_acl(_Packet, #pstate{is_super = IsSuper, enable_acl = EnableAcl})
|
|
|
- when IsSuper orelse (not EnableAcl) ->
|
|
|
+check_pub_acl(_Packet, #pstate{credentials = #{is_super := IsSuper}, enable_acl = EnableAcl})
|
|
|
+ when IsSuper orelse (not EnableAcl) ->
|
|
|
ok;
|
|
|
-
|
|
|
check_pub_acl(#mqtt_packet{variable = #mqtt_packet_publish{topic_name = Topic}}, PState) ->
|
|
|
case emqx_access_control:check_acl(credentials(PState), publish, Topic) of
|
|
|
allow -> ok;
|
|
|
- deny ->
|
|
|
- {error, ?RC_NOT_AUTHORIZED}
|
|
|
+ deny -> {error, ?RC_NOT_AUTHORIZED}
|
|
|
end.
|
|
|
|
|
|
-run_check_steps([], _Packet, PState) ->
|
|
|
- {ok, PState};
|
|
|
+run_check_steps([], _Packet, _PState) ->
|
|
|
+ ok;
|
|
|
run_check_steps([Check|Steps], Packet, PState) ->
|
|
|
case Check(Packet, PState) of
|
|
|
ok ->
|
|
|
run_check_steps(Steps, Packet, PState);
|
|
|
- {ok, PState1} ->
|
|
|
- run_check_steps(Steps, Packet, PState1);
|
|
|
Error = {error, _RC} ->
|
|
|
Error
|
|
|
end.
|
|
|
@@ -886,15 +862,13 @@ check_subscribe(TopicFilters, PState = #pstate{zone = Zone}) ->
|
|
|
{error, TopicFilter1}
|
|
|
end.
|
|
|
|
|
|
-check_sub_acl(TopicFilters, #pstate{is_super = IsSuper, enable_acl = EnableAcl})
|
|
|
- when IsSuper orelse (not EnableAcl) ->
|
|
|
+check_sub_acl(TopicFilters, #pstate{credentials = #{is_super := IsSuper}, enable_acl = EnableAcl})
|
|
|
+ when IsSuper orelse (not EnableAcl) ->
|
|
|
{ok, TopicFilters};
|
|
|
-
|
|
|
check_sub_acl(TopicFilters, PState) ->
|
|
|
- Credentials = credentials(PState),
|
|
|
lists:foldr(
|
|
|
fun({Topic, SubOpts}, {Ok, Acc}) ->
|
|
|
- case emqx_access_control:check_acl(Credentials, subscribe, Topic) of
|
|
|
+ case emqx_access_control:check_acl(credentials(PState), publish, Topic) of
|
|
|
allow -> {Ok, [{Topic, SubOpts}|Acc]};
|
|
|
deny ->
|
|
|
{error, [{Topic, SubOpts#{rc := ?RC_NOT_AUTHORIZED}}|Acc]}
|
|
|
@@ -928,7 +902,7 @@ terminate(discard, _PState) ->
|
|
|
ok;
|
|
|
terminate(Reason, PState) ->
|
|
|
?LOG(info, "Shutdown for ~p", [Reason]),
|
|
|
- emqx_hooks:run('client.disconnected', [credentials(PState), Reason]).
|
|
|
+ ok = emqx_hooks:run('client.disconnected', [credentials(PState), Reason]).
|
|
|
|
|
|
start_keepalive(0, _PState) ->
|
|
|
ignore;
|
|
|
@@ -999,4 +973,4 @@ reason_codes_compat(_PktType, ReasonCodes, ?MQTT_PROTO_V5) ->
|
|
|
reason_codes_compat(unsuback, _ReasonCodes, _ProtoVer) ->
|
|
|
undefined;
|
|
|
reason_codes_compat(PktType, ReasonCodes, _ProtoVer) ->
|
|
|
- [emqx_reason_codes:compat(PktType, RC) || RC <- ReasonCodes].
|
|
|
+ [emqx_reason_codes:compat(PktType, RC) || RC <- ReasonCodes].
|