From 550a586d2a44e938873ea5d33eae3e2d90774aa3 Mon Sep 17 00:00:00 2001 From: Badlop Date: Tue, 18 Jul 2023 10:55:56 +0200 Subject: [PATCH] New listener option unix_socket, useful when setting unix socket files (#4059) listen: - port: "unix://tmp/asd/socket" unix_socket: mode: '0775' owner: 117 group: 135 --- src/ejabberd_listener.erl | 85 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/src/ejabberd_listener.erl b/src/ejabberd_listener.erl index fcfe44ab3..26fddb8c8 100644 --- a/src/ejabberd_listener.erl +++ b/src/ejabberd_listener.erl @@ -108,6 +108,7 @@ init({Port, _, udp} = EndPoint, Module, Opts, SockOpts) -> {Port2, ExtraOpts} = case Port of <<"unix:", Path/binary>> -> SO = lists:keydelete(ip, 1, SockOpts), + setup_provisional_udsocket_dir(Path), file:delete(Path), {0, [{ip, {local, Path}} | SO]}; _ -> @@ -119,6 +120,7 @@ init({Port, _, udp} = EndPoint, Module, Opts, SockOpts) -> {reuseaddr, true} | ExtraOpts2]) of {ok, Socket} -> + set_definitive_udsocket(Port, Opts), case inet:sockname(Socket) of {ok, {Addr, Port1}} -> proc_lib:init_ack({ok, self()}), @@ -149,6 +151,7 @@ init({Port, _, udp} = EndPoint, Module, Opts, SockOpts) -> init({Port, _, tcp} = EndPoint, Module, Opts, SockOpts) -> case listen_tcp(Port, SockOpts) of {ok, ListenSocket} -> + set_definitive_udsocket(Port, Opts), case inet:sockname(ListenSocket) of {ok, {Addr, Port1}} -> proc_lib:init_ack({ok, self()}), @@ -186,8 +189,10 @@ listen_tcp(Port, SockOpts) -> {Port2, ExtraOpts} = case Port of <<"unix:", Path/binary>> -> SO = lists:keydelete(ip, 1, SockOpts), + Prov = setup_provisional_udsocket_dir(Path), file:delete(Path), - {0, [{ip, {local, Path}} | SO]}; + file:delete(Prov), + {0, [{ip, {local, Prov}} | SO]}; _ -> {Port, SockOpts} end, @@ -205,6 +210,72 @@ listen_tcp(Port, SockOpts) -> Err end. +%%% +%%% Unix Domain Socket utility functions +%%% + +setup_provisional_udsocket_dir(DefinitivePath) -> + ProvisionalPath = get_provisional_udsocket_path(DefinitivePath), + SocketDir = filename:dirname(ProvisionalPath), + file:make_dir(SocketDir), + file:change_mode(SocketDir, 8#00700), + ?DEBUG("Creating a Unix Domain Socket provisional file at ~ts for the definitive path ~s", + [ProvisionalPath, DefinitivePath]), + ProvisionalPath. + +get_provisional_udsocket_path(Path) -> + MnesiaDir = mnesia:system_info(directory), + SocketDir = filename:join(MnesiaDir, "socket"), + PathBase64 = misc:term_to_base64(Path), + PathBuild = filename:join(SocketDir, PathBase64), + %% Shorthen the path, a long path produces a crash when opening the socket. + binary:part(PathBuild, {0, erlang:min(107, byte_size(PathBuild))}). + +get_definitive_udsocket_path(<<"unix", _>> = Unix) -> + Unix; +get_definitive_udsocket_path(ProvisionalPath) -> + PathBase64 = filename:basename(ProvisionalPath), + {term, Path} = misc:base64_to_term(PathBase64), + Path. + +set_definitive_udsocket(<<"unix:", Path/binary>>, Opts) -> + Prov = get_provisional_udsocket_path(Path), + timer:sleep(5000), + Usd = maps:get(unix_socket, Opts), + case maps:get(mode, Usd, undefined) of + undefined -> ok; + Mode -> ok = file:change_mode(Prov, Mode) + end, + case maps:get(owner, Usd, undefined) of + undefined -> ok; + Owner -> + try + ok = file:change_owner(Prov, Owner) + catch + error:{badmatch, {error, eperm}} -> + ?ERROR_MSG("Error trying to set owner ~p for socket ~p", [Owner, Prov]), + throw({error_setting_socket_owner, Owner, Prov}) + end + end, + case maps:get(group, Usd, undefined) of + undefined -> ok; + Group -> + try + ok = file:change_group(Prov, Group) + catch + error:{badmatch, {error, eperm}} -> + ?ERROR_MSG("Error trying to set group ~p for socket ~p", [Group, Prov]), + throw({error_setting_socket_group, Group, Prov}) + end + end, + file:rename(Prov, Path); +set_definitive_udsocket(_Port, _Opts) -> + ok. + +%%% +%%% +%%% + -spec split_opts(transport(), opts()) -> {opts(), [gen_tcp:option()]}. split_opts(Transport, Opts) -> maps:fold( @@ -493,8 +564,11 @@ format_error(Reason) -> -spec format_endpoint(endpoint()) -> string(). format_endpoint({Port, IP, _Transport}) -> case Port of + <<"unix:", _/binary>> -> + Port; Unix when is_binary(Unix) -> - <<"unix:", Unix/binary>>; + Def = get_definitive_udsocket_path(Unix), + <<"unix:", Def/binary>>; _ -> IPStr = case tuple_size(IP) of 4 -> inet:ntoa(IP); @@ -699,6 +773,12 @@ listen_opt_type(shaper) -> econf:shaper(); listen_opt_type(access) -> econf:acl(); +listen_opt_type(unix_socket) -> + econf:options( + #{group => econf:non_neg_int(), + owner => econf:non_neg_int(), + mode => econf:octal()}, + [unique, {return, map}]); listen_opt_type(use_proxy_protocol) -> econf:bool(). @@ -709,5 +789,6 @@ listen_options() -> {accept_interval, 0}, {send_timeout, 15000}, {backlog, 128}, + {unix_socket, #{}}, {use_proxy_protocol, false}, {supervisor, true}].