%%%------------------------------------------------------------------- %%% File : mod_host_meta.erl %%% Author : Badlop %%% Purpose : Serve host-meta files as described in XEP-0156 %%% Created : 25 March 2022 by Badlop %%% %%% %%% ejabberd, Copyright (C) 2022 ProcessOne %%% %%% This program is free software; you can redistribute it and/or %%% modify it under the terms of the GNU General Public License as %%% published by the Free Software Foundation; either version 2 of the %%% License, or (at your option) any later version. %%% %%% This program is distributed in the hope that it will be useful, %%% but WITHOUT ANY WARRANTY; without even the implied warranty of %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU %%% General Public License for more details. %%% %%% You should have received a copy of the GNU General Public License along %%% with this program; if not, write to the Free Software Foundation, Inc., %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. %%% %%%---------------------------------------------------------------------- -module(mod_host_meta). -author('badlop@process-one.net'). -protocol({xep, 156, '1.4.0', '22.05', "complete", ""}). -behaviour(gen_mod). -export([start/2, stop/1, reload/3, process/2, mod_opt_type/1, mod_options/1, depends/2]). -export([mod_doc/0]). -include("logger.hrl"). -include_lib("xmpp/include/xmpp.hrl"). -include("ejabberd_http.hrl"). -include("ejabberd_web_admin.hrl"). -include("translate.hrl"). %%%---------------------------------------------------------------------- %%% gen_mod callbacks %%%---------------------------------------------------------------------- start(_Host, _Opts) -> report_hostmeta_listener(), ok. stop(_Host) -> ok. reload(_Host, _NewOpts, _OldOpts) -> report_hostmeta_listener(), ok. depends(_Host, _Opts) -> [{mod_bosh, soft}]. %%%---------------------------------------------------------------------- %%% HTTP handlers %%%---------------------------------------------------------------------- process([], #request{method = 'GET', host = Host, path = Path}) -> case lists:last(Path) of <<"host-meta">> -> file_xml(Host); <<"host-meta.json">> -> file_json(Host) end; process(_Path, _Request) -> {404, [], "Not Found"}. %%%---------------------------------------------------------------------- %%% Internal %%%---------------------------------------------------------------------- %% When set to 'auto', it only takes the first valid listener options it finds file_xml(Host) -> BoshList = case ejabberd_http:get_url(?MODULE, bosh, true, Host) of undefined -> []; BoshUrl -> [?XA(<<"Link">>, [{<<"rel">>, <<"urn:xmpp:alt-connections:xbosh">>}, {<<"href">>, BoshUrl}] )] end, WsList = case ejabberd_http:get_url(?MODULE, websocket, true, Host) of undefined -> []; WsUrl -> [?XA(<<"Link">>, [{<<"rel">>, <<"urn:xmpp:alt-connections:websocket">>}, {<<"href">>, WsUrl}] )] end, {200, [html, {<<"Content-Type">>, <<"application/xrd+xml">>}, {<<"Access-Control-Allow-Origin">>, <<"*">>}], [<<"\n">>, fxml:element_to_binary( ?XAE(<<"XRD">>, [{<<"xmlns">>,<<"http://docs.oasis-open.org/ns/xri/xrd-1.0">>}], BoshList ++ WsList) )]}. file_json(Host) -> BoshList = case ejabberd_http:get_url(?MODULE, bosh, true, Host) of undefined -> []; BoshUrl -> [#{rel => <<"urn:xmpp:alt-connections:xbosh">>, href => BoshUrl}] end, WsList = case ejabberd_http:get_url(?MODULE, websocket, true, Host) of undefined -> []; WsUrl -> [#{rel => <<"urn:xmpp:alt-connections:websocket">>, href => WsUrl}] end, {200, [html, {<<"Content-Type">>, <<"application/json">>}, {<<"Access-Control-Allow-Origin">>, <<"*">>}], [misc:json_encode(#{links => BoshList ++ WsList})]}. report_hostmeta_listener() -> case {ejabberd_http:find_handler_port_path(false, ?MODULE), ejabberd_http:find_handler_port_path(true, ?MODULE)} of {[], []} -> ?CRITICAL_MSG("It seems you enabled ~p in 'modules' but forgot to " "add it as a request_handler in an ejabberd_http " "listener.", [?MODULE]); {[_|_], _} -> ?WARNING_MSG("Apparently ~p is enabled in a request_handler in a " "non-encrypted ejabberd_http listener. This is " "disallowed by XEP-0156. Please enable 'tls' in that " "listener, or setup a proxy encryption mechanism.", [?MODULE]); {[], [_|_]} -> ok end. %%%---------------------------------------------------------------------- %%% Options and Doc %%%---------------------------------------------------------------------- mod_opt_type(bosh_service_url) -> econf:either(undefined, econf:binary()); mod_opt_type(websocket_url) -> econf:either(undefined, econf:binary()). mod_options(_) -> [{bosh_service_url, <<"auto">>}, {websocket_url, <<"auto">>}]. mod_doc() -> #{desc => [?T("This module serves small 'host-meta' files as described in " "https://xmpp.org/extensions/xep-0156.html[XEP-0156: Discovering " "Alternative XMPP Connection Methods]."), "", ?T("To use this module, in addition to adding it to the 'modules' " "section, you must also enable it in 'listen' -> 'ejabberd_http' -> " "_`listen-options.md#request_handlers|request_handlers`_."), "", ?T("Notice it only works if _`listen.md#ejabberd_http|ejabberd_http`_ " "has _`listen-options.md#tls|tls`_ enabled.")], note => "added in 22.05", example => ["listen:", " -", " port: 443", " module: ejabberd_http", " tls: true", " request_handlers:", " /bosh: mod_bosh", " /websocket: ejabberd_http_ws", " /.well-known/host-meta: mod_host_meta", " /.well-known/host-meta.json: mod_host_meta", "", "modules:", " mod_bosh: {}", " mod_host_meta:", " bosh_service_url: \"https://@HOST@:5443/bosh\"", " websocket_url: \"wss://@HOST@:5443/websocket\""], opts => [{websocket_url, #{value => "undefined | auto | WebSocketURL", desc => ?T("WebSocket URL to announce. " "The keyword '@HOST@' is replaced with the real virtual " "host name. " "If set to 'auto', it will build the URL of the first " "configured WebSocket request handler. " "The default value is 'auto'.")}}, {bosh_service_url, #{value => "undefined | auto | BoshURL", desc => ?T("BOSH service URL to announce. " "The keyword '@HOST@' is replaced with the real " "virtual host name. " "If set to 'auto', it will build the URL of the first " "configured BOSH request handler. " "The default value is 'auto'.")}}] }.