diff --git a/.github/workflows/build_packages.yaml b/.github/workflows/build_packages.yaml index e4cd6852..6cbad4e7 100644 --- a/.github/workflows/build_packages.yaml +++ b/.github/workflows/build_packages.yaml @@ -16,13 +16,13 @@ jobs: strategy: matrix: os: + - ubuntu20.04 - ubuntu18.04 - ubuntu16.04 - - ubuntu14.04 - debian10 - debian9 - - debian8 - opensuse + - centos8 - centos7 - centos6 @@ -30,14 +30,14 @@ jobs: - uses: actions/checkout@v1 - name: build emqx packages env: - ERL_OTP: erl22.1 + ERL_OTP: erl22.3 SYSTEM: ${{ matrix.os }} run: | docker run -i --name emqtt-$SYSTEM-build -v $(pwd):/emqtt emqx/build-env:$ERL_OTP-$SYSTEM /bin/bash -c "cd /emqtt && .github/workflows/script/build.sh" cd _packages && for var in $(ls); do sudo bash -c "echo $(sha256sum $var | awk '{print $1}') > $var.sha256"; done && cd - - uses: actions/upload-artifact@v1 with: - name: packages-${{ matrix.os }} + name: packages path: _packages/. build-on-mac: @@ -47,9 +47,9 @@ jobs: - uses: actions/checkout@v1 - name: prepare run: | - /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" - brew install curl zip unzip gnu-sed erlang - echo "/usr/local/bin:$PATH" >> ~/.bashrc + brew install curl zip unzip gnu-sed erlang@22 + echo "/usr/local/opt/erlang@22/bin" >> $GITHUB_PATH + echo "/usr/local/bin" >> $GITHUB_PATH - name: install rebar3 run: | curl -Lo /usr/local/bin/rebar3 https://s3.amazonaws.com/rebar3/rebar3 @@ -73,63 +73,28 @@ jobs: - uses: actions/checkout@v2 - uses: actions/download-artifact@v1 with: - name: packages-ubuntu18.04 - path: _packages - - uses: actions/download-artifact@v1 - with: - name: packages-ubuntu16.04 - path: _packages - - uses: actions/download-artifact@v1 - with: - name: packages-ubuntu14.04 - path: _packages - - uses: actions/download-artifact@v1 - with: - name: packages-debian10 - path: _packages - - uses: actions/download-artifact@v1 - with: - name: packages-debian9 - path: _packages - - uses: actions/download-artifact@v1 - with: - name: packages-debian8 - path: _packages - - uses: actions/download-artifact@v1 - with: - name: packages-opensuse - path: _packages - - uses: actions/download-artifact@v1 - with: - name: packages-centos7 - path: _packages - - uses: actions/download-artifact@v1 - with: - name: packages-centos6 - path: _packages - - uses: actions/download-artifact@v1 - with: - name: packages-mac + name: packages path: _packages + - name: get packages + run: | + cd _packages && for var in $( ls |grep emqtt |grep -v sha256); do + echo "$(cat $var.sha256) $var" | sha256sum -c || exit 1 + done - name: set aws + if: github.event_name == 'release' run: | curl "https://d1vvhvl2y92vvt.cloudfront.net/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" unzip awscliv2.zip sudo ./aws/install - aws configure set aws_access_key_id ${{ secrets.AwsAccessKeyId }} - aws configure set aws_secret_access_key ${{ secrets.AwsSecretAccessKey }} + aws configure set aws_access_key_id ${{ secrets.AWS_ACCESS_KEY_ID }} + aws configure set aws_secret_access_key ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws configure set default.region us-west-2 - - name: get packages - run: | - cd _packages && for var in $( ls |grep emqtt |grep -v sha256); do - echo "$(cat $var.sha256) $var" | sha256sum -c || exit 1 - done - name: upload aws if: github.event_name == 'release' run: | version=$(echo ${{ github.ref }} | sed -r "s .*/.*/(.*) \1 g") - aws s3 cp --recursive ./_packages s3://packages.emqx.io/emqtt/$version - aws cloudfront create-invalidation --distribution-id E3TYD0WSP4S14P --paths "/emqtt/$version/*" + aws s3 cp --recursive ./_packages s3://packages.emqx/emqtt/$version + aws cloudfront create-invalidation --distribution-id E170YEULGLT8XB --paths "/emqtt/$version/*" - name: upload github if: github.event_name == 'release' run: | diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e2a3233d..00000000 --- a/.travis.yml +++ /dev/null @@ -1,10 +0,0 @@ -language: erlang - -sudo: false - -otp_release: - - 21.3 - - 22.0 - -script: - - make diff --git a/README.md b/README.md index a0f05167..188710b2 100644 --- a/README.md +++ b/README.md @@ -369,7 +369,8 @@ option() = {name, atom()} | {auto_ack, boolean()} | {ack_timeout, pos_integer()} | {force_ping, boolean()} | - {properties, properties()} + {properties, properties()} | + {enhanced_auth, enhanced_auth()} ``` **client()** @@ -470,6 +471,33 @@ pubopt() = {retain, boolean()} | reason_code() = 0..16#FF ``` +**method()** + +``` +method() = binary() +``` + +**params()** + +``` +params() = any() +``` + +**enhanced_auth_state()** + +``` +enhanced_auth_state() = #{method => method(), params => params(), stage => initialized | atom(), latest_server_data => undefined | binary(), any() => any()}). +``` + +**enhanced_auth()** + +``` +enhanced_auth() = #{method => method(), params => params()} | + #{method => method(), params => params(), + function => fun((EnhancedAuthState :: enhanced_auth_state()) -> + {ok, NEnhancedAuthState :: enhanced_auth_state()} | {ok, NAuthData :: binary(), NEnhancedAuthState :: enhanced_auth_state()})} +``` + ### Exports **emqtt:start_link() -> {ok, Pid} | ignore | {error, Reason}** @@ -596,6 +624,10 @@ If false (the default), if any other packet is sent during keep alive interval, Properties of CONNECT packet. +`{enhanced_auth, EnhancedAuth}` + +The data required to enhance authentication + **emqtt:connect(Client) -> {ok, Properties} | {error, Reason}**   **Types** @@ -766,6 +798,18 @@ Send a `PUBREL` packet to the MQTT server. `PacketId`, `ReasonCode` and `Propert Send a `PUBCOMP` packet to the MQTT server. `PacketId`, `ReasonCode` and `Properties` specify packet identifier, reason code and properties for `PUBCOMP` packet. +**emqtt:reauthentication(Client) -> ok** + +**emqtt:reauthentication(Client, EnhancedAuth) -> ok** + +  **Types** + +    **Client = [client()](#client)** + +    **EnhancedAuth = [enhanced_auth](#enhanced_auth)** + +Send a `AUTH` packet to the MQTT server. + **emqtt:subscriptions(Client) -> Subscriptions**   **Types** diff --git a/rebar.config b/rebar.config index 7aa193b0..fb606601 100644 --- a/rebar.config +++ b/rebar.config @@ -1,7 +1,6 @@ {minimum_otp_vsn, "21.0"}. {erl_opts, [debug_info, - warn_export_all, warn_unused_vars, warn_shadow_vars, warn_unused_import, @@ -10,7 +9,8 @@ {deps, [{cowlib, "2.8.0"}, {gun, {git, "https://github.com/emqx/gun", {tag, "1.3.4"}}}, - {getopt, "1.0.1"} + {getopt, "1.0.1"}, + {esasl, {git, "https://github.com/emqx/esasl", {tag, "master"}}} ]}. {escript_name, emqtt_cli}. @@ -23,8 +23,9 @@ [{test, [{deps, [{meck, "0.8.13"}, - {emqx, {git, "https://github.com/emqx/emqx", {branch, "master"}}}, - {emqx_ct_helpers, {git, "https://github.com/emqx/emqx-ct-helpers", {branch, "develop"}}} + {emqx, {git, "https://github.com/emqx/emqx", {tag, "v4.2.2"}}}, + {emqx_sasl, {git, "https://github.com/emqx/emqx-sasl", {tag, "v4.2.2"}}}, + {emqx_ct_helpers, {git, "https://github.com/emqx/emqx-ct-helpers", {tag, "1.2.2"}}} ]}, {erl_opts, [debug_info]} ]}, @@ -42,7 +43,8 @@ os_mon, inets, compiler, - runtime_tools + runtime_tools, + esasl ]}, {overlay_vars,["vars.config"]}, {overlay, [ @@ -70,7 +72,8 @@ os_mon, inets, compiler, - runtime_tools + runtime_tools, + esasl ]}, {overlay_vars,["vars-pkg.config"]}, {overlay, [ @@ -102,5 +105,3 @@ syntax_tools, compiler, crypto]}. - - diff --git a/rebar3 b/rebar3 index 640804d1..fe721409 100755 Binary files a/rebar3 and b/rebar3 differ diff --git a/src/emqtt.erl b/src/emqtt.erl index 687b9007..fd33fd34 100644 --- a/src/emqtt.erl +++ b/src/emqtt.erl @@ -43,6 +43,8 @@ , publish/5 , unsubscribe/2 , unsubscribe/3 + , reauthentication/1 + , reauthentication/2 ]). %% Puback... @@ -165,6 +167,22 @@ -type(client() :: pid() | atom()). +-type(method() :: binary()). + +-type(params() :: any()). + +-type(enhanced_auth_state() :: #{method => method(), + params => params(), + stage => initialized | atom(), + latest_server_data => undefined | binary(), + any() => any()}). + +-type(enhanced_auth_function() :: fun((EnhancedAuthState :: enhanced_auth_state()) -> {ok, NEnhancedAuthState :: enhanced_auth_state()} + | {ok, NAuthData :: binary(), NEnhancedAuthState :: enhanced_auth_state()})). + +-type(enhanced_auth() :: #{method => method(), params => params()} + | #{method => method(), params => params(), function => enhanced_auth_function()}). + -opaque(mqtt_msg() :: #mqtt_msg{}). -record(state, { @@ -192,6 +210,8 @@ will_flag :: boolean(), will_msg :: mqtt_msg(), properties :: properties(), + enhanced_auth :: enhanced_auth(), + enhanced_auth_state :: enhanced_auth_state(), pending_calls :: list(), subscriptions :: map(), max_inflight :: infinity | pos_integer(), @@ -391,6 +411,12 @@ disconnect(Client, ReasonCode) -> disconnect(Client, ReasonCode, Properties) -> gen_statem:call(Client, {disconnect, ReasonCode, Properties}). +reauthentication(Client) -> + reauthentication(Client, #{}). + +reauthentication(Client, EnhancedAuth) when is_map(EnhancedAuth) -> + gen_statem:call(Client, {reauthentication, EnhancedAuth}). + %%-------------------------------------------------------------------- %% For test cases %%-------------------------------------------------------------------- @@ -479,13 +505,16 @@ init([Options]) -> inflight = #{}, awaiting_rel = #{}, properties = #{}, + enhanced_auth = #{}, + enhanced_auth_state = #{}, auto_ack = true, ack_timeout = ?DEFAULT_ACK_TIMEOUT, retry_interval = ?DEFAULT_RETRY_INTERVAL, connect_timeout = ?DEFAULT_CONNECT_TIMEOUT, last_packet_id = 1 }), - {ok, initialized, init_parse_state(State)}. + NState = init_enhanced_auth(State), + {ok, initialized, init_parse_state(NState)}. random_client_id() -> rand:seed(exsplus, erlang:timestamp()), @@ -578,6 +607,8 @@ init([{force_ping, ForcePing} | Opts], State) when is_boolean(ForcePing) -> init(Opts, State#state{force_ping = ForcePing}); init([{properties, Properties} | Opts], State = #state{properties = InitProps}) -> init(Opts, State#state{properties = maps:merge(InitProps, Properties)}); +init([{enhanced_auth, EnhancedAuth} | Opts], State = #state{enhanced_auth = InitEnhancedAuth}) -> + init(Opts, State#state{enhanced_auth = maps:merge(InitEnhancedAuth, EnhancedAuth)}); init([{max_inflight, infinity} | Opts], State) -> init(Opts, State#state{max_inflight = infinity, inflight = #{}}); @@ -606,7 +637,27 @@ init_will_msg({retain, Retain}, WillMsg) when is_boolean(Retain) -> init_will_msg({qos, QoS}, WillMsg) -> WillMsg#mqtt_msg{qos = ?QOS_I(QoS)}. -init_parse_state(State = #state{proto_ver = Ver, properties = Properties}) -> +init_enhanced_auth(State = #state{proto_ver = ?MQTT_PROTO_V5, + properties = Properties, + enhanced_auth =#{ + method := AuthMethod, + params := Parms + } = EnhancedAuth, + enhanced_auth_state = EnhancedAuthState}) -> + AuthFun = maps:get(function, EnhancedAuth, fun emqtt_sasl:check/1), + {ok, AuthData, NEnhancedAuthState} = erlang:apply(AuthFun, [maps:merge(EnhancedAuthState, + #{method => AuthMethod, + params => Parms, + stage => initialized, + latest_server_data => undefined})]), + State#state{properties = maps:merge(Properties, #{'Authentication-Method' => AuthMethod,'Authentication-Data' => AuthData}), + enhanced_auth = maps:merge(EnhancedAuth, #{function => AuthFun}), + enhanced_auth_state = NEnhancedAuthState}; + +init_enhanced_auth(State) -> State. + +init_parse_state(State = #state{proto_ver = Ver, + properties = Properties}) -> MaxSize = maps:get('Maximum-Packet-Size', Properties, ?MAX_PACKET_SIZE), ParseState = emqtt_frame:initial_parse_state( #{max_size => MaxSize, version => Ver}), @@ -629,7 +680,7 @@ initialized({call, From}, {connect, ConnMod}, State = #state{sock_opts = SockOpt case mqtt_connect(run_sock(State#state{conn_mod = ConnMod, socket = Sock})) of {ok, NewState} -> {next_state, waiting_for_connack, - add_call(new_call(connect, From), NewState), [Timeout]}; + add_call(new_call(connect, From), NewState), [Timeout]}; Error = {error, Reason} -> {stop_and_reply, Reason, [{reply, From, Error}]} end; @@ -670,6 +721,54 @@ mqtt_connect(State = #state{clientid = ClientId, username = Username, password = Password}), State). +waiting_for_connack(cast, _Packet = ?AUTH_PACKET(?RC_CONTINUE_AUTHENTICATION, + Properties = #{'Authentication-Method' := AuthMethod, + 'Authentication-Data' := AuthData}), + State = #state{proto_ver = ?MQTT_PROTO_V5, + properties = AllProps, + enhanced_auth = #{function := AuthFun}, + enhanced_auth_state = EnhancedAuthState}) -> + case erlang:apply(AuthFun, [maps:merge(EnhancedAuthState, #{latest_server_data => AuthData})]) of + {ok, NAuthData, NEnhancedAuthState} -> + NPacket = ?AUTH_PACKET(?RC_CONTINUE_AUTHENTICATION, #{'Authentication-Method' => AuthMethod, + 'Authentication-Data' => NAuthData}), + + case send(NPacket, State) of + {ok, NState} -> + {keep_state, NState#state{properties = maps:merge(AllProps, maps:merge(Properties, #{'Authentication-Data' => NAuthData})), + enhanced_auth_state = NEnhancedAuthState}}; + {error, Reason} -> + {stop, Reason} + end; + Reason -> {stop, Reason} + end; + +waiting_for_connack(cast, _Packet = ?CONNACK_PACKET(?RC_SUCCESS, + SessPresent, + Properties = #{'Authentication-Method' := _AuthMethod, + 'Authentication-Data' := AuthData}), + State = #state{properties = AllProps, + clientid = ClientId, + enhanced_auth = #{function := AuthFun}, + enhanced_auth_state = EnhancedAuthState}) -> + case take_call(connect, State) of + {value, #call{from = From}, State1} -> + case erlang:apply(AuthFun, [maps:merge(EnhancedAuthState, #{latest_server_data => AuthData})]) of + {ok, NEnhancedAuthState} -> + AllProps1 = maps:merge(AllProps, Properties), + Reply = {ok, Properties}, + State2 = State1#state{clientid = assign_id(ClientId, AllProps1), + properties = AllProps1, + session_present = SessPresent, + enhanced_auth_state = NEnhancedAuthState}, + {next_state, connected, ensure_keepalive_timer(State2), + [{reply, From, Reply}]}; + _ -> {stop, bad_connack} + end; + false -> + {stop, bad_connack} + end; + waiting_for_connack(cast, ?CONNACK_PACKET(?RC_SUCCESS, SessPresent, Properties), @@ -806,6 +905,25 @@ connected({call, From}, {disconnect, ReasonCode, Properties}, State) -> {stop_and_reply, Reason, [{reply, From, Error}]} end; +connected({call, From}, {reauthentication, EnhancedAuth}, State = #state{proto_ver = ?MQTT_PROTO_V5, + properties = #{'Authentication-Method' := AuthMethod}, + enhanced_auth = OldEnhancedAuth, + enhanced_auth_state = EnhancedAuthState}) -> + NEnhancedAuth = #{method := AuthMethod, params := Parms, function := AuthFun} = maps:merge(OldEnhancedAuth, EnhancedAuth), + + case erlang:apply(AuthFun, [maps:merge(EnhancedAuthState, #{params => Parms})]) of + {ok, AuthData, NEnhancedAuthState} -> + case send(?AUTH_PACKET(?RC_RE_AUTHENTICATE, #{'Authentication-Method' => AuthMethod, + 'Authentication-Data' => AuthData}), State) of + {ok, NewState} -> + {keep_state, NewState#state{enhanced_auth = NEnhancedAuth, enhanced_auth_state = NEnhancedAuthState},[{reply, From, ok}]}; + Error = {error, Reason} -> + {stop_and_reply, Reason, [{reply, From, Error}]} + end; + Error = {error, Reason} -> + {stop_and_reply, Reason, [{reply, From, Error}]} + end; + connected(cast, {puback, PacketId, ReasonCode, Properties}, State) -> send_puback(?PUBACK_PACKET(PacketId, ReasonCode, Properties), State); @@ -903,6 +1021,41 @@ connected(cast, ?PACKET(?PINGRESP), State) -> connected(cast, ?DISCONNECT_PACKET(ReasonCode, Properties), State) -> {stop, {disconnected, ReasonCode, Properties}, State}; +connected(cast, ?AUTH_PACKET(?RC_CONTINUE_AUTHENTICATION, + Properties = #{'Authentication-Method' := AuthMethod, + 'Authentication-Data' := AuthData}), + State = #state{proto_ver = ?MQTT_PROTO_V5, + properties = AllProps, + enhanced_auth = #{function := AuthFun}, + enhanced_auth_state = EnhancedAuthState}) -> + case erlang:apply(AuthFun, [maps:merge(EnhancedAuthState, #{latest_server_data => AuthData})]) of + {ok, NAuthData, NEnhancedAuthState} -> + NPacket = ?AUTH_PACKET(?RC_CONTINUE_AUTHENTICATION, #{'Authentication-Method' => AuthMethod, + 'Authentication-Data' => NAuthData}), + case send(NPacket, State) of + {ok, NState} -> + {keep_state, NState#state{properties = maps:merge(AllProps, maps:merge(Properties, #{'Authentication-Method' => NAuthData})), + enhanced_auth_state = NEnhancedAuthState}}; + {error, Reason} -> + {stop, Reason} + end; + Reason -> {stop, Reason} + end; + +connected(cast, ?AUTH_PACKET(?RC_SUCCESS, + Properties = #{'Authentication-Method' := _AuthMethod, + 'Authentication-Data' := AuthData}), + State = #state{proto_ver = ?MQTT_PROTO_V5, + properties = AllProps, + enhanced_auth = #{function := AuthFun}, + enhanced_auth_state = EnhancedAuthState}) -> + case erlang:apply(AuthFun, [maps:merge(EnhancedAuthState, #{latest_server_data => AuthData})]) of + {ok, NEnhancedAuthState} -> + {keep_state, State#state{properties = maps:merge(AllProps, Properties), + enhanced_auth_state = NEnhancedAuthState}}; + Reason -> {stop, Reason} + end; + connected(info, {timeout, _TRef, keepalive}, State = #state{force_ping = true}) -> case send(?PACKET(?PINGREQ), State) of {ok, NewState} -> diff --git a/src/emqtt_sasl.erl b/src/emqtt_sasl.erl new file mode 100644 index 00000000..ed635584 --- /dev/null +++ b/src/emqtt_sasl.erl @@ -0,0 +1,34 @@ +%%%------------------------------------------------------------------- +%% @doc sasl public API +%% @end +%%%------------------------------------------------------------------- + +-module(emqtt_sasl). + +-export([ check/1 + , supported/0]). + +check(EnhancedAuthState = #{method := <<"SCRAM-SHA-1">>, + params := Params, + stage := initialized}) -> + Data = esasl:apply(<<"SCRAM-SHA-1">>, Params), + AuthContext = maps:merge(Params, #{client_first => Data}), + {ok, Data, maps:merge(EnhancedAuthState, #{stage => continue, + context => AuthContext})}; + +check(EnhancedAuthState = #{method := <<"SCRAM-SHA-1">>, + stage := continue, + latest_server_data := ServerAuthData, + context := AuthContext}) -> + case esasl:check_server_data(<<"SCRAM-SHA-1">>, ServerAuthData, AuthContext) of + {continue, Data, NAuthContext} -> + {ok, Data, maps:merge(EnhancedAuthState, #{stage => continue, context => NAuthContext})}; + {ok, <<>>, _} -> + {ok, maps:merge(EnhancedAuthState, #{stage => initialized, context => #{}})} + end; + +check(_EnhancedAuthState) -> + {error, authentication_failed}. + +supported() -> + [<<"SCRAM-SHA-1">>]. diff --git a/test/emqtt_SUITE.erl b/test/emqtt_SUITE.erl index 2029e154..4fb0137f 100644 --- a/test/emqtt_SUITE.erl +++ b/test/emqtt_SUITE.erl @@ -68,10 +68,11 @@ groups() -> dollar_topics_test]}, {mqttv5, [non_parallel_tests], [basic_test_v5, - retain_as_publish_test]}]. + retain_as_publish_test, + enhanced_auth]}]. init_per_suite(Config) -> - emqx_ct_helpers:start_apps([]), + emqx_ct_helpers:start_apps([emqx_sasl]), Config. end_per_suite(_Config) -> @@ -327,7 +328,7 @@ anonymous_test(_Config) -> process_flag(trap_exit, true), {ok, C1} = emqtt:start_link(), {_,{unauthorized_client,_}} = emqtt:connect(C1), - receive {'EXIT', _, _} -> ok + receive {'EXIT',_, _} -> ok after 500 -> error("allow_anonymous") end, process_flag(trap_exit, false), @@ -335,7 +336,10 @@ anonymous_test(_Config) -> application:set_env(emqx, allow_anonymous, true), {ok, C2} = emqtt:start_link([{username, <<"test">>}, {password, <<"password">>}]), {ok, _} = emqtt:connect(C2), - ok = emqtt:disconnect(C2). + ok = emqtt:disconnect(C2), + + [{_, #{clientinfo := #{username := Username}}, _}] = ets:tab2list(emqx_channel_info), + ?assertEqual(<<"test">>, Username). retry_interval_test(_Config) -> {ok, Pub} = emqtt:start_link([{clean_start, true}, {retry_interval, 1}]), @@ -514,3 +518,41 @@ retain_as_publish_test(_) -> ok = emqtt:disconnect(Pub), clean_retained(Topic). + +enhanced_auth(_) -> + process_flag(trap_exit, true), + + Username = <<"username">>, + Password = <<"password">>, + Salt = <<"emqx">>, + AuthMethod = <<"SCRAM-SHA-1">>, + ok = emqx_sasl_scram:add(Username, Password, Salt), + + {error, _} = emqtt:start_link([{clean_start, true}, + {proto_ver, v5}, + {enhanced_auth, #{method => AuthMethod, + params => #{}, + function => fun (_State) -> {error, authentication_failed} end}}, + {connect_timeout, 6000}]), + + + {ok, Client1} = emqtt:start_link([{clean_start, true}, + {proto_ver, v5}, + {enhanced_auth, #{method => AuthMethod, + params => #{username => Username, + password => Password, + salt => Salt}}}, + {connect_timeout, 6000}]), + {ok, _} = emqtt:connect(Client1), + + timer:sleep(200), + ok = emqtt:reauthentication(Client1), + + timer:sleep(200), + ErrorFun = fun (_State) -> {error, authentication_failed} end, + {error,authentication_failed} = emqtt:reauthentication(Client1, #{params => #{username => Username, + password => Password, + salt => Salt}, + function => ErrorFun}), + + process_flag(trap_exit, false).