diff options
Diffstat (limited to 'src/web')
-rw-r--r-- | src/web/Makefile.in | 37 | ||||
-rw-r--r-- | src/web/Makefile.win32 | 33 | ||||
-rw-r--r-- | src/web/ejabberd_http.erl | 826 | ||||
-rw-r--r-- | src/web/ejabberd_http.hrl | 36 | ||||
-rw-r--r-- | src/web/ejabberd_http_bind.erl | 1235 | ||||
-rw-r--r-- | src/web/ejabberd_http_poll.erl | 428 | ||||
-rw-r--r-- | src/web/ejabberd_web.erl | 105 | ||||
-rw-r--r-- | src/web/ejabberd_web_admin.erl | 2893 | ||||
-rw-r--r-- | src/web/ejabberd_web_admin.hrl | 105 | ||||
-rw-r--r-- | src/web/http_bind.hrl | 47 | ||||
-rw-r--r-- | src/web/mod_http_bind.erl | 144 | ||||
-rw-r--r-- | src/web/mod_http_fileserver.erl | 454 | ||||
-rw-r--r-- | src/web/mod_register_web.erl | 565 |
13 files changed, 0 insertions, 6908 deletions
diff --git a/src/web/Makefile.in b/src/web/Makefile.in deleted file mode 100644 index 151f4c476..000000000 --- a/src/web/Makefile.in +++ /dev/null @@ -1,37 +0,0 @@ -# $Id$ - -CC = @CC@ -CFLAGS = @CFLAGS@ -CPPFLAGS = @CPPFLAGS@ -LDFLAGS = @LDFLAGS@ -LIBS = @LIBS@ - -ERLANG_CFLAGS = @ERLANG_CFLAGS@ -ERLANG_LIBS = @ERLANG_LIBS@ - -EFLAGS += -I .. -EFLAGS += -pz .. - -# make debug=true to compile Erlang module with debug informations. -ifdef debug - EFLAGS+=+debug_info -endif - -SOURCES = $(wildcard *.erl) -OUTDIR = .. -BEAMS = $(addprefix $(OUTDIR)/,$(SOURCES:.erl=.beam)) - -all: $(BEAMS) - -$(OUTDIR)/%.beam: %.erl - @ERLC@ -W $(EFLAGS) -o $(OUTDIR) $< - -clean: - rm -f $(BEAMS) - -distclean: clean - rm -f Makefile - -TAGS: - etags *.erl - diff --git a/src/web/Makefile.win32 b/src/web/Makefile.win32 deleted file mode 100644 index 411d57ce8..000000000 --- a/src/web/Makefile.win32 +++ /dev/null @@ -1,33 +0,0 @@ - -include ..\Makefile.inc - -EFLAGS = -I .. -pz .. - -OUTDIR = .. -BEAMS = ..\ejabberd_http.beam ..\ejabberd_http_bind.beam ..\ejabberd_http_poll.beam ..\ejabberd_web.beam ..\ejabberd_web_admin.beam ..\mod_http_bind.beam ..\mod_http_fileserver.beam - -ALL : $(BEAMS) - -CLEAN : - -@erase $(BEAMS) - -$(OUTDIR)\ejabberd_http.beam : ejabberd_http.erl - erlc -W $(EFLAGS) -o $(OUTDIR) ejabberd_http.erl - -$(OUTDIR)\ejabberd_web.beam : ejabberd_web.erl - erlc -W $(EFLAGS) -o $(OUTDIR) ejabberd_web.erl - -$(OUTDIR)\ejabberd_web_admin.beam : ejabberd_web_admin.erl - erlc -W $(EFLAGS) -o $(OUTDIR) ejabberd_web_admin.erl - -$(OUTDIR)\ejabberd_http_bind.beam : ejabberd_http_bind.erl - erlc -W $(EFLAGS) -o $(OUTDIR) ejabberd_http_bind.erl - -$(OUTDIR)\ejabberd_http_poll.beam : ejabberd_http_poll.erl - erlc -W $(EFLAGS) -o $(OUTDIR) ejabberd_http_poll.erl - -$(OUTDIR)\mod_http_bind.beam : mod_http_bind.erl - erlc -W $(EFLAGS) -o $(OUTDIR) mod_http_bind.erl - -$(OUTDIR)\mod_http_fileserver.beam : mod_http_fileserver.erl - erlc -W $(EFLAGS) -o $(OUTDIR) mod_http_fileserver.erl diff --git a/src/web/ejabberd_http.erl b/src/web/ejabberd_http.erl deleted file mode 100644 index 25928c0af..000000000 --- a/src/web/ejabberd_http.erl +++ /dev/null @@ -1,826 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : ejabberd_http.erl -%%% Author : Alexey Shchepin <alexey@process-one.net> -%%% Purpose : -%%% Created : 27 Feb 2004 by Alexey Shchepin <alexey@process-one.net> -%%% -%%% -%%% ejabberd, Copyright (C) 2002-2013 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., 59 Temple Place, Suite 330, Boston, MA -%%% 02111-1307 USA -%%% -%%%---------------------------------------------------------------------- - --module(ejabberd_http). - --author('alexey@process-one.net'). - -%% External exports --export([start/2, start_link/2, become_controller/1, - socket_type/0, receive_headers/1, url_encode/1]). - -%% Callbacks --export([init/2]). - --include("ejabberd.hrl"). - --include("jlib.hrl"). - --include("ejabberd_http.hrl"). - --record(state, {sockmod, - socket, - request_method, - request_version, - request_path, - request_auth, - request_keepalive, - request_content_length, - request_lang = "en", - %% XXX bard: request handlers are configured in - %% ejabberd.cfg under the HTTP service. For example, - %% to have the module test_web handle requests with - %% paths starting with "/test/module": - %% - %% {5280, ejabberd_http, [http_poll, web_admin, - %% {request_handlers, [{["test", "module"], mod_test_web}]}]} - %% - request_handlers = [], - request_host, - request_port, - request_tp, - request_headers = [], - end_of_request = false, - default_host, - trail = <<>> - }). - --define(XHTML_DOCTYPE, - <<"<?xml version='1.0'?>\n<!DOCTYPE html " - "PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//" - "EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1" - "-transitional.dtd\">\n">>). - --define(HTML_DOCTYPE, - <<"<!DOCTYPE html PUBLIC \"-//W3C//DTD " - "XHTML 1.0 Transitional//EN\" \"http://www.w3." - "org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n" - "">>). - -start(SockData, Opts) -> - supervisor:start_child(ejabberd_http_sup, - [SockData, Opts]). - -start_link(SockData, Opts) -> - {ok, - proc_lib:spawn_link(ejabberd_http, init, - [SockData, Opts])}. - -init({SockMod, Socket}, Opts) -> - TLSEnabled = lists:member(tls, Opts), - TLSOpts1 = lists:filter(fun ({certfile, _}) -> true; - (_) -> false - end, - Opts), - TLSOpts = [verify_none | TLSOpts1], - {SockMod1, Socket1} = if TLSEnabled -> - inet:setopts(Socket, [{recbuf, 8192}]), - {ok, TLSSocket} = tls:tcp_to_tls(Socket, - TLSOpts), - {tls, TLSSocket}; - true -> {SockMod, Socket} - end, - case SockMod1 of - gen_tcp -> - inet:setopts(Socket1, [{packet, http_bin}, {recbuf, 8192}]); - _ -> ok - end, - Captcha = case lists:member(captcha, Opts) of - true -> [{[<<"captcha">>], ejabberd_captcha}]; - false -> [] - end, - Register = case lists:member(register, Opts) of - true -> [{[<<"register">>], mod_register_web}]; - false -> [] - end, - Admin = case lists:member(web_admin, Opts) of - true -> [{[<<"admin">>], ejabberd_web_admin}]; - false -> [] - end, - Bind = case lists:member(http_bind, Opts) of - true -> [{[<<"http-bind">>], mod_http_bind}]; - false -> [] - end, - Poll = case lists:member(http_poll, Opts) of - true -> [{[<<"http-poll">>], ejabberd_http_poll}]; - false -> [] - end, - DefinedHandlers = case lists:keysearch(request_handlers, - 1, Opts) - of - {value, {request_handlers, H}} -> H; - false -> [] - end, - RequestHandlers = DefinedHandlers ++ Captcha ++ Register ++ - Admin ++ Bind ++ Poll, - ?DEBUG("S: ~p~n", [RequestHandlers]), - - DefaultHost = gen_mod:get_opt(default_host, Opts, fun(A) -> A end, undefined), - - ?INFO_MSG("started: ~p", [{SockMod1, Socket1}]), - State = #state{sockmod = SockMod1, - socket = Socket1, - default_host = DefaultHost, - request_handlers = RequestHandlers}, - receive_headers(State). - -become_controller(_Pid) -> ok. - -socket_type() -> - raw. - -send_text(State, Text) -> - case catch - (State#state.sockmod):send(State#state.socket, Text) - of - ok -> ok; - {error, timeout} -> - ?INFO_MSG("Timeout on ~p:send", [State#state.sockmod]), - exit(normal); - Error -> - ?DEBUG("Error in ~p:send: ~p", - [State#state.sockmod, Error]), - exit(normal) - end. - -receive_headers(#state{trail = Trail} = State) -> - SockMod = State#state.sockmod, - Socket = State#state.socket, - Data = SockMod:recv(Socket, 0, 300000), - case State#state.sockmod of - gen_tcp -> - NewState = process_header(State, Data), - case NewState#state.end_of_request of - true -> - ok; - _ -> - receive_headers(NewState) - end; - _ -> - case Data of - {ok, D} -> - parse_headers(State#state{trail = <<Trail/binary, D/binary>>}); - {error, _} -> - ok - end - end. - -parse_headers(#state{trail = <<>>} = State) -> - receive_headers(State); -parse_headers(#state{request_method = Method, - trail = Data} = - State) -> - PktType = case Method of - undefined -> http; - _ -> httph - end, - case erlang:decode_packet(PktType, Data) of - {ok, Pkt, Rest} -> - NewState = process_header(State#state{trail = Rest}, {ok, Pkt}), - case NewState#state.end_of_request of - true -> - ok; - _ -> - parse_headers(NewState) - end; - {more, _} -> - receive_headers(State#state{trail = Data}); - _ -> - ok - end. - -process_header(State, Data) -> - SockMod = State#state.sockmod, - Socket = State#state.socket, - case Data of - {ok, {http_request, Method, Uri, Version}} -> - KeepAlive = case Version of - {1, 1} -> true; - _ -> false - end, - Path = case Uri of - {absoluteURI, _Scheme, _Host, _Port, P} -> - {abs_path, P}; - {abs_path, P} -> - {abs_path, P}; - _ -> Uri - end, - State#state{request_method = Method, - request_version = Version, request_path = Path, - request_keepalive = KeepAlive}; - {ok, {http_header, _, 'Connection' = Name, _, Conn}} -> - KeepAlive1 = case jlib:tolower(Conn) of - <<"keep-alive">> -> true; - <<"close">> -> false; - _ -> State#state.request_keepalive - end, - State#state{request_keepalive = KeepAlive1, - request_headers = add_header(Name, Conn, State)}; - {ok, - {http_header, _, 'Authorization' = Name, _, Auth}} -> - State#state{request_auth = parse_auth(Auth), - request_headers = add_header(Name, Auth, State)}; - {ok, - {http_header, _, 'Content-Length' = Name, _, SLen}} -> - case catch jlib:binary_to_integer(SLen) of - Len when is_integer(Len) -> - State#state{request_content_length = Len, - request_headers = add_header(Name, SLen, State)}; - _ -> State - end; - {ok, - {http_header, _, 'Accept-Language' = Name, _, Langs}} -> - State#state{request_lang = parse_lang(Langs), - request_headers = add_header(Name, Langs, State)}; - {ok, {http_header, _, 'Host' = Name, _, Host}} -> - State#state{request_host = Host, - request_headers = add_header(Name, Host, State)}; - {ok, {http_header, _, Name, _, Value}} -> - State#state{request_headers = - add_header(Name, Value, State)}; - {ok, http_eoh} - when State#state.request_host == undefined -> - ?WARNING_MSG("An HTTP request without 'Host' HTTP " - "header was received.", - []), - throw(http_request_no_host_header); - {ok, http_eoh} -> - ?DEBUG("(~w) http query: ~w ~s~n", - [State#state.socket, State#state.request_method, - element(2, State#state.request_path)]), - {HostProvided, Port, TP} = - get_transfer_protocol(SockMod, - State#state.request_host), - Host = get_host_really_served(State#state.default_host, - HostProvided), - State2 = State#state{request_host = Host, - request_port = Port, request_tp = TP}, - Out = process_request(State2), - send_text(State2, Out), - case State2#state.request_keepalive of - true -> - case SockMod of - gen_tcp -> inet:setopts(Socket, [{packet, http_bin}]); - _ -> ok - end, - #state{sockmod = SockMod, socket = Socket, - request_handlers = State#state.request_handlers}; - _ -> - #state{end_of_request = true, - request_handlers = State#state.request_handlers} - end; - {error, _Reason} -> - #state{end_of_request = true, - request_handlers = State#state.request_handlers}; - _ -> - #state{end_of_request = true, - request_handlers = State#state.request_handlers} - end. - -add_header(Name, Value, State)-> - [{Name, Value} | State#state.request_headers]. - -get_host_really_served(undefined, Provided) -> - Provided; -get_host_really_served(Default, Provided) -> - case lists:member(Provided, ?MYHOSTS) of - true -> Provided; - false -> Default - end. - -%% @spec (SockMod, HostPort) -> {Host::string(), Port::integer(), TP} -%% where -%% SockMod = gen_tcp | tls -%% HostPort = string() -%% TP = http | https -%% @doc Given a socket and hostport header, return data of transfer protocol. -%% Note that HostPort can be a string of a host like "example.org", -%% or a string of a host and port like "example.org:5280". -get_transfer_protocol(SockMod, HostPort) -> - [Host | PortList] = str:tokens(HostPort, <<":">>), - case {SockMod, PortList} of - {gen_tcp, []} -> {Host, 80, http}; - {gen_tcp, [Port]} -> - {Host, jlib:binary_to_integer(Port), http}; - {tls, []} -> {Host, 443, https}; - {tls, [Port]} -> - {Host, jlib:binary_to_integer(Port), https} - end. - -%% XXX bard: search through request handlers looking for one that -%% matches the requested URL path, and pass control to it. If none is -%% found, answer with HTTP 404. -process([], _) -> - ejabberd_web:error(not_found); -process(Handlers, Request) -> - %% Only the first element in the path prefix is checked - [{HandlerPathPrefix, HandlerModule} | HandlersLeft] = - Handlers, - case lists:prefix(HandlerPathPrefix, - Request#request.path) - or (HandlerPathPrefix == Request#request.path) - of - true -> - ?DEBUG("~p matches ~p", - [Request#request.path, HandlerPathPrefix]), - LocalPath = lists:nthtail(length(HandlerPathPrefix), - Request#request.path), - ?DEBUG("~p", [Request#request.headers]), - R = HandlerModule:process(LocalPath, Request), - ejabberd_hooks:run(http_request_debug, - [{LocalPath, Request}]), - R; - false -> process(HandlersLeft, Request) - end. - -process_request(#state{request_method = Method, - request_path = {abs_path, Path}, request_auth = Auth, - request_lang = Lang, request_handlers = RequestHandlers, - request_host = Host, request_port = Port, - request_tp = TP, request_headers = RequestHeaders, - sockmod = SockMod, - socket = Socket} = State) - when Method=:='GET' orelse Method=:='HEAD' orelse Method=:='DELETE' orelse Method=:='OPTIONS' -> - case (catch url_decode_q_split(Path)) of - {'EXIT', _} -> - make_bad_request(State); - {NPath, Query} -> - LPath = [path_decode(NPE) || NPE <- str:tokens(NPath, <<"/">>)], - LQuery = case (catch parse_urlencoded(Query)) of - {'EXIT', _Reason} -> - []; - LQ -> - LQ - end, - {ok, IPHere} = - case SockMod of - gen_tcp -> - inet:peername(Socket); - _ -> - SockMod:peername(Socket) - end, - XFF = proplists:get_value('X-Forwarded-For', RequestHeaders, []), - IP = analyze_ip_xff(IPHere, XFF, Host), - Request = #request{method = Method, - path = LPath, - q = LQuery, - auth = Auth, - lang = Lang, - host = Host, - port = Port, - tp = TP, - headers = RequestHeaders, - ip = IP}, - %% XXX bard: This previously passed control to - %% ejabberd_web:process_get, now passes it to a local - %% procedure (process) that handles dispatching based on - %% URL path prefix. - case process(RequestHandlers, Request) of - El when element(1, El) == xmlel -> - make_xhtml_output(State, 200, [], El); - {Status, Headers, El} when - element(1, El) == xmlel -> - make_xhtml_output(State, Status, Headers, El); - Output when is_list(Output) or is_binary(Output) -> - make_text_output(State, 200, [], Output); - {Status, Headers, Output} when is_list(Output) or is_binary(Output) -> - make_text_output(State, Status, Headers, Output) - end - end; -process_request(#state{request_method = Method, - request_path = {abs_path, Path}, request_auth = Auth, - request_content_length = Len, request_lang = Lang, - sockmod = SockMod, socket = Socket, request_host = Host, - request_port = Port, request_tp = TP, - request_headers = RequestHeaders, - request_handlers = RequestHandlers} = - State) - when (Method =:= 'POST' orelse Method =:= 'PUT') andalso - is_integer(Len) -> - {ok, IPHere} = case SockMod of - gen_tcp -> inet:peername(Socket); - _ -> SockMod:peername(Socket) - end, - XFF = proplists:get_value('X-Forwarded-For', - RequestHeaders, []), - IP = analyze_ip_xff(IPHere, XFF, Host), - case SockMod of - gen_tcp -> inet:setopts(Socket, [{packet, 0}]); - _ -> ok - end, - Data = recv_data(State, Len), - ?DEBUG("client data: ~p~n", [Data]), - case (catch url_decode_q_split(Path)) of - {'EXIT', _} -> - make_bad_request(State); - {NPath, _Query} -> - LPath = [path_decode(NPE) || NPE <- str:tokens(NPath, <<"/">>)], - LQuery = case (catch parse_urlencoded(Data)) of - {'EXIT', _Reason} -> - []; - LQ -> - LQ - end, - Request = #request{method = Method, - path = LPath, - q = LQuery, - auth = Auth, - data = Data, - lang = Lang, - host = Host, - port = Port, - tp = TP, - headers = RequestHeaders, - ip = IP}, - case process(RequestHandlers, Request) of - El when element(1, El) == xmlel -> - make_xhtml_output(State, 200, [], El); - {Status, Headers, El} when - element(1, El) == xmlel -> - make_xhtml_output(State, Status, Headers, El); - Output when is_list(Output) or is_binary(Output) -> - make_text_output(State, 200, [], Output); - {Status, Headers, Output} when is_list(Output) or is_binary(Output) -> - make_text_output(State, Status, Headers, Output) - end - end; -process_request(State) -> make_bad_request(State). - -make_bad_request(State) -> -%% Support for X-Forwarded-From - make_xhtml_output(State, 400, [], - ejabberd_web:make_xhtml([#xmlel{name = <<"h1">>, - attrs = [], - children = - [{xmlcdata, - <<"400 Bad Request">>}]}])). - -analyze_ip_xff(IP, [], _Host) -> IP; -analyze_ip_xff({IPLast, Port}, XFF, Host) -> - [ClientIP | ProxiesIPs] = str:tokens(XFF, <<", ">>) ++ - [jlib:ip_to_list(IPLast)], - TrustedProxies = ejabberd_config:get_local_option( - {trusted_proxies, Host}, - fun(TPs) -> - [iolist_to_binary(TP) || TP <- TPs] - end, []), - IPClient = case is_ipchain_trusted(ProxiesIPs, - TrustedProxies) - of - true -> - {ok, IPFirst} = inet_parse:address( - binary_to_list(ClientIP)), - ?DEBUG("The IP ~w was replaced with ~w due to " - "header X-Forwarded-For: ~s", - [IPLast, IPFirst, XFF]), - IPFirst; - false -> IPLast - end, - {IPClient, Port}. - -is_ipchain_trusted(_UserIPs, all) -> true; -is_ipchain_trusted(UserIPs, TrustedIPs) -> - [] == UserIPs -- [<<"127.0.0.1">> | TrustedIPs]. - -recv_data(State, Len) -> recv_data(State, Len, <<>>). - -recv_data(_State, 0, Acc) -> (iolist_to_binary(Acc)); -recv_data(State, Len, Acc) -> - case State#state.trail of - <<>> -> - case (State#state.sockmod):recv(State#state.socket, Len, - 300000) - of - {ok, Data} -> - recv_data(State, Len - byte_size(Data), <<Acc/binary, Data/binary>>); - _ -> <<"">> - end; - _ -> - Trail = (State#state.trail), - recv_data(State#state{trail = <<>>}, - Len - byte_size(Trail), <<Acc/binary, Trail/binary>>) - end. - -make_xhtml_output(State, Status, Headers, XHTML) -> - Data = case lists:member(html, Headers) of - true -> - iolist_to_binary([?HTML_DOCTYPE, - xml:element_to_binary(XHTML)]); - _ -> - iolist_to_binary([?XHTML_DOCTYPE, - xml:element_to_binary(XHTML)]) - end, - Headers1 = case lists:keysearch(<<"Content-Type">>, 1, - Headers) - of - {value, _} -> - [{<<"Content-Length">>, - iolist_to_binary(integer_to_list(byte_size(Data)))} - | Headers]; - _ -> - [{<<"Content-Type">>, <<"text/html; charset=utf-8">>}, - {<<"Content-Length">>, - iolist_to_binary(integer_to_list(byte_size(Data)))} - | Headers] - end, - HeadersOut = case {State#state.request_version, - State#state.request_keepalive} - of - {{1, 1}, true} -> Headers1; - {_, true} -> - [{<<"Connection">>, <<"keep-alive">>} | Headers1]; - {_, false} -> - [{<<"Connection">>, <<"close">>} | Headers1] - end, - Version = case State#state.request_version of - {1, 1} -> <<"HTTP/1.1 ">>; - _ -> <<"HTTP/1.0 ">> - end, - H = lists:map(fun ({Attr, Val}) -> - [Attr, <<": ">>, Val, <<"\r\n">>]; - (_) -> [] - end, - HeadersOut), - SL = [Version, - iolist_to_binary(integer_to_list(Status)), <<" ">>, - code_to_phrase(Status), <<"\r\n">>], - Data2 = case State#state.request_method of - 'HEAD' -> <<"">>; - _ -> Data - end, - [SL, H, <<"\r\n">>, Data2]. - -make_text_output(State, Status, Headers, Text) -> - make_text_output(State, Status, <<"">>, Headers, Text). - -make_text_output(State, Status, Reason, Headers, Text) -> - Data = iolist_to_binary(Text), - Headers1 = case lists:keysearch(<<"Content-Type">>, 1, - Headers) - of - {value, _} -> - [{<<"Content-Length">>, - jlib:integer_to_binary(byte_size(Data))} - | Headers]; - _ -> - [{<<"Content-Type">>, <<"text/html; charset=utf-8">>}, - {<<"Content-Length">>, - jlib:integer_to_binary(byte_size(Data))} - | Headers] - end, - HeadersOut = case {State#state.request_version, - State#state.request_keepalive} - of - {{1, 1}, true} -> Headers1; - {_, true} -> - [{<<"Connection">>, <<"keep-alive">>} | Headers1]; - {_, false} -> - [{<<"Connection">>, <<"close">>} | Headers1] - end, - Version = case State#state.request_version of - {1, 1} -> <<"HTTP/1.1 ">>; - _ -> <<"HTTP/1.0 ">> - end, - H = lists:map(fun ({Attr, Val}) -> - [Attr, <<": ">>, Val, <<"\r\n">>] - end, - HeadersOut), - NewReason = case Reason of - <<"">> -> code_to_phrase(Status); - _ -> Reason - end, - SL = [Version, - jlib:integer_to_binary(Status), <<" ">>, - NewReason, <<"\r\n">>], - Data2 = case State#state.request_method of - 'HEAD' -> <<"">>; - _ -> Data - end, - [SL, H, <<"\r\n">>, Data2]. - -parse_lang(Langs) -> - case str:tokens(Langs, <<",; ">>) of - [First | _] -> First; - [] -> <<"en">> - end. - -% Code below is taken (with some modifications) from the yaws webserver, which -% is distributed under the folowing license: -% -% This software (the yaws webserver) is free software. -% Parts of this software is Copyright (c) Claes Wikstrom <klacke@hyber.org> -% Any use or misuse of the source code is hereby freely allowed. -% -% 1. Redistributions of source code must retain the above copyright -% notice as well as this list of conditions. -% -% 2. Redistributions in binary form must reproduce the above copyright -% notice as well as this list of conditions. - -%% @doc Split the URL and return {Path, QueryPart} -url_decode_q_split(Path) -> - url_decode_q_split(Path, <<>>). - -url_decode_q_split(<<$?, T/binary>>, Acc) -> - %% Don't decode the query string here, that is parsed separately. - {path_norm_reverse(Acc), T}; -url_decode_q_split(<<H, T/binary>>, Acc) when H /= 0 -> - url_decode_q_split(T, <<H, Acc/binary>>); -url_decode_q_split(<<>>, Ack) -> - {path_norm_reverse(Ack), <<>>}. - -%% @doc Decode a part of the URL and return string() -path_decode(Path) -> path_decode(Path, <<>>). - -path_decode(<<$%, Hi, Lo, Tail/binary>>, Acc) -> - Hex = hex_to_integer([Hi, Lo]), - if Hex == 0 -> exit(badurl); - true -> ok - end, - path_decode(Tail, <<Acc/binary, Hex>>); -path_decode(<<H, T/binary>>, Acc) when H /= 0 -> - path_decode(T, <<Acc/binary, H>>); -path_decode(<<>>, Acc) -> Acc. - -path_norm_reverse(<<"/", T/binary>>) -> start_dir(0, <<"/">>, T); -path_norm_reverse(T) -> start_dir(0, <<"">>, T). - -start_dir(N, Path, <<"..">>) -> rest_dir(N, Path, <<"">>); -start_dir(N, Path, <<"/", T/binary>>) -> start_dir(N, Path, T); -start_dir(N, Path, <<"./", T/binary>>) -> start_dir(N, Path, T); -start_dir(N, Path, <<"../", T/binary>>) -> - start_dir(N + 1, Path, T); -start_dir(N, Path, T) -> rest_dir(N, Path, T). - -rest_dir(_N, Path, <<>>) -> - case Path of - <<>> -> <<"/">>; - _ -> Path - end; -rest_dir(0, Path, <<$/, T/binary>>) -> - start_dir(0, <<$/, Path/binary>>, T); -rest_dir(N, Path, <<$/, T/binary>>) -> - start_dir(N - 1, Path, T); -rest_dir(0, Path, <<H, T/binary>>) -> - rest_dir(0, <<H, Path/binary>>, T); -rest_dir(N, Path, <<_H, T/binary>>) -> rest_dir(N, Path, T). - -%% hex_to_integer - -hex_to_integer(Hex) -> - case catch list_to_integer(Hex, 16) of - {'EXIT', _} -> old_hex_to_integer(Hex); - X -> X - end. - -old_hex_to_integer(Hex) -> - DEHEX = fun (H) when H >= $a, H =< $f -> H - $a + 10; - (H) when H >= $A, H =< $F -> H - $A + 10; - (H) when H >= $0, H =< $9 -> H - $0 - end, - lists:foldl(fun (E, Acc) -> Acc * 16 + DEHEX(E) end, 0, - Hex). - -code_to_phrase(100) -> <<"Continue">>; -code_to_phrase(101) -> <<"Switching Protocols ">>; -code_to_phrase(200) -> <<"OK">>; -code_to_phrase(201) -> <<"Created">>; -code_to_phrase(202) -> <<"Accepted">>; -code_to_phrase(203) -> - <<"Non-Authoritative Information">>; -code_to_phrase(204) -> <<"No Content">>; -code_to_phrase(205) -> <<"Reset Content">>; -code_to_phrase(206) -> <<"Partial Content">>; -code_to_phrase(300) -> <<"Multiple Choices">>; -code_to_phrase(301) -> <<"Moved Permanently">>; -code_to_phrase(302) -> <<"Found">>; -code_to_phrase(303) -> <<"See Other">>; -code_to_phrase(304) -> <<"Not Modified">>; -code_to_phrase(305) -> <<"Use Proxy">>; -code_to_phrase(306) -> <<"(Unused)">>; -code_to_phrase(307) -> <<"Temporary Redirect">>; -code_to_phrase(400) -> <<"Bad Request">>; -code_to_phrase(401) -> <<"Unauthorized">>; -code_to_phrase(402) -> <<"Payment Required">>; -code_to_phrase(403) -> <<"Forbidden">>; -code_to_phrase(404) -> <<"Not Found">>; -code_to_phrase(405) -> <<"Method Not Allowed">>; -code_to_phrase(406) -> <<"Not Acceptable">>; -code_to_phrase(407) -> - <<"Proxy Authentication Required">>; -code_to_phrase(408) -> <<"Request Timeout">>; -code_to_phrase(409) -> <<"Conflict">>; -code_to_phrase(410) -> <<"Gone">>; -code_to_phrase(411) -> <<"Length Required">>; -code_to_phrase(412) -> <<"Precondition Failed">>; -code_to_phrase(413) -> <<"Request Entity Too Large">>; -code_to_phrase(414) -> <<"Request-URI Too Long">>; -code_to_phrase(415) -> <<"Unsupported Media Type">>; -code_to_phrase(416) -> - <<"Requested Range Not Satisfiable">>; -code_to_phrase(417) -> <<"Expectation Failed">>; -code_to_phrase(500) -> <<"Internal Server Error">>; -code_to_phrase(501) -> <<"Not Implemented">>; -code_to_phrase(502) -> <<"Bad Gateway">>; -code_to_phrase(503) -> <<"Service Unavailable">>; -code_to_phrase(504) -> <<"Gateway Timeout">>; -code_to_phrase(505) -> <<"HTTP Version Not Supported">>. - -parse_auth(<<"Basic ", Auth64/binary>>) -> - Auth = jlib:decode_base64(Auth64), - %% Auth should be a string with the format: user@server:password - %% Note that password can contain additional characters '@' and ':' - case str:chr(Auth, $:) of - 0 -> - undefined; - Pos -> - {User, <<$:, Pass/binary>>} = erlang:split_binary(Auth, Pos-1), - {User, Pass} - end; -parse_auth(<<_/binary>>) -> undefined. - -parse_urlencoded(S) -> - parse_urlencoded(S, nokey, <<>>, key). - -parse_urlencoded(<<$%, Hi, Lo, Tail/binary>>, Last, Cur, - State) -> - Hex = hex_to_integer([Hi, Lo]), - parse_urlencoded(Tail, Last, <<Cur/binary, Hex>>, State); -parse_urlencoded(<<$&, Tail/binary>>, _Last, Cur, key) -> - [{Cur, <<"">>} | parse_urlencoded(Tail, - nokey, <<>>, - key)]; %% cont keymode -parse_urlencoded(<<$&, Tail/binary>>, Last, Cur, value) -> - V = {Last, Cur}, - [V | parse_urlencoded(Tail, nokey, <<>>, key)]; -parse_urlencoded(<<$+, Tail/binary>>, Last, Cur, State) -> - parse_urlencoded(Tail, Last, <<Cur/binary, $\s>>, State); -parse_urlencoded(<<$=, Tail/binary>>, _Last, Cur, key) -> - parse_urlencoded(Tail, Cur, <<>>, - value); %% change mode -parse_urlencoded(<<H, Tail/binary>>, Last, Cur, State) -> - parse_urlencoded(Tail, Last, <<Cur/binary, H>>, State); -parse_urlencoded(<<>>, Last, Cur, _State) -> - [{Last, Cur}]; -parse_urlencoded(undefined, _, _, _) -> []. - - -url_encode(A) -> - url_encode(A, <<>>). - -url_encode(<<H:8, T/binary>>, Acc) when - (H >= $a andalso H =< $z) orelse - (H >= $A andalso H =< $Z) orelse - (H >= $0 andalso H =< $9) orelse - H == $_ orelse - H == $. orelse - H == $- orelse - H == $/ orelse - H == $: -> - url_encode(T, <<Acc/binary, H>>); -url_encode(<<H:8, T/binary>>, Acc) -> - case integer_to_hex(H) of - [X, Y] -> url_encode(T, <<Acc/binary, $%, X, Y>>); - [X] -> url_encode(T, <<Acc/binary, $%, $0, X>>) - end; -url_encode(<<>>, Acc) -> - Acc. - - -integer_to_hex(I) -> - case catch erlang:integer_to_list(I, 16) of - {'EXIT', _} -> old_integer_to_hex(I); - Int -> Int - end. - -old_integer_to_hex(I) when I < 10 -> integer_to_list(I); -old_integer_to_hex(I) when I < 16 -> [I - 10 + $A]; -old_integer_to_hex(I) when I >= 16 -> - N = trunc(I / 16), - old_integer_to_hex(N) ++ old_integer_to_hex(I rem 16). - -%% strip_spaces(String, left) -> -%% drop_spaces(String); diff --git a/src/web/ejabberd_http.hrl b/src/web/ejabberd_http.hrl deleted file mode 100644 index 542dc0cdb..000000000 --- a/src/web/ejabberd_http.hrl +++ /dev/null @@ -1,36 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% -%%% ejabberd, Copyright (C) 2002-2013 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., 59 Temple Place, Suite 330, Boston, MA -%%% 02111-1307 USA -%%% -%%%---------------------------------------------------------------------- - --record(request, - {method, % :: method(), - path = [] :: [binary()], - q = [] :: [{binary() | nokey, binary()}], - us = {<<>>, <<>>} :: {binary(), binary()}, - auth :: {binary(), binary()} | - {auth_jid, {binary(), binary()}, jlib:jid()}, - lang = <<"">> :: binary(), - data = <<"">> :: binary(), - ip :: {inet:ip_address(), inet:port_number()}, - host = <<"">> :: binary(), - port = 5280 :: inet:port_number(), - tp = http, % :: protocol(), - headers = [] :: [{atom() | binary(), binary()}]}). - diff --git a/src/web/ejabberd_http_bind.erl b/src/web/ejabberd_http_bind.erl deleted file mode 100644 index 913291672..000000000 --- a/src/web/ejabberd_http_bind.erl +++ /dev/null @@ -1,1235 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : ejabberd_http_bind.erl -%%% Author : Stefan Strigler <steve@zeank.in-berlin.de> -%%% Purpose : Implements XMPP over BOSH (XEP-0206) (formerly known as -%%% HTTP Binding) -%%% Created : 21 Sep 2005 by Stefan Strigler <steve@zeank.in-berlin.de> -%%% Modified: may 2009 by Mickael Remond, Alexey Schepin -%%% Id : $Id: ejabberd_http_bind.erl 953 2009-05-07 10:40:40Z alexey $ -%%%---------------------------------------------------------------------- - --module(ejabberd_http_bind). - --behaviour(gen_fsm). - -%% External exports --export([start_link/3, - init/1, - handle_event/3, - handle_sync_event/4, - code_change/4, - handle_info/3, - terminate/3, - send/2, - send_xml/2, - sockname/1, - peername/1, - setopts/2, - controlling_process/2, - become_controller/2, - custom_receiver/1, - reset_stream/1, - change_shaper/2, - monitor/1, - close/1, - start/4, - handle_session_start/8, - handle_http_put/7, - http_put/7, - http_get/2, - prepare_response/4, - process_request/2]). - --include("ejabberd.hrl"). - --include("jlib.hrl"). - --include("ejabberd_http.hrl"). - --include("http_bind.hrl"). - --record(http_bind, - {id, pid, to, hold, wait, process_delay, version}). - --define(NULL_PEER, {{0, 0, 0, 0}, 0}). - -%% http binding request --record(hbr, {rid, - key, - out}). - --record(state, {id, - rid = none, - key, - socket, - output = "", - input = queue:new(), - waiting_input = false, - shaper_state, - shaper_timer, - last_receiver, - last_poll, - http_receiver, - out_of_order_receiver = false, - wait_timer, - ctime = 0, - timer, - pause=0, - unprocessed_req_list = [], % list of request that have been delayed for proper reordering: {Request, PID} - req_list = [], % list of requests (cache) - max_inactivity, - max_pause, - ip = ?NULL_PEER - }). - -%% Internal request format: --record(http_put, {rid, - attrs, - payload, - payload_size, - hold, - stream, - ip}). - -%%-define(DBGFSM, true). --ifdef(DBGFSM). - --define(FSMOPTS, [{debug, [trace]}]). - --else. - --define(FSMOPTS, []). - --endif. - -%% Wait 100ms before continue processing, to allow the client provide more related stanzas. --define(BOSH_VERSION, <<"1.8">>). - --define(NS_CLIENT, <<"jabber:client">>). - --define(NS_BOSH, <<"urn:xmpp:xbosh">>). - --define(NS_HTTP_BIND, - <<"http://jabber.org/protocol/httpbind">>). - --define(MAX_REQUESTS, 2). - --define(MIN_POLLING, 2000000). - --define(MAX_WAIT, 3600). - --define(MAX_INACTIVITY, 30000). - --define(MAX_PAUSE, 120). - --define(PROCESS_DELAY_DEFAULT, 100). - --define(PROCESS_DELAY_MIN, 0). - --define(PROCESS_DELAY_MAX, 1000). - -%% Line copied from mod_http_bind.erl --define(PROCNAME_MHB, ejabberd_mod_http_bind). - -%%%---------------------------------------------------------------------- -%%% API -%%%---------------------------------------------------------------------- -%% TODO: If compile with no supervisor option, start the session without -%% supervisor -start(XMPPDomain, Sid, Key, IP) -> - ?DEBUG("Starting session", []), - SupervisorProc = gen_mod:get_module_proc(XMPPDomain, ?PROCNAME_MHB), - case catch supervisor:start_child(SupervisorProc, [Sid, Key, IP]) of - {ok, Pid} -> {ok, Pid}; - _ -> check_bind_module(XMPPDomain), - {error, "Cannot start HTTP bind session"} - end. - -start_link(Sid, Key, IP) -> - gen_fsm:start_link(?MODULE, [Sid, Key, IP], ?FSMOPTS). - -send({http_bind, FsmRef, _IP}, Packet) -> - gen_fsm:sync_send_all_state_event(FsmRef, - {send, Packet}). - -send_xml({http_bind, FsmRef, _IP}, Packet) -> - gen_fsm:sync_send_all_state_event(FsmRef, - {send_xml, Packet}). - -setopts({http_bind, FsmRef, _IP}, Opts) -> - case lists:member({active, once}, Opts) of - true -> - gen_fsm:send_all_state_event(FsmRef, {activate, self()}); - _ -> - ok - end. - -controlling_process(_Socket, _Pid) -> ok. - -custom_receiver({http_bind, FsmRef, _IP}) -> - {receiver, ?MODULE, FsmRef}. - -become_controller(FsmRef, C2SPid) -> - gen_fsm:send_all_state_event(FsmRef, - {become_controller, C2SPid}). - -reset_stream({http_bind, _FsmRef, _IP}) -> - ok. - -change_shaper({http_bind, FsmRef, _IP}, Shaper) -> - gen_fsm:send_all_state_event(FsmRef, - {change_shaper, Shaper}). - -monitor({http_bind, FsmRef, _IP}) -> - erlang:monitor(process, FsmRef). - -close({http_bind, FsmRef, _IP}) -> - catch gen_fsm:sync_send_all_state_event(FsmRef, - {stop, close}). - -sockname(_Socket) -> {ok, ?NULL_PEER}. - -peername({http_bind, _FsmRef, IP}) -> {ok, IP}. - - -%% Entry point for data coming from client through ejabberd HTTP server: -process_request(Data, IP) -> - Opts1 = ejabberd_c2s_config:get_c2s_limits(), - Opts = [{xml_socket, true} | Opts1], - MaxStanzaSize = case lists:keysearch(max_stanza_size, 1, - Opts) - of - {value, {_, Size}} -> Size; - _ -> infinity - end, - PayloadSize = iolist_size(Data), - case catch parse_request(Data, PayloadSize, - MaxStanzaSize) - of - %% No existing session: - {ok, {<<"">>, Rid, Attrs, Payload}} -> - case xml:get_attr_s(<<"to">>, Attrs) of - <<"">> -> - ?DEBUG("Session not created (Improper addressing)", []), - {200, ?HEADER, - <<"<body type='terminate' condition='improper-ad" - "dressing' xmlns='", - (?NS_HTTP_BIND)/binary, "'/>">>}; - XmppDomain -> - Sid = sha:sha(term_to_binary({now(), make_ref()})), - case start(XmppDomain, Sid, <<"">>, IP) of - {error, _} -> - {500, ?HEADER, - <<"<body type='terminate' condition='internal-se" - "rver-error' xmlns='", - (?NS_HTTP_BIND)/binary, - "'>Internal Server Error</body>">>}; - {ok, Pid} -> - handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs, - Payload, PayloadSize, IP) - end - end; - %% Existing session - {ok, {Sid, Rid, Attrs, Payload1}} -> - StreamStart = case xml:get_attr_s(<<"xmpp:restart">>, - Attrs) - of - <<"true">> -> true; - _ -> false - end, - Payload2 = case xml:get_attr_s(<<"type">>, Attrs) of - <<"terminate">> -> - Payload1 ++ [{xmlstreamend, <<"stream:stream">>}]; - _ -> Payload1 - end, - handle_http_put(Sid, Rid, Attrs, Payload2, PayloadSize, - StreamStart, IP); - {size_limit, Sid} -> - case mnesia:dirty_read({http_bind, Sid}) of - {error, _} -> {404, ?HEADER, <<"">>}; - {ok, #http_bind{pid = FsmRef}} -> - gen_fsm:sync_send_all_state_event(FsmRef, - {stop, close}), - {200, ?HEADER, - <<"<body type='terminate' condition='undefined-c" - "ondition' xmlns='", - (?NS_HTTP_BIND)/binary, "'>Request Too Large</body>">>} - end; - _ -> - ?DEBUG("Received bad request: ~p", [Data]), - {400, ?HEADER, <<"">>} - end. - -handle_session_start(Pid, XmppDomain, Sid, Rid, Attrs, - Payload, PayloadSize, IP) -> - ?DEBUG("got pid: ~p", [Pid]), - Wait = case str:to_integer(xml:get_attr_s(<<"wait">>, - Attrs)) - of - {error, _} -> ?MAX_WAIT; - {CWait, _} -> - if CWait > (?MAX_WAIT) -> ?MAX_WAIT; - true -> CWait - end - end, - Hold = case str:to_integer(xml:get_attr_s(<<"hold">>, - Attrs)) - of - {error, _} -> (?MAX_REQUESTS) - 1; - {CHold, _} -> - if CHold > (?MAX_REQUESTS) - 1 -> (?MAX_REQUESTS) - 1; - true -> CHold - end - end, - Pdelay = case - str:to_integer(xml:get_attr_s(<<"process-delay">>, - Attrs)) - of - {error, _} -> ?PROCESS_DELAY_DEFAULT; - {CPdelay, _} - when ((?PROCESS_DELAY_MIN) =< CPdelay) and - (CPdelay =< (?PROCESS_DELAY_MAX)) -> - CPdelay; - {CPdelay, _} -> - lists:max([lists:min([CPdelay, ?PROCESS_DELAY_MAX]), - ?PROCESS_DELAY_MIN]) - end, - Version = case catch - list_to_float(binary_to_list(xml:get_attr_s(<<"ver">>, Attrs))) - of - {'EXIT', _} -> 0.0; - V -> V - end, - XmppVersion = xml:get_attr_s(<<"xmpp:version">>, Attrs), - ?DEBUG("Create session: ~p", [Sid]), - mnesia:dirty_write( - #http_bind{id = Sid, - pid = Pid, - to = {XmppDomain, - XmppVersion}, - hold = Hold, - wait = Wait, - process_delay = Pdelay, - version = Version - }), - handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize, true, IP). - -%%%---------------------------------------------------------------------- -%%% Callback functions from gen_fsm -%%%---------------------------------------------------------------------- - -%%---------------------------------------------------------------------- -%% Func: init/1 -%% Returns: {ok, StateName, StateData} | -%% {ok, StateName, StateData, Timeout} | -%% ignore | -%% {stop, StopReason} -%%---------------------------------------------------------------------- -init([Sid, Key, IP]) -> - ?DEBUG("started: ~p", [{Sid, Key, IP}]), - Opts1 = ejabberd_c2s_config:get_c2s_limits(), - Opts = [{xml_socket, true} | Opts1], - Shaper = none, - ShaperState = shaper:new(Shaper), - Socket = {http_bind, self(), IP}, - ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket, Opts), - Timer = erlang:start_timer(?MAX_INACTIVITY, self(), []), - {ok, loop, #state{id = Sid, - key = Key, - socket = Socket, - shaper_state = ShaperState, - max_inactivity = ?MAX_INACTIVITY, - max_pause = ?MAX_PAUSE, - timer = Timer}}. - -%%---------------------------------------------------------------------- -%% Func: handle_event/3 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} -%%---------------------------------------------------------------------- -handle_event({become_controller, C2SPid}, StateName, StateData) -> - case StateData#state.input of - cancel -> - {next_state, StateName, - StateData#state{waiting_input = C2SPid}}; - Input -> - lists:foreach(fun (Event) -> C2SPid ! Event end, - queue:to_list(Input)), - {next_state, StateName, - StateData#state{input = queue:new(), - waiting_input = C2SPid}} - end; -handle_event({change_shaper, Shaper}, StateName, - StateData) -> - NewShaperState = shaper:new(Shaper), - {next_state, StateName, - StateData#state{shaper_state = NewShaperState}}; -handle_event(_Event, StateName, StateData) -> - {next_state, StateName, StateData}. - -%%---------------------------------------------------------------------- -%% Func: handle_sync_event/4 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {reply, Reply, NextStateName, NextStateData} | -%% {reply, Reply, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} | -%% {stop, Reason, Reply, NewStateData} -%%---------------------------------------------------------------------- -handle_sync_event({send_xml, Packet}, _From, StateName, - #state{http_receiver = undefined} = StateData) -> - Output = [Packet | StateData#state.output], - Reply = ok, - {reply, Reply, StateName, - StateData#state{output = Output}}; -handle_sync_event({send_xml, Packet}, _From, StateName, - #state{out_of_order_receiver = true} = StateData) -> - Output = [Packet | StateData#state.output], - Reply = ok, - {reply, Reply, StateName, - StateData#state{output = Output}}; -handle_sync_event({send_xml, Packet}, _From, StateName, - StateData) -> - Output = [Packet | StateData#state.output], - cancel_timer(StateData#state.timer), - Timer = set_inactivity_timer(StateData#state.pause, - StateData#state.max_inactivity), - HTTPReply = {ok, Output}, - gen_fsm:reply(StateData#state.http_receiver, HTTPReply), - cancel_timer(StateData#state.wait_timer), - Rid = StateData#state.rid, - ReqList = [#hbr{rid = Rid, key = StateData#state.key, - out = Output} - | [El - || El <- StateData#state.req_list, El#hbr.rid /= Rid]], - Reply = ok, - {reply, Reply, StateName, - StateData#state{output = [], http_receiver = undefined, - req_list = ReqList, wait_timer = undefined, - timer = Timer}}; - -handle_sync_event({stop,close}, _From, _StateName, StateData) -> - Reply = ok, - {stop, normal, Reply, StateData}; -handle_sync_event({stop,stream_closed}, _From, _StateName, StateData) -> - Reply = ok, - {stop, normal, Reply, StateData}; -handle_sync_event({stop,Reason}, _From, _StateName, StateData) -> - ?DEBUG("Closing bind session ~p - Reason: ~p", [StateData#state.id, Reason]), - Reply = ok, - {stop, normal, Reply, StateData}; -%% HTTP PUT: Receive packets from the client -handle_sync_event(#http_put{rid = Rid}, _From, - StateName, StateData) - when StateData#state.shaper_timer /= undefined -> - Pause = case - erlang:read_timer(StateData#state.shaper_timer) - of - false -> 0; - P -> P - end, - Reply = {wait, Pause}, - ?DEBUG("Shaper timer for RID ~p: ~p", [Rid, Reply]), - {reply, Reply, StateName, StateData}; -handle_sync_event(#http_put{payload_size = - PayloadSize} = - Request, - _From, StateName, StateData) -> - ?DEBUG("New request: ~p", [Request]), - {NewShaperState, NewShaperTimer} = - update_shaper(StateData#state.shaper_state, - PayloadSize), - handle_http_put_event(Request, StateName, - StateData#state{shaper_state = NewShaperState, - shaper_timer = NewShaperTimer}); -%% HTTP GET: send packets to the client -handle_sync_event({http_get, Rid, Wait, Hold}, From, StateName, StateData) -> - %% setup timer - TNow = tnow(), - if - (Hold > 0) and - ((StateData#state.output == []) or (StateData#state.rid < Rid)) and - ((TNow - StateData#state.ctime) < (Wait*1000*1000)) and - (StateData#state.rid =< Rid) and - (StateData#state.pause == 0) -> - send_receiver_reply(StateData#state.http_receiver, {ok, empty}), - cancel_timer(StateData#state.wait_timer), - WaitTimer = erlang:start_timer(Wait * 1000, self(), []), - %% MR: Not sure we should cancel the state timer here. - cancel_timer(StateData#state.timer), - {next_state, StateName, StateData#state{ - http_receiver = From, - out_of_order_receiver = StateData#state.rid < Rid, - wait_timer = WaitTimer, - timer = undefined}}; - true -> - cancel_timer(StateData#state.timer), - Reply = {ok, StateData#state.output}, - %% save request - ReqList = [#hbr{rid = Rid, - key = StateData#state.key, - out = StateData#state.output - } | - [El || El <- StateData#state.req_list, - El#hbr.rid /= Rid ] - ], - if - (StateData#state.http_receiver /= undefined) and - StateData#state.out_of_order_receiver -> - {reply, Reply, StateName, StateData#state{ - output = [], - timer = undefined, - req_list = ReqList, - out_of_order_receiver = false}}; - true -> - send_receiver_reply(StateData#state.http_receiver, {ok, empty}), - cancel_timer(StateData#state.wait_timer), - Timer = set_inactivity_timer(StateData#state.pause, - StateData#state.max_inactivity), - {reply, Reply, StateName, - StateData#state{output = [], - http_receiver = undefined, - wait_timer = undefined, - timer = Timer, - req_list = ReqList}} - end - end; -handle_sync_event(peername, _From, StateName, - StateData) -> - Reply = {ok, StateData#state.ip}, - {reply, Reply, StateName, StateData}; -handle_sync_event(_Event, _From, StateName, - StateData) -> - Reply = ok, {reply, Reply, StateName, StateData}. - -code_change(_OldVsn, StateName, StateData, _Extra) -> - {ok, StateName, StateData}. - -%%---------------------------------------------------------------------- -%% Func: handle_info/3 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} -%%---------------------------------------------------------------------- -%% We reached the max_inactivity timeout: -handle_info({timeout, Timer, _}, _StateName, - #state{id = SID, timer = Timer} = StateData) -> - ?INFO_MSG("Session timeout. Closing the HTTP bind " - "session: ~p", - [SID]), - {stop, normal, StateData}; -handle_info({timeout, WaitTimer, _}, StateName, - #state{wait_timer = WaitTimer} = StateData) -> - if StateData#state.http_receiver /= undefined -> - cancel_timer(StateData#state.timer), - Timer = set_inactivity_timer(StateData#state.pause, - StateData#state.max_inactivity), - gen_fsm:reply(StateData#state.http_receiver, - {ok, empty}), - Rid = StateData#state.rid, - ReqList = [#hbr{rid = Rid, key = StateData#state.key, - out = []} - | [El - || El <- StateData#state.req_list, El#hbr.rid /= Rid]], - {next_state, StateName, - StateData#state{http_receiver = undefined, - req_list = ReqList, wait_timer = undefined, - timer = Timer}}; - true -> {next_state, StateName, StateData} - end; -handle_info({timeout, ShaperTimer, _}, StateName, - #state{shaper_timer = ShaperTimer} = StateData) -> - {next_state, StateName, StateData#state{shaper_timer = undefined}}; - -handle_info(_, StateName, StateData) -> - {next_state, StateName, StateData}. - -%%---------------------------------------------------------------------- -%% Func: terminate/3 -%% Purpose: Shutdown the fsm -%% Returns: any -%%---------------------------------------------------------------------- -terminate(_Reason, _StateName, StateData) -> - ?DEBUG("terminate: Deleting session ~s", - [StateData#state.id]), - mnesia:dirty_delete({http_bind, StateData#state.id}), - send_receiver_reply(StateData#state.http_receiver, - {ok, terminate}), - case StateData#state.waiting_input of - false -> ok; - C2SPid -> gen_fsm:send_event(C2SPid, closed) - end, - ok. - -%%%---------------------------------------------------------------------- -%%% Internal functions -%%%---------------------------------------------------------------------- - -%% PUT / Get processing: -handle_http_put_event(#http_put{rid = Rid, - attrs = Attrs, hold = Hold} = - Request, - StateName, StateData) -> - ?DEBUG("New request: ~p", [Request]), - RidAllow = rid_allow(StateData#state.rid, Rid, Attrs, - Hold, StateData#state.max_pause), - case RidAllow of - buffer -> - ?DEBUG("Buffered request: ~p", [Request]), - PendingRequests = StateData#state.unprocessed_req_list, - Requests = lists:keydelete(Rid, 2, PendingRequests), - ReqList = [#hbr{rid = Rid, key = StateData#state.key, - out = []} - | [El - || El <- StateData#state.req_list, - El#hbr.rid > Rid - 1 - Hold]], - ?DEBUG("reqlist: ~p", [ReqList]), - UnprocessedReqList = [Request | Requests], - cancel_timer(StateData#state.timer), - Timer = set_inactivity_timer(0, - StateData#state.max_inactivity), - {reply, ok, StateName, - StateData#state{unprocessed_req_list = - UnprocessedReqList, - req_list = ReqList, timer = Timer}, - hibernate}; - _ -> - process_http_put(Request, StateName, StateData, - RidAllow) - end. - -process_http_put(#http_put{rid = Rid, attrs = Attrs, - payload = Payload, hold = Hold, stream = StreamTo, - ip = IP} = - Request, - StateName, StateData, RidAllow) -> - ?DEBUG("Actually processing request: ~p", [Request]), - Key = xml:get_attr_s(<<"key">>, Attrs), - NewKey = xml:get_attr_s(<<"newkey">>, Attrs), - KeyAllow = case RidAllow of - repeat -> true; - false -> false; - {true, _} -> - case StateData#state.key of - <<"">> -> true; - OldKey -> - NextKey = sha:sha(Key), - ?DEBUG("Key/OldKey/NextKey: ~s/~s/~s", - [Key, OldKey, NextKey]), - if OldKey == NextKey -> true; - true -> ?DEBUG("wrong key: ~s", [Key]), false - end - end - end, - TNow = tnow(), - LastPoll = if Payload == [] -> TNow; - true -> 0 - end, - if (Payload == []) and (Hold == 0) and - (TNow - StateData#state.last_poll < (?MIN_POLLING)) -> - Reply = {error, polling_too_frequently}, - {reply, Reply, StateName, StateData}; - KeyAllow -> - case RidAllow of - false -> - Reply = {error, not_exists}, - {reply, Reply, StateName, StateData}; - repeat -> - ?DEBUG("REPEATING ~p", [Rid]), - case [El#hbr.out - || El <- StateData#state.req_list, El#hbr.rid == Rid] - of - [] -> {error, not_exists}; - [Out | _XS] -> - if (Rid == StateData#state.rid) and - (StateData#state.http_receiver /= undefined) -> - {reply, ok, StateName, StateData}; - true -> - Reply = {repeat, lists:reverse(Out)}, - {reply, Reply, StateName, - StateData#state{last_poll = LastPoll}} - end - end; - {true, Pause} -> - SaveKey = if NewKey == <<"">> -> Key; - true -> NewKey - end, - ?DEBUG(" -- SaveKey: ~s~n", [SaveKey]), - ReqList1 = [El - || El <- StateData#state.req_list, - El#hbr.rid > Rid - 1 - Hold], - ReqList = case lists:keymember(Rid, #hbr.rid, ReqList1) - of - true -> ReqList1; - false -> - [#hbr{rid = Rid, key = StateData#state.key, - out = []} - | ReqList1] - end, - ?DEBUG("reqlist: ~p", [ReqList]), - cancel_timer(StateData#state.timer), - Timer = set_inactivity_timer(Pause, - StateData#state.max_inactivity), - case StateData#state.waiting_input of - false -> - Input = lists:foldl(fun queue:in/2, - StateData#state.input, Payload), - Reply = ok, - process_buffered_request(Reply, StateName, - StateData#state{input = Input, - rid = Rid, - key = SaveKey, - ctime = TNow, - timer = Timer, - pause = Pause, - last_poll = - LastPoll, - req_list = - ReqList, - ip = IP}); - C2SPid -> - case StreamTo of - {To, <<"">>} -> - gen_fsm:send_event(C2SPid, - {xmlstreamstart, - <<"stream:stream">>, - [{<<"to">>, To}, - {<<"xmlns">>, ?NS_CLIENT}, - {<<"xmlns:stream">>, - ?NS_STREAM}]}); - {To, Version} -> - gen_fsm:send_event(C2SPid, - {xmlstreamstart, - <<"stream:stream">>, - [{<<"to">>, To}, - {<<"xmlns">>, ?NS_CLIENT}, - {<<"version">>, Version}, - {<<"xmlns:stream">>, - ?NS_STREAM}]}); - _ -> ok - end, - MaxInactivity = get_max_inactivity(StreamTo, - StateData#state.max_inactivity), - MaxPause = get_max_inactivity(StreamTo, - StateData#state.max_pause), - ?DEBUG("really sending now: ~p", [Payload]), - lists:foreach(fun ({xmlstreamend, End}) -> - gen_fsm:send_event(C2SPid, - {xmlstreamend, - End}); - (El) -> - gen_fsm:send_event(C2SPid, - {xmlstreamelement, - El}) - end, - Payload), - Reply = ok, - process_buffered_request(Reply, StateName, - StateData#state{input = - queue:new(), - rid = Rid, - key = SaveKey, - ctime = TNow, - timer = Timer, - pause = Pause, - last_poll = - LastPoll, - req_list = - ReqList, - max_inactivity = - MaxInactivity, - max_pause = - MaxPause, - ip = IP}) - end - end; - true -> - Reply = {error, bad_key}, - {reply, Reply, StateName, StateData} - end. - -process_buffered_request(Reply, StateName, StateData) -> - Rid = StateData#state.rid, - Requests = StateData#state.unprocessed_req_list, - case lists:keysearch(Rid + 1, 2, Requests) of - {value, Request} -> - ?DEBUG("Processing buffered request: ~p", [Request]), - NewRequests = lists:keydelete(Rid + 1, 2, Requests), - handle_http_put_event(Request, StateName, - StateData#state{unprocessed_req_list = - NewRequests}); - _ -> {reply, Reply, StateName, StateData, hibernate} - end. - -handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize, - StreamStart, IP) -> - case http_put(Sid, Rid, Attrs, Payload, PayloadSize, - StreamStart, IP) - of - {error, not_exists} -> - ?DEBUG("no session associated with sid: ~p", [Sid]), - {404, ?HEADER, <<"">>}; - {{error, Reason}, Sess} -> - ?DEBUG("Error on HTTP put. Reason: ~p", [Reason]), - handle_http_put_error(Reason, Sess); - {{repeat, OutPacket}, Sess} -> - ?DEBUG("http_put said 'repeat!' ...~nOutPacket: ~p", - [OutPacket]), - send_outpacket(Sess, OutPacket); - {{wait, Pause}, _Sess} -> - ?DEBUG("Trafic Shaper: Delaying request ~p", [Rid]), - timer:sleep(Pause), - handle_http_put(Sid, Rid, Attrs, Payload, PayloadSize, - StreamStart, IP); - {ok, Sess} -> - prepare_response(Sess, Rid, [], StreamStart) - end. - -http_put(Sid, Rid, Attrs, Payload, PayloadSize, - StreamStart, IP) -> - ?DEBUG("Looking for session: ~p", [Sid]), - case mnesia:dirty_read({http_bind, Sid}) of - [] -> - {error, not_exists}; - [#http_bind{pid = FsmRef, hold=Hold, to={To, StreamVersion}}=Sess] -> - NewStream = - case StreamStart of - true -> - {To, StreamVersion}; - _ -> - <<"">> - end, - {gen_fsm:sync_send_all_state_event( - FsmRef, #http_put{rid = Rid, attrs = Attrs, payload = Payload, - payload_size = PayloadSize, hold = Hold, - stream = NewStream, ip = IP}, 30000), Sess} - end. - -handle_http_put_error(Reason, - #http_bind{pid = FsmRef, version = Version}) - when Version >= 0 -> - gen_fsm:sync_send_all_state_event(FsmRef, - {stop, {put_error, Reason}}), - case Reason of - not_exists -> - {200, ?HEADER, - xml:element_to_binary(#xmlel{name = <<"body">>, - attrs = - [{<<"xmlns">>, ?NS_HTTP_BIND}, - {<<"type">>, <<"terminate">>}, - {<<"condition">>, - <<"item-not-found">>}], - children = []})}; - bad_key -> - {200, ?HEADER, - xml:element_to_binary(#xmlel{name = <<"body">>, - attrs = - [{<<"xmlns">>, ?NS_HTTP_BIND}, - {<<"type">>, <<"terminate">>}, - {<<"condition">>, - <<"item-not-found">>}], - children = []})}; - polling_too_frequently -> - {200, ?HEADER, - xml:element_to_binary(#xmlel{name = <<"body">>, - attrs = - [{<<"xmlns">>, ?NS_HTTP_BIND}, - {<<"type">>, <<"terminate">>}, - {<<"condition">>, - <<"policy-violation">>}], - children = []})} - end; -handle_http_put_error(Reason, - #http_bind{pid = FsmRef}) -> - gen_fsm:sync_send_all_state_event(FsmRef, - {stop, {put_error_no_version, Reason}}), - case Reason of - not_exists -> %% bad rid - ?DEBUG("Closing HTTP bind session (Bad rid).", []), - {404, ?HEADER, <<"">>}; - bad_key -> - ?DEBUG("Closing HTTP bind session (Bad key).", []), - {404, ?HEADER, <<"">>}; - polling_too_frequently -> - ?DEBUG("Closing HTTP bind session (User polling " - "too frequently).", - []), - {403, ?HEADER, <<"">>} - end. - -%% Control RID ordering -rid_allow(none, _NewRid, _Attrs, _Hold, _MaxPause) -> - {true, 0}; -rid_allow(OldRid, NewRid, Attrs, Hold, MaxPause) -> - ?DEBUG("Previous rid / New rid: ~p/~p", - [OldRid, NewRid]), - if - %% We did not miss any packet, we can process it immediately: - NewRid == OldRid + 1 -> - case catch - jlib:binary_to_integer(xml:get_attr_s(<<"pause">>, - Attrs)) - of - {'EXIT', _} -> {true, 0}; - Pause1 when Pause1 =< MaxPause -> - ?DEBUG("got pause: ~p", [Pause1]), {true, Pause1}; - _ -> {true, 0} - end; - %% We have missed packets, we need to cached it to process it later on: - (OldRid < NewRid) and (NewRid =< OldRid + Hold + 1) -> - buffer; - (NewRid =< OldRid) and (NewRid > OldRid - Hold - 1) -> - repeat; - true -> false - end. - -update_shaper(ShaperState, PayloadSize) -> - {NewShaperState, Pause} = shaper:update(ShaperState, - PayloadSize), - if Pause > 0 -> - ShaperTimer = erlang:start_timer(Pause, self(), - activate), - {NewShaperState, ShaperTimer}; - true -> {NewShaperState, undefined} - end. - -prepare_response(Sess, Rid, OutputEls, StreamStart) -> - receive after Sess#http_bind.process_delay -> ok end, - case catch http_get(Sess, Rid) of - {ok, cancel} -> - {200, ?HEADER, - <<"<body type='error' xmlns='", (?NS_HTTP_BIND)/binary, - "'/>">>}; - {ok, empty} -> - {200, ?HEADER, - <<"<body xmlns='", (?NS_HTTP_BIND)/binary, "'/>">>}; - {ok, terminate} -> - {200, ?HEADER, - <<"<body type='terminate' xmlns='", - (?NS_HTTP_BIND)/binary, "'/>">>}; - {ok, ROutPacket} -> - OutPacket = lists:reverse(ROutPacket), - ?DEBUG("OutPacket: ~p", [OutputEls ++ OutPacket]), - prepare_outpacket_response(Sess, Rid, - OutputEls ++ OutPacket, StreamStart); - {'EXIT', {shutdown, _}} -> - {200, ?HEADER, - <<"<body type='terminate' condition='system-shut" - "down' xmlns='", - (?NS_HTTP_BIND)/binary, "'/>">>}; - {'EXIT', _Reason} -> - {200, ?HEADER, - <<"<body type='terminate' xmlns='", - (?NS_HTTP_BIND)/binary, "'/>">>} - end. - -%% Send output payloads on establised sessions -prepare_outpacket_response(Sess, _Rid, OutPacket, - false) -> - case catch send_outpacket(Sess, OutPacket) of - {'EXIT', _Reason} -> - ?DEBUG("Error in sending packet ~p ", [_Reason]), - {200, ?HEADER, - <<"<body type='terminate' xmlns='", - (?NS_HTTP_BIND)/binary, "'/>">>}; - SendRes -> SendRes - end; -%% Handle a new session along with its output payload -prepare_outpacket_response(#http_bind{id = Sid, - wait = Wait, hold = Hold, to = To} = - _Sess, - _Rid, OutPacket, true) -> - case OutPacket of - [{xmlstreamstart, _, OutAttrs} | Els] -> - AuthID = xml:get_attr_s(<<"id">>, OutAttrs), - From = xml:get_attr_s(<<"from">>, OutAttrs), - Version = xml:get_attr_s(<<"version">>, OutAttrs), - OutEls = case Els of - [] -> []; - [{xmlstreamelement, - #xmlel{name = <<"stream:features">>, - attrs = StreamAttribs, children = StreamEls}} - | StreamTail] -> - TypedTail = [check_default_xmlns(OEl) - || {xmlstreamelement, OEl} <- StreamTail], - [#xmlel{name = <<"stream:features">>, - attrs = - [{<<"xmlns:stream">>, ?NS_STREAM}] ++ - StreamAttribs, - children = StreamEls}] - ++ TypedTail; - StreamTail -> - [check_default_xmlns(OEl) - || {xmlstreamelement, OEl} <- StreamTail] - end, - case OutEls of - [#xmlel{name = <<"stream:error">>}] -> - {200, ?HEADER, - <<"<body type='terminate' condition='host-unknow" - "n' xmlns='", - (?NS_HTTP_BIND)/binary, "'/>">>}; - _ -> - BOSH_attribs = [{<<"authid">>, AuthID}, - {<<"xmlns:xmpp">>, ?NS_BOSH}, - {<<"xmlns:stream">>, ?NS_STREAM}] - ++ - case OutEls of - [] -> []; - _ -> [{<<"xmpp:version">>, Version}] - end, - MaxInactivity = get_max_inactivity(To, ?MAX_INACTIVITY), - MaxPause = get_max_pause(To), - {200, ?HEADER, - xml:element_to_binary(#xmlel{name = <<"body">>, - attrs = - [{<<"xmlns">>, ?NS_HTTP_BIND}, - {<<"sid">>, Sid}, - {<<"wait">>, - iolist_to_binary(integer_to_list(Wait))}, - {<<"requests">>, - iolist_to_binary(integer_to_list(Hold - + - 1))}, - {<<"inactivity">>, - iolist_to_binary(integer_to_list(trunc(MaxInactivity - / - 1000)))}, - {<<"maxpause">>, - iolist_to_binary(integer_to_list(MaxPause))}, - {<<"polling">>, - iolist_to_binary(integer_to_list(trunc((?MIN_POLLING) - / - 1000000)))}, - {<<"ver">>, ?BOSH_VERSION}, - {<<"from">>, From}, - {<<"secure">>, <<"true">>}] - ++ BOSH_attribs, - children = OutEls})} - end; - _ -> - {200, ?HEADER, - <<"<body type='terminate' condition='internal-se" - "rver-error' xmlns='", - (?NS_HTTP_BIND)/binary, "'/>">>} - end. - -http_get(#http_bind{pid = FsmRef, wait = Wait, - hold = Hold}, - Rid) -> - gen_fsm:sync_send_all_state_event(FsmRef, - {http_get, Rid, Wait, Hold}, - 2 * (?MAX_WAIT) * 1000). - -send_outpacket(#http_bind{pid = FsmRef}, OutPacket) -> - case OutPacket of - [] -> - {200, ?HEADER, - <<"<body xmlns='", (?NS_HTTP_BIND)/binary, "'/>">>}; - [{xmlstreamend, _}] -> - gen_fsm:sync_send_all_state_event(FsmRef, - {stop, stream_closed}), - {200, ?HEADER, - <<"<body xmlns='", (?NS_HTTP_BIND)/binary, "'/>">>}; - _ -> - AllElements = lists:all(fun ({xmlstreamelement, - #xmlel{name = <<"stream:error">>}}) -> - false; - ({xmlstreamelement, _}) -> true; - ({xmlstreamraw, _}) -> true; - (_) -> false - end, - OutPacket), - case AllElements of - true -> - TypedEls = lists:foldl(fun ({xmlstreamelement, El}, - Acc) -> - Acc ++ - [xml:element_to_binary(check_default_xmlns(El))]; - ({xmlstreamraw, R}, Acc) -> - Acc ++ [R] - end, - [], OutPacket), - Body = <<"<body xmlns='", (?NS_HTTP_BIND)/binary, "'>", - (iolist_to_binary(TypedEls))/binary, "</body>">>, - ?DEBUG(" --- outgoing data --- ~n~s~n --- END " - "--- ~n", - [Body]), - {200, ?HEADER, Body}; - false -> - case OutPacket of - [{xmlstreamstart, _, _} | SEls] -> - OutEls = case SEls of - [{xmlstreamelement, - #xmlel{name = <<"stream:features">>, - attrs = StreamAttribs, - children = StreamEls}} - | StreamTail] -> - TypedTail = [check_default_xmlns(OEl) - || {xmlstreamelement, OEl} - <- StreamTail], - [#xmlel{name = <<"stream:features">>, - attrs = - [{<<"xmlns:stream">>, - ?NS_STREAM}] - ++ StreamAttribs, - children = StreamEls}] - ++ TypedTail; - StreamTail -> - [check_default_xmlns(OEl) - || {xmlstreamelement, OEl} <- StreamTail] - end, - {200, ?HEADER, - xml:element_to_binary(#xmlel{name = <<"body">>, - attrs = - [{<<"xmlns">>, - ?NS_HTTP_BIND}], - children = OutEls})}; - _ -> - SErrCond = lists:filter(fun ({xmlstreamelement, - #xmlel{name = - <<"stream:error">>}}) -> - true; - (_) -> false - end, - OutPacket), - StreamErrCond = case SErrCond of - [] -> null; - [{xmlstreamelement, - #xmlel{} = StreamErrorTag} - | _] -> - [StreamErrorTag] - end, - gen_fsm:sync_send_all_state_event(FsmRef, - {stop, - {stream_error, - OutPacket}}), - case StreamErrCond of - null -> - {200, ?HEADER, - <<"<body type='terminate' condition='internal-se" - "rver-error' xmlns='", - (?NS_HTTP_BIND)/binary, "'/>">>}; - _ -> - {200, ?HEADER, - <<"<body type='terminate' condition='remote-stre" - "am-error' xmlns='", - (?NS_HTTP_BIND)/binary, "' ", "xmlns:stream='", - (?NS_STREAM)/binary, "'>", - (elements_to_string(StreamErrCond))/binary, - "</body>">>} - end - end - end - end. - -parse_request(Data, PayloadSize, MaxStanzaSize) -> - ?DEBUG("--- incoming data --- ~n~s~n --- END " - "--- ", - [Data]), - case xml_stream:parse_element(Data) of - #xmlel{name = <<"body">>, attrs = Attrs, - children = Els} -> - Xmlns = xml:get_attr_s(<<"xmlns">>, Attrs), - if Xmlns /= (?NS_HTTP_BIND) -> {error, bad_request}; - true -> - case catch - jlib:binary_to_integer(xml:get_attr_s(<<"rid">>, - Attrs)) - of - {'EXIT', _} -> {error, bad_request}; - Rid -> - FixedEls = lists:filter(fun (I) -> - case I of - #xmlel{} -> true; - _ -> false - end - end, - Els), - Sid = xml:get_attr_s(<<"sid">>, Attrs), - if PayloadSize =< MaxStanzaSize -> - {ok, {Sid, Rid, Attrs, FixedEls}}; - true -> {size_limit, Sid} - end - end - end; - #xmlel{} -> {error, bad_request}; - {error, _Reason} -> {error, bad_request} - end. - -send_receiver_reply(undefined, _Reply) -> ok; -send_receiver_reply(Receiver, Reply) -> - gen_fsm:reply(Receiver, Reply). - -%% Cancel timer and empty message queue. -cancel_timer(undefined) -> ok; -cancel_timer(Timer) -> - erlang:cancel_timer(Timer), - receive {timeout, Timer, _} -> ok after 0 -> ok end. - -%% If client asked for a pause (pause > 0), we apply the pause value -%% as inactivity timer: -set_inactivity_timer(Pause, _MaxInactivity) - when Pause > 0 -> - erlang:start_timer(Pause * 1000, self(), []); -%% Otherwise, we apply the max_inactivity value as inactivity timer: -set_inactivity_timer(_Pause, MaxInactivity) -> - erlang:start_timer(MaxInactivity, self(), []). - -%% TODO: Use tail recursion and list reverse ? -elements_to_string([]) -> []; -elements_to_string([El | Els]) -> - [xml:element_to_binary(El) | elements_to_string(Els)]. - -%% @spec (To, Default::integer()) -> integer() -%% where To = [] | {Host::string(), Version::string()} -get_max_inactivity({Host, _}, Default) -> - case gen_mod:get_module_opt(Host, mod_http_bind, max_inactivity, - fun(I) when is_integer(I), I>0 -> I end, - undefined) - of - Seconds when is_integer(Seconds) -> Seconds * 1000; - undefined -> Default - end; -get_max_inactivity(_, Default) -> Default. - -get_max_pause({Host, _}) -> - gen_mod:get_module_opt(Host, mod_http_bind, max_pause, - fun(I) when is_integer(I), I>0 -> I end, - ?MAX_PAUSE); -get_max_pause(_) -> ?MAX_PAUSE. - -%% Current time as integer -tnow() -> - {TMegSec, TSec, TMSec} = now(), - (TMegSec * 1000000 + TSec) * 1000000 + TMSec. - -check_default_xmlns(#xmlel{name = Name, attrs = Attrs, - children = Els} = - El) -> - case xml:get_tag_attr_s(<<"xmlns">>, El) of - <<"">> -> - #xmlel{name = Name, - attrs = [{<<"xmlns">>, ?NS_CLIENT} | Attrs], - children = Els}; - _ -> El - end; -check_default_xmlns(El) -> El. - -%% Check that mod_http_bind has been defined in config file. -%% Print a warning in log file if this is not the case. -check_bind_module(XmppDomain) -> - case gen_mod:is_loaded(XmppDomain, mod_http_bind) of - true -> true; - false -> - ?ERROR_MSG("You are trying to use BOSH (HTTP Bind) " - "in host ~p, but the module mod_http_bind " - "is not started in that host. Configure " - "your BOSH client to connect to the correct " - "host, or add your desired host to the " - "configuration, or check your 'modules' " - "section in your ejabberd configuration " - "file.", - [XmppDomain]), - false - end. diff --git a/src/web/ejabberd_http_poll.erl b/src/web/ejabberd_http_poll.erl deleted file mode 100644 index 7648f5710..000000000 --- a/src/web/ejabberd_http_poll.erl +++ /dev/null @@ -1,428 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : ejabberd_http_poll.erl -%%% Author : Alexey Shchepin <alexey@process-one.net> -%%% Purpose : HTTP Polling support (XEP-0025) -%%% Created : 4 Mar 2004 by Alexey Shchepin <alexey@process-one.net> -%%% -%%% -%%% ejabberd, Copyright (C) 2002-2013 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., 59 Temple Place, Suite 330, Boston, MA -%%% 02111-1307 USA -%%% -%%%---------------------------------------------------------------------- - --module(ejabberd_http_poll). - --author('alexey@process-one.net'). - --behaviour(gen_fsm). - -%% External exports --export([start_link/3, init/1, handle_event/3, - handle_sync_event/4, code_change/4, handle_info/3, - terminate/3, send/2, setopts/2, sockname/1, peername/1, - controlling_process/2, close/1, process/2]). - --include("ejabberd.hrl"). - --include("jlib.hrl"). - --include("ejabberd_http.hrl"). - --record(http_poll, {id :: pid() | binary(), pid :: pid()}). - --type poll_socket() :: #http_poll{}. --export_type([poll_socket/0]). - --record(state, - {id, key, socket, output = <<"">>, input = <<"">>, - waiting_input = false, last_receiver, http_poll_timeout, - timer}). - -%-define(DBGFSM, true). - --ifdef(DBGFSM). - --define(FSMOPTS, [{debug, [trace]}]). - --else. - --define(FSMOPTS, []). - --endif. - --define(HTTP_POLL_TIMEOUT, 300). - --define(CT, - {<<"Content-Type">>, <<"text/xml; charset=utf-8">>}). - --define(BAD_REQUEST, - [?CT, {<<"Set-Cookie">>, <<"ID=-3:0; expires=-1">>}]). - -%%%---------------------------------------------------------------------- -%%% API -%%%---------------------------------------------------------------------- -start(ID, Key, IP) -> - mnesia:create_table(http_poll, - [{ram_copies, [node()]}, - {attributes, record_info(fields, http_poll)}]), - supervisor:start_child(ejabberd_http_poll_sup, [ID, Key, IP]). - -start_link(ID, Key, IP) -> - gen_fsm:start_link(?MODULE, [ID, Key, IP], ?FSMOPTS). - -send({http_poll, FsmRef, _IP}, Packet) -> - gen_fsm:sync_send_all_state_event(FsmRef, - {send, Packet}). - -setopts({http_poll, FsmRef, _IP}, Opts) -> - case lists:member({active, once}, Opts) of - true -> - gen_fsm:send_all_state_event(FsmRef, - {activate, self()}); - _ -> ok - end. - -sockname(_Socket) -> {ok, {{0, 0, 0, 0}, 0}}. - -peername({http_poll, _FsmRef, IP}) -> {ok, IP}. - -controlling_process(_Socket, _Pid) -> ok. - -close({http_poll, FsmRef, _IP}) -> - catch gen_fsm:sync_send_all_state_event(FsmRef, close). - -process([], - #request{data = Data, ip = IP} = _Request) -> - case catch parse_request(Data) of - {ok, ID1, Key, NewKey, Packet} -> - ID = if - (ID1 == <<"0">>) or (ID1 == <<"mobile">>) -> - NewID = sha:sha(term_to_binary({now(), make_ref()})), - {ok, Pid} = start(NewID, <<"">>, IP), - mnesia:transaction( - fun() -> - mnesia:write(#http_poll{id = NewID, pid = Pid}) - end), - NewID; - true -> - ID1 - end, - case http_put(ID, Key, NewKey, Packet) of - {error, not_exists} -> - {200, ?BAD_REQUEST, <<"">>}; - {error, bad_key} -> - {200, ?BAD_REQUEST, <<"">>}; - ok -> - receive - after 100 -> ok - end, - case http_get(ID) of - {error, not_exists} -> - {200, ?BAD_REQUEST, <<"">>}; - {ok, OutPacket} -> - if - ID == ID1 -> - Cookie = <<"ID=", ID/binary, "; expires=-1">>, - {200, [?CT, {<<"Set-Cookie">>, Cookie}], - OutPacket}; - ID1 == <<"mobile">> -> - {200, [?CT], [ID, $\n, OutPacket]}; - true -> - Cookie = <<"ID=", ID/binary, "; expires=-1">>, - {200, [?CT, {<<"Set-Cookie">>, Cookie}], - OutPacket} - end - end - end; - _ -> - HumanHTMLxmlel = get_human_html_xmlel(), - {200, [?CT, {<<"Set-Cookie">>, <<"ID=-2:0; expires=-1">>}], HumanHTMLxmlel} - end; -process(_, _Request) -> - {400, [], - #xmlel{name = <<"h1">>, attrs = [], - children = [{xmlcdata, <<"400 Bad Request">>}]}}. - -%% Code copied from mod_http_bind.erl and customized -get_human_html_xmlel() -> - Heading = <<"ejabberd ", - (iolist_to_binary(atom_to_list(?MODULE)))/binary>>, - #xmlel{name = <<"html">>, - attrs = - [{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>}], - children = - [#xmlel{name = <<"head">>, attrs = [], - children = - [#xmlel{name = <<"title">>, attrs = [], - children = [{xmlcdata, Heading}]}]}, - #xmlel{name = <<"body">>, attrs = [], - children = - [#xmlel{name = <<"h1">>, attrs = [], - children = [{xmlcdata, Heading}]}, - #xmlel{name = <<"p">>, attrs = [], - children = - [{xmlcdata, <<"An implementation of ">>}, - #xmlel{name = <<"a">>, - attrs = - [{<<"href">>, - <<"http://xmpp.org/extensions/xep-0025.html">>}], - children = - [{xmlcdata, - <<"Jabber HTTP Polling (XEP-0025)">>}]}]}, - #xmlel{name = <<"p">>, attrs = [], - children = - [{xmlcdata, - <<"This web page is only informative. To " - "use HTTP-Poll you need a Jabber/XMPP " - "client that supports it.">>}]}]}]}. - -%%%---------------------------------------------------------------------- -%%% Callback functions from gen_fsm -%%%---------------------------------------------------------------------- - -%%---------------------------------------------------------------------- -%% Func: init/1 -%% Returns: {ok, StateName, StateData} | -%% {ok, StateName, StateData, Timeout} | -%% ignore | -%% {stop, StopReason} -%%---------------------------------------------------------------------- -init([ID, Key, IP]) -> - ?INFO_MSG("started: ~p", [{ID, Key, IP}]), - Opts = ejabberd_c2s_config:get_c2s_limits(), - HTTPPollTimeout = ejabberd_config:get_local_option( - {http_poll_timeout, ?MYNAME}, - fun(I) when is_integer(I), I>0 -> I end, - ?HTTP_POLL_TIMEOUT) * 1000, - Socket = {http_poll, self(), IP}, - ejabberd_socket:start(ejabberd_c2s, ?MODULE, Socket, - Opts), - Timer = erlang:start_timer(HTTPPollTimeout, self(), []), - {ok, loop, - #state{id = ID, key = Key, socket = Socket, - http_poll_timeout = HTTPPollTimeout, timer = Timer}}. - -%%---------------------------------------------------------------------- -%% Func: StateName/2 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} -%% {stop, Reason, NewStateData} -%%---------------------------------------------------------------------- - -%%---------------------------------------------------------------------- -%% Func: StateName/3 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {reply, Reply, NextStateName, NextStateData} | -%% {reply, Reply, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} | -%% {stop, Reason, Reply, NewStateData} -%% {stop, Reason, Reply, NewStateData} -%%---------------------------------------------------------------------- -%state_name(Event, From, StateData) -> -% Reply = ok, -% {reply, Reply, state_name, StateData}. - -%%---------------------------------------------------------------------- -%% Func: handle_event/3 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} -%%---------------------------------------------------------------------- -handle_event({activate, From}, StateName, StateData) -> - case StateData#state.input of - <<"">> -> - {next_state, StateName, - StateData#state{waiting_input = {From, ok}}}; - Input -> - Receiver = From, - Receiver ! - {tcp, StateData#state.socket, iolist_to_binary(Input)}, - {next_state, StateName, - StateData#state{input = <<"">>, waiting_input = false, - last_receiver = Receiver}} - end; -handle_event(_Event, StateName, StateData) -> - {next_state, StateName, StateData}. - -%%---------------------------------------------------------------------- -%% Func: handle_sync_event/4 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {reply, Reply, NextStateName, NextStateData} | -%% {reply, Reply, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} | -%% {stop, Reason, Reply, NewStateData} -%%---------------------------------------------------------------------- -handle_sync_event({send, Packet}, _From, StateName, - StateData) -> - Packet2 = if is_binary(Packet) -> (Packet); - true -> Packet - end, - Output = StateData#state.output ++ - [lists:flatten(Packet2)], - Reply = ok, - {reply, Reply, StateName, - StateData#state{output = Output}}; -handle_sync_event(stop, _From, _StateName, StateData) -> - Reply = ok, {stop, normal, Reply, StateData}; -handle_sync_event({http_put, Key, NewKey, Packet}, - _From, StateName, StateData) -> - Allow = case StateData#state.key of - <<"">> -> true; - OldKey -> - NextKey = jlib:encode_base64((crypto:sha(Key))), - if OldKey == NextKey -> true; - true -> false - end - end, - if Allow -> - case StateData#state.waiting_input of - false -> - Input = [StateData#state.input | Packet], - Reply = ok, - {reply, Reply, StateName, - StateData#state{input = Input, key = NewKey}}; - {Receiver, _Tag} -> - Receiver ! - {tcp, StateData#state.socket, iolist_to_binary(Packet)}, - cancel_timer(StateData#state.timer), - Timer = - erlang:start_timer(StateData#state.http_poll_timeout, - self(), []), - Reply = ok, - {reply, Reply, StateName, - StateData#state{waiting_input = false, - last_receiver = Receiver, key = NewKey, - timer = Timer}} - end; - true -> - Reply = {error, bad_key}, - {reply, Reply, StateName, StateData} - end; -handle_sync_event(http_get, _From, StateName, - StateData) -> - Reply = {ok, StateData#state.output}, - {reply, Reply, StateName, - StateData#state{output = <<"">>}}; -handle_sync_event(_Event, _From, StateName, - StateData) -> - Reply = ok, {reply, Reply, StateName, StateData}. - -code_change(_OldVsn, StateName, StateData, _Extra) -> - {ok, StateName, StateData}. - -%%---------------------------------------------------------------------- -%% Func: handle_info/3 -%% Returns: {next_state, NextStateName, NextStateData} | -%% {next_state, NextStateName, NextStateData, Timeout} | -%% {stop, Reason, NewStateData} -%%---------------------------------------------------------------------- -handle_info({timeout, Timer, _}, _StateName, - #state{timer = Timer} = StateData) -> - {stop, normal, StateData}; -handle_info(_, StateName, StateData) -> - {next_state, StateName, StateData}. - -%%---------------------------------------------------------------------- -%% Func: terminate/3 -%% Purpose: Shutdown the fsm -%% Returns: any -%%---------------------------------------------------------------------- -terminate(_Reason, _StateName, StateData) -> - mnesia:transaction( - fun() -> - mnesia:delete({http_poll, StateData#state.id}) - end), - case StateData#state.waiting_input of - false -> - case StateData#state.last_receiver of - undefined -> ok; - Receiver -> - Receiver ! {tcp_closed, StateData#state.socket} - end; - {Receiver, _Tag} -> - Receiver ! {tcp_closed, StateData#state.socket} - end, - catch resend_messages(StateData#state.output), - ok. - -%%%---------------------------------------------------------------------- -%%% Internal functions -%%%---------------------------------------------------------------------- - -http_put(ID, Key, NewKey, Packet) -> - case mnesia:dirty_read({http_poll, ID}) of - [] -> - {error, not_exists}; - [#http_poll{pid = FsmRef}] -> - gen_fsm:sync_send_all_state_event( - FsmRef, {http_put, Key, NewKey, Packet}) - end. - -http_get(ID) -> - case mnesia:dirty_read({http_poll, ID}) of - [] -> - {error, not_exists}; - [#http_poll{pid = FsmRef}] -> - gen_fsm:sync_send_all_state_event(FsmRef, http_get) - end. - -parse_request(Data) -> - Comma = str:chr(Data, $,), - Header = str:substr(Data, 1, Comma - 1), - Packet = str:substr(Data, Comma + 1, byte_size(Data)), - {ID, Key, NewKey} = case str:tokens(Header, <<";">>) of - [ID1] -> {ID1, <<"">>, <<"">>}; - [ID1, Key1] -> {ID1, Key1, Key1}; - [ID1, Key1, NewKey1] -> {ID1, Key1, NewKey1} - end, - {ok, ID, Key, NewKey, Packet}. - -cancel_timer(Timer) -> - erlang:cancel_timer(Timer), - receive {timeout, Timer, _} -> ok after 0 -> ok end. - -%% Resend the polled messages -resend_messages(Messages) -> -%% This function is used to resend messages that have been polled but not -%% delivered. - lists:foreach(fun (Packet) -> resend_message(Packet) - end, - Messages). - -resend_message(Packet) -> - #xmlel{name = Name} = ParsedPacket = - xml_stream:parse_element(Packet), - if Name == <<"iq">>; - Name == <<"message">>; - Name == <<"presence">> -> - From = get_jid(<<"from">>, ParsedPacket), - To = get_jid(<<"to">>, ParsedPacket), - ?DEBUG("Resend ~p ~p ~p~n", [From, To, ParsedPacket]), - ejabberd_router:route(From, To, ParsedPacket); - true -> ok - end. - -%% Type can be "from" or "to" -%% Parsed packet is a parsed Jabber packet. -get_jid(Type, ParsedPacket) -> - case xml:get_tag_attr(Type, ParsedPacket) of - {value, StringJid} -> jlib:string_to_jid(StringJid); - false -> jlib:make_jid(<<"">>, <<"">>, <<"">>) - end. diff --git a/src/web/ejabberd_web.erl b/src/web/ejabberd_web.erl deleted file mode 100644 index 8c7ccaf69..000000000 --- a/src/web/ejabberd_web.erl +++ /dev/null @@ -1,105 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : ejabberd_web.erl -%%% Author : Alexey Shchepin <alexey@process-one.net> -%%% Purpose : -%%% Purpose : -%%% Created : 28 Feb 2004 by Alexey Shchepin <alexey@process-one.net> -%%% -%%% -%%% ejabberd, Copyright (C) 2002-2013 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., 59 Temple Place, Suite 330, Boston, MA -%%% 02111-1307 USA -%%% -%%%---------------------------------------------------------------------- - --module(ejabberd_web). - --author('alexey@process-one.net'). - -%% External exports --export([make_xhtml/1, make_xhtml/2, error/1]). - --include("ejabberd.hrl"). - --include("jlib.hrl"). - --include("ejabberd_http.hrl"). - -%% XXX bard: there are variants of make_xhtml in ejabberd_http and -%% ejabberd_web_admin. It might be a good idea to centralize it here -%% and also create an ejabberd_web.hrl file holding the macros, so -%% that third parties can use ejabberd_web as an "utility" library. - -make_xhtml(Els) -> make_xhtml([], Els). - -make_xhtml(HeadEls, Els) -> - #xmlel{name = <<"html">>, - attrs = - [{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>}, - {<<"xml:lang">>, <<"en">>}, {<<"lang">>, <<"en">>}], - children = - [#xmlel{name = <<"head">>, attrs = [], - children = - [#xmlel{name = <<"meta">>, - attrs = - [{<<"http-equiv">>, <<"Content-Type">>}, - {<<"content">>, - <<"text/html; charset=utf-8">>}], - children = []} - | HeadEls]}, - #xmlel{name = <<"body">>, attrs = [], children = Els}]}. - --define(X(Name), - #xmlel{name = Name, attrs = [], children = []}). - --define(XA(Name, Attrs), - #xmlel{name = Name, attrs = Attrs, children = []}). - --define(XE(Name, Els), - #xmlel{name = Name, attrs = [], children = Els}). - --define(XAE(Name, Attrs, Els), - #xmlel{name = Name, attrs = Attrs, children = Els}). - --define(C(Text), {xmlcdata, Text}). - --define(XC(Name, Text), ?XE(Name, [?C(Text)])). - --define(XAC(Name, Attrs, Text), - ?XAE(Name, Attrs, [?C(Text)])). - --define(LI(Els), ?XE(<<"li">>, Els)). - --define(A(URL, Els), - ?XAE(<<"a">>, [{<<"href">>, URL}], Els)). - --define(AC(URL, Text), ?A(URL, [?C(Text)])). - --define(P, ?X(<<"p">>)). - --define(BR, ?X(<<"br">>)). - --define(INPUT(Type, Name, Value), - ?XA(<<"input">>, - [{<<"type">>, Type}, {<<"name">>, Name}, - {<<"value">>, Value}])). - -error(not_found) -> - {404, [], - make_xhtml([?XC(<<"h1">>, <<"404 Not Found">>)])}; -error(not_allowed) -> - {401, [], - make_xhtml([?XC(<<"h1">>, <<"401 Unauthorized">>)])}. diff --git a/src/web/ejabberd_web_admin.erl b/src/web/ejabberd_web_admin.erl deleted file mode 100644 index 73c7ab52a..000000000 --- a/src/web/ejabberd_web_admin.erl +++ /dev/null @@ -1,2893 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : ejabberd_web_admin.erl -%%% Author : Alexey Shchepin <alexey@process-one.net> -%%% Purpose : Administration web interface -%%% Created : 9 Apr 2004 by Alexey Shchepin <alexey@process-one.net> -%%% -%%% -%%% ejabberd, Copyright (C) 2002-2013 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., 59 Temple Place, Suite 330, Boston, MA -%%% 02111-1307 USA -%%% -%%%---------------------------------------------------------------------- - -%%%% definitions - --module(ejabberd_web_admin). - --author('alexey@process-one.net'). - -%% External exports --export([process/2, list_users/4, - list_users_in_diapason/4, pretty_print_xml/1, - term_to_id/1]). - --include("ejabberd.hrl"). - --include("jlib.hrl"). - --include("ejabberd_http.hrl"). - --include("ejabberd_web_admin.hrl"). - --define(INPUTATTRS(Type, Name, Value, Attrs), - ?XA(<<"input">>, - (Attrs ++ - [{<<"type">>, Type}, {<<"name">>, Name}, - {<<"value">>, Value}]))). - -%%%================================== -%%%% get_acl_access - -%% @spec (Path::[string()], Method) -> {HostOfRule, [AccessRule]} -%% where Method = 'GET' | 'POST' - -%% All accounts can access those URLs -get_acl_rule([], _) -> {<<"localhost">>, [all]}; -get_acl_rule([<<"style.css">>], _) -> - {<<"localhost">>, [all]}; -get_acl_rule([<<"logo.png">>], _) -> - {<<"localhost">>, [all]}; -get_acl_rule([<<"logo-fill.png">>], _) -> - {<<"localhost">>, [all]}; -get_acl_rule([<<"favicon.ico">>], _) -> - {<<"localhost">>, [all]}; -get_acl_rule([<<"additions.js">>], _) -> - {<<"localhost">>, [all]}; -%% This page only displays vhosts that the user is admin: -get_acl_rule([<<"vhosts">>], _) -> - {<<"localhost">>, [all]}; -%% The pages of a vhost are only accesible if the user is admin of that vhost: -get_acl_rule([<<"server">>, VHost | _RPath], Method) - when Method =:= 'GET' orelse Method =:= 'HEAD' -> - {VHost, [configure, webadmin_view]}; -get_acl_rule([<<"server">>, VHost | _RPath], 'POST') -> - {VHost, [configure]}; -%% Default rule: only global admins can access any other random page -get_acl_rule(_RPath, Method) - when Method =:= 'GET' orelse Method =:= 'HEAD' -> - {global, [configure, webadmin_view]}; -get_acl_rule(_RPath, 'POST') -> {global, [configure]}. - -is_acl_match(Host, Rules, Jid) -> - lists:any(fun (Rule) -> - allow == acl:match_rule(Host, Rule, Jid) - end, - Rules). - -%%%================================== -%%%% Menu Items Access - -get_jid(Auth, HostHTTP, Method) -> - case get_auth_admin(Auth, HostHTTP, [], Method) of - {ok, {User, Server}} -> - jlib:make_jid(User, Server, <<"">>); - {unauthorized, Error} -> - ?ERROR_MSG("Unauthorized ~p: ~p", [Auth, Error]), - throw({unauthorized, Auth}) - end. - -get_menu_items(global, cluster, Lang, JID) -> - {Base, _, Items} = make_server_menu([], [], Lang, JID), - lists:map(fun ({URI, Name}) -> - {<<Base/binary, URI/binary, "/">>, Name}; - ({URI, Name, _SubMenu}) -> - {<<Base/binary, URI/binary, "/">>, Name} - end, - Items); -get_menu_items(Host, cluster, Lang, JID) -> - {Base, _, Items} = make_host_menu(Host, [], Lang, JID), - lists:map(fun ({URI, Name}) -> - {<<Base/binary, URI/binary, "/">>, Name}; - ({URI, Name, _SubMenu}) -> - {<<Base/binary, URI/binary, "/">>, Name} - end, - Items). - -%% get_menu_items(Host, Node, Lang, JID) -> -%% {Base, _, Items} = make_host_node_menu(Host, Node, Lang, JID), -%% lists:map( -%% fun({URI, Name}) -> -%% {Base++URI++"/", Name}; -%% ({URI, Name, _SubMenu}) -> -%% {Base++URI++"/", Name} -%% end, -%% Items -%% ). - -is_allowed_path(BasePath, {Path, _}, JID) -> - is_allowed_path(BasePath ++ [Path], JID); -is_allowed_path(BasePath, {Path, _, _}, JID) -> - is_allowed_path(BasePath ++ [Path], JID). - -is_allowed_path([<<"admin">> | Path], JID) -> - is_allowed_path(Path, JID); -is_allowed_path(Path, JID) -> - {HostOfRule, AccessRule} = get_acl_rule(Path, 'GET'), - is_acl_match(HostOfRule, AccessRule, JID). - -%% @spec(Path) -> URL -%% where Path = [string()] -%% URL = string() -%% Convert ["admin", "user", "tom"] -> "/admin/user/tom/" -%%path_to_url(Path) -> -%% "/" ++ string:join(Path, "/") ++ "/". - -%% @spec(URL) -> Path -%% where Path = [string()] -%% URL = string() -%% Convert "admin/user/tom" -> ["admin", "user", "tom"] -url_to_path(URL) -> str:tokens(URL, <<"/">>). - -%%%================================== -%%%% process/2 - -process([<<"doc">>, LocalFile], _Request) -> - DocPath = case os:getenv("EJABBERD_DOC_PATH") of - P when is_list(P) -> P; - false -> <<"/share/doc/ejabberd/">> - end, - FileName = filename:join(DocPath, LocalFile), - case file:read_file(FileName) of - {ok, FileContents} -> - ?DEBUG("Delivering content.", []), - {200, [{<<"Server">>, <<"ejabberd">>}], FileContents}; - {error, Error} -> - ?DEBUG("Delivering error: ~p", [Error]), - Help = <<" ", FileName/binary, - " - Try to specify the path to ejabberd " - "documentation with the environment variable " - "EJABBERD_DOC_PATH. Check the ejabberd " - "Guide for more information.">>, - case Error of - eacces -> {403, [], <<"Forbidden", Help/binary>>}; - enoent -> {404, [], <<"Not found", Help/binary>>}; - _Else -> - {404, [], <<(iolist_to_binary(atom_to_list(Error)))/binary, Help/binary>>} - end - end; -process([<<"server">>, SHost | RPath] = Path, - #request{auth = Auth, lang = Lang, host = HostHTTP, - method = Method} = - Request) -> - Host = jlib:nameprep(SHost), - case lists:member(Host, ?MYHOSTS) of - true -> - case get_auth_admin(Auth, HostHTTP, Path, Method) of - {ok, {User, Server}} -> - AJID = get_jid(Auth, HostHTTP, Method), - process_admin(Host, - Request#request{path = RPath, - auth = {auth_jid, Auth, AJID}, - us = {User, Server}}); - {unauthorized, <<"no-auth-provided">>} -> - {401, - [{<<"WWW-Authenticate">>, - <<"basic realm=\"ejabberd\"">>}], - ejabberd_web:make_xhtml([?XCT(<<"h1">>, - <<"Unauthorized">>)])}; - {unauthorized, Error} -> - {BadUser, _BadPass} = Auth, - {IPT, _Port} = Request#request.ip, - IPS = jlib:ip_to_list(IPT), - ?WARNING_MSG("Access of ~p from ~p failed with error: ~p", - [BadUser, IPS, Error]), - {401, - [{<<"WWW-Authenticate">>, - <<"basic realm=\"auth error, retry login " - "to ejabberd\"">>}], - ejabberd_web:make_xhtml([?XCT(<<"h1">>, - <<"Unauthorized">>)])} - end; - false -> ejabberd_web:error(not_found) - end; -process(RPath, - #request{auth = Auth, lang = Lang, host = HostHTTP, - method = Method} = - Request) -> - case get_auth_admin(Auth, HostHTTP, RPath, Method) of - {ok, {User, Server}} -> - AJID = get_jid(Auth, HostHTTP, Method), - process_admin(global, - Request#request{path = RPath, - auth = {auth_jid, Auth, AJID}, - us = {User, Server}}); - {unauthorized, <<"no-auth-provided">>} -> - {401, - [{<<"WWW-Authenticate">>, - <<"basic realm=\"ejabberd\"">>}], - ejabberd_web:make_xhtml([?XCT(<<"h1">>, - <<"Unauthorized">>)])}; - {unauthorized, Error} -> - {BadUser, _BadPass} = Auth, - {IPT, _Port} = Request#request.ip, - IPS = jlib:ip_to_list(IPT), - ?WARNING_MSG("Access of ~p from ~p failed with error: ~p", - [BadUser, IPS, Error]), - {401, - [{<<"WWW-Authenticate">>, - <<"basic realm=\"auth error, retry login " - "to ejabberd\"">>}], - ejabberd_web:make_xhtml([?XCT(<<"h1">>, - <<"Unauthorized">>)])} - end. - -get_auth_admin(Auth, HostHTTP, RPath, Method) -> - case Auth of - {SJID, Pass} -> - {HostOfRule, AccessRule} = get_acl_rule(RPath, Method), - case jlib:string_to_jid(SJID) of - error -> {unauthorized, <<"badformed-jid">>}; - #jid{user = <<"">>, server = User} -> - get_auth_account(HostOfRule, AccessRule, User, HostHTTP, - Pass); - #jid{user = User, server = Server} -> - get_auth_account(HostOfRule, AccessRule, User, Server, - Pass) - end; - undefined -> {unauthorized, <<"no-auth-provided">>} - end. - -get_auth_account(HostOfRule, AccessRule, User, Server, - Pass) -> - case ejabberd_auth:check_password(User, Server, Pass) of - true -> - case is_acl_match(HostOfRule, AccessRule, - jlib:make_jid(User, Server, <<"">>)) - of - false -> {unauthorized, <<"unprivileged-account">>}; - true -> {ok, {User, Server}} - end; - false -> - case ejabberd_auth:is_user_exists(User, Server) of - true -> {unauthorized, <<"bad-password">>}; - false -> {unauthorized, <<"inexistent-account">>} - end - end. - -%%%================================== -%%%% make_xhtml - -make_xhtml(Els, Host, Lang, JID) -> - make_xhtml(Els, Host, cluster, Lang, JID). - -%% @spec (Els, Host, Node, Lang, JID) -> {200, [html], xmlelement()} -%% where Host = global | string() -%% Node = cluster | atom() -%% JID = jid() -make_xhtml(Els, Host, Node, Lang, JID) -> - Base = get_base_path(Host, cluster), - MenuItems = make_navigation(Host, Node, Lang, JID), - {200, [html], - #xmlel{name = <<"html">>, - attrs = - [{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>}, - {<<"xml:lang">>, Lang}, {<<"lang">>, Lang}], - children = - [#xmlel{name = <<"head">>, attrs = [], - children = - [?XCT(<<"title">>, <<"ejabberd Web Admin">>), - #xmlel{name = <<"meta">>, - attrs = - [{<<"http-equiv">>, <<"Content-Type">>}, - {<<"content">>, - <<"text/html; charset=utf-8">>}], - children = []}, - #xmlel{name = <<"script">>, - attrs = - [{<<"src">>, - <<Base/binary, "/additions.js">>}, - {<<"type">>, <<"text/javascript">>}], - children = [?C(<<" ">>)]}, - #xmlel{name = <<"link">>, - attrs = - [{<<"href">>, - <<Base/binary, "favicon.ico">>}, - {<<"type">>, <<"image/x-icon">>}, - {<<"rel">>, <<"shortcut icon">>}], - children = []}, - #xmlel{name = <<"link">>, - attrs = - [{<<"href">>, - <<Base/binary, "style.css">>}, - {<<"type">>, <<"text/css">>}, - {<<"rel">>, <<"stylesheet">>}], - children = []}]}, - ?XE(<<"body">>, - [?XAE(<<"div">>, [{<<"id">>, <<"container">>}], - [?XAE(<<"div">>, [{<<"id">>, <<"header">>}], - [?XE(<<"h1">>, - [?ACT(<<"/admin/">>, - <<"ejabberd Web Admin">>)])]), - ?XAE(<<"div">>, [{<<"id">>, <<"navigation">>}], - [?XE(<<"ul">>, MenuItems)]), - ?XAE(<<"div">>, [{<<"id">>, <<"content">>}], Els), - ?XAE(<<"div">>, [{<<"id">>, <<"clearcopyright">>}], - [{xmlcdata, <<"">>}])]), - ?XAE(<<"div">>, [{<<"id">>, <<"copyrightouter">>}], - [?XAE(<<"div">>, [{<<"id">>, <<"copyright">>}], - [?XC(<<"p">>, - <<"ejabberd (c) 2002-2013 ProcessOne">>)])])])]}}. - -get_base_path(global, cluster) -> <<"/admin/">>; -get_base_path(Host, cluster) -> - <<"/admin/server/", Host/binary, "/">>; -get_base_path(global, Node) -> - <<"/admin/node/", - (iolist_to_binary(atom_to_list(Node)))/binary, "/">>; -get_base_path(Host, Node) -> - <<"/admin/server/", Host/binary, "/node/", - (iolist_to_binary(atom_to_list(Node)))/binary, "/">>. - -%%%================================== -%%%% css & images - -additions_js() -> - <<"\nfunction selectAll() {\n for(i=0;i<documen" - "t.forms[0].elements.length;i++)\n { " - "var e = document.forms[0].elements[i];\n " - " if(e.type == 'checkbox')\n { e.checked " - "= true; }\n }\n}\nfunction unSelectAll() " - "{\n for(i=0;i<document.forms[0].elements.len" - "gth;i++)\n { var e = document.forms[0].eleme" - "nts[i];\n if(e.type == 'checkbox')\n " - " { e.checked = false; }\n }\n}\n">>. - -css(Host) -> - Base = get_base_path(Host, cluster), - <<"\nhtml,body {\n background: white;\n " - " margin: 0;\n padding: 0;\n height: " - "100%;\n}\n\n#container {\n padding: " - "0;\n margin: 0;\n min-height: 100%;\n " - " height: 100%;\n margin-bottom: -30px;\n}\n\n" - "html>body #container {\n height: auto;\n}\n\n" - "#header h1 {\n width: 100%;\n height: " - "55px;\n padding: 0;\n margin: 0;\n " - " background: transparent url(\"", - Base/binary, - "logo-fill.png\");\n}\n\n#header h1 a " - "{\n position: absolute;\n top: 0;\n " - " left: 0;\n width: 100%;\n height: " - "55px;\n padding: 0;\n margin: 0;\n " - " background: transparent url(\"", - Base/binary, - "logo.png\") no-repeat;\n display: block;\n " - " text-indent: -700em;\n}\n\n#clearcopyright " - "{\n display: block;\n width: 100%;\n " - " height: 30px;\n}\n\n#copyrightouter " - "{\n display: table;\n width: 100%;\n " - " height: 30px;\n}\n\n#copyright {\n " - " display: table-cell;\n vertical-align: " - "bottom;\n width: 100%;\n height: 30px;\n}\n\n" - "#copyright p {\n margin-left: 0;\n " - " margin-right: 0;\n margin-top: 5px;\n " - " margin-bottom: 0;\n padding-left: " - "0;\n padding-right: 0;\n padding-top: " - "1px;\n padding-bottom: 1px;\n width: " - "100%;\n color: #ffffff;\n background-color: " - "#fe8a00;\n font-family: Verdana, Arial, " - "Helvetica, sans-serif; \n font-size: " - "7pt;\n font-weight: bold;\n text-align: " - "center;\n}\n\n#navigation ul {\n position: " - "absolute;\n top: 65px;\n left: 0;\n " - " padding: 0 1px 1px 1px;\n margin: " - "0;\n font-family: Verdana, Arial, Helvetica, " - "sans-serif; \n font-size: 8pt;\n font-weigh" - "t: bold;\n border-top: 1px solid #d47911;\n " - " width: 17em;\n}\n\n#navigation ul li " - "{\n list-style: none;\n margin: 0;\n " - " text-align: left;\n display: inline;\n}\n\n" - "#navigation ul li a {\n margin: 0;\n " - " display: block;\n padding: 3px 6px " - "3px 9px;\n border-left: 1em solid #ffc78c;\n " - " border-right: 1px solid #d47911;\n " - " border-bottom: 1px solid #d47911;\n " - " background: #ffe3c9;\n text-decoration: " - "none;\n}\n\n#navigation ul li a:link " - "{\n color: #844;\n}\n\n#navigation " - "ul li a:visited {\n color: #766;\n}\n\n#navig" - "ation ul li a:hover {\n border-color: " - "#fc8800;\n color: #FFF;\n background: " - "#332;\n}\n\nul li #navhead a, ul li " - "#navheadsub a, ul li #navheadsubsub " - "a {\n text-align: center;\n border-top: " - "1px solid #d47911;\n border-bottom: " - "2px solid #d47911;\n background: #FED6A6;\n}\n\n" - "#navheadsub, #navitemsub {\n border-left: " - "7px solid white;\n margin-left: 2px;\n}\n\n#" - "navheadsubsub, #navitemsubsub {\n border-lef" - "t: 14px solid white;\n margin-left: " - "4px;\n}\n\n#lastactivity li {\n font-weight: " - "bold;\n border: 1px solid #d6760e;\n " - " background-color: #fff2e8;\n padding: " - "2px;\n margin-bottom: -1px;\n}\n\ntd.copy " - "{\n color: #ffffff;\n background-color: " - "#fe8a00;\n font-family: Verdana, Arial, " - "Helvetica, sans-serif; \n font-size: " - "7pt;\n font-weight: bold;\n text-align: " - "center;\n}\n\ninput {\n font-family: " - "Verdana, Arial, Helvetica, sans-serif; " - "\n font-size: 10pt;\n border: 1px " - "solid #d6760e;\n color: #723202;\n " - " background-color: #fff2e8;\n vertical-align" - ": middle;\n margin-bottom: 0px;\n " - "padding: 0.1em;\n}\n\ninput[type=submit] " - "{\n font-family: Verdana, Arial, Helvetica, " - "sans-serif; \n font-size: 8pt;\n font-weigh" - "t: bold;\n color: #ffffff;\n background-col" - "or: #fe8a00;\n border: 1px solid #d6760e;\n}\n\n" - "textarea {\n font-family: Verdana, " - "Arial, Helvetica, sans-serif; \n font-size: " - "10pt;\n border: 1px solid #d6760e;\n " - " color: #723202;\n background-color: " - "#fff2e8;\n}\n\nselect {\n border: 1px " - "solid #d6760e;\n color: #723202;\n " - " background-color: #fff2e8;\n vertical-align" - ": middle;\n margin-bottom: 0px; \n " - " padding: 0.1em;\n}\n\nthead {\n color: " - "#000000;\n background-color: #ffffff;\n " - " font-family: Verdana, Arial, Helvetica, " - "sans-serif; \n font-size: 10pt;\n " - "font-weight: bold;\n}\n\ntr.head {\n " - " color: #ffffff;\n background-color: " - "#3b547a;\n font-family: Verdana, Arial, " - "Helvetica, sans-serif; \n font-size: " - "9pt;\n font-weight: bold;\n text-align: " - "center;\n}\n\ntr.oddraw {\n color: " - "#412c75;\n background-color: #ccd4df;\n " - " font-family: Verdana, Arial, Helvetica, " - "sans-serif; \n font-size: 9pt;\n font-weigh" - "t: normal;\n text-align: center;\n}\n\ntr.ev" - "enraw {\n color: #412c75;\n background-colo" - "r: #dbe0e8;\n font-family: Verdana, " - "Arial, Helvetica, sans-serif; \n font-size: " - "9pt;\n font-weight: normal;\n text-align: " - "center;\n}\n\ntd.leftheader {\n color: " - "#412c75;\n background-color: #ccccc1;\n " - " font-family: Verdana, Arial, Helvetica, " - "sans-serif; \n font-size: 9pt;\n font-weigh" - "t: bold;\n padding-left: 5px;\n padding-top" - ": 2px;\n padding-bottom: 2px;\n margin-top: " - "0px;\n margin-bottom: 0px;\n}\n\ntd.leftcont" - "ent {\n color: #000044;\n background-color: " - "#e6e6df;\n font-family: Verdana, Arial, " - "Helvetica, sans-serif; \n font-size: " - "7pt;\n font-weight: normal;\n padding-left: " - "5px;\n padding-right: 5px;\n padding-top: " - "2px;\n padding-bottom: 2px;\n margin-top: " - "0px;\n margin-bottom: 0px;\n}\n\ntd.rightcon" - "tent {\n color: #000044;\n font-family: " - "Verdana, Arial, Helvetica, sans-serif; " - "\n font-size: 10pt;\n font-weight: " - "normal;\n text-align: justify;\n padding-le" - "ft: 10px;\n padding-right: 10px;\n " - " padding-bottom: 5px;\n}\n\n\nh1 {\n " - " color: #000044;\n font-family: Verdana, " - "Arial, Helvetica, sans-serif; \n font-size: " - "14pt;\n font-weight: bold;\n text-align: " - "center;\n padding-top: 2px;\n padding-botto" - "m: 2px;\n margin-top: 0px;\n margin-bottom: " - "0px;\n}\n\nh2 {\n color: #000044;\n " - " font-family: Verdana, Arial, Helvetica, " - "sans-serif; \n font-size: 12pt;\n " - "font-weight: bold;\n text-align: center;\n " - " padding-top: 2px;\n padding-bottom: " - "2px;\n margin-top: 0px;\n margin-bottom: " - "0px;\n}\n\nh3 {\n color: #000044;\n " - " font-family: Verdana, Arial, Helvetica, " - "sans-serif; \n font-size: 10pt;\n " - "font-weight: bold;\n text-align: left;\n " - " padding-top: 20px;\n padding-bottom: " - "2px;\n margin-top: 0px;\n margin-bottom: " - "0px;\n}\n\n#content a:link {\n color: " - "#990000; \n font-family: Verdana, Arial, " - "Helvetica, sans-serif; \n font-size: " - "10pt;\n font-weight: bold;\n text-decoratio" - "n: underline;\n}\n#content a:visited " - "{\n color: #990000; \n font-family: " - "Verdana, Arial, Helvetica, sans-serif; " - "\n font-size: 10pt;\n font-weight: " - "bold;\n text-decoration: underline;\n}\n#con" - "tent a:hover {\n color: #cc6600; \n " - " font-family: Verdana, Arial, Helvetica, " - "sans-serif; \n font-size: 10pt;\n " - "font-weight: bold;\n text-decoration: " - "underline;\n}\n\n\n#content ul li {\n " - " list-style-type: disc;\n font-size: " - "10pt;\n /*font-size: 7pt;*/\n padding-left: " - "10px;\n}\n\n#content ul.nolistyle>li " - "{\n list-style-type: none;\n}\n\n#content " - "li.big {\n font-size: 10pt;\n}\n\n#content " - "{\n font-family: Verdana, Arial, Helvetica, " - "sans-serif; \n font-size: 10pt;\n " - "padding-left: 17em;\n padding-top: " - "5px;\n}\n\ndiv.guidelink {\n text-align: " - "right;\n padding-right: 1em;\n}\n\ntable.wit" - "htextareas>tbody>tr>td {\n vertical-align: " - "top;\n}\n\np.result {\n border: 1px;\n " - " border-style: dashed;\n border-color: " - "#FE8A02;\n padding: 1em;\n margin-right: " - "1em;\n background: #FFE3C9;\n}\n\n*.alignrig" - "ht {\n font-size: 10pt;\n text-align: " - "right;\n}\n\n">>. - -favicon() -> - jlib:decode_base64(<<"AAABAAEAEBAQAAEABAAoAQAAFgAAACgAAAAQAAAAIAAAA" - "AEABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJf+cAAI" - "PsAAGC8gAVhecAAIr8ACiR7wBBmOcAUKPsAFun8ABhqeo" - "AgLryAJLB8ACz1PcAv9r7AMvi+gAAAAAAAgICARMhICAk" - "JCQkQkFCQgICN2d2cSMgJCRevdvVQkICAlqYh5MgICQkX" - "rRCQkJCMgI7kiAjICAUFF2swkFBQRQUXazCQUFBAgI7ki" - "AgICAkJF60QkJCQgICOpiHkyAgJCRevdvlQkICAjdndnM" - "gICQkJCRCQkJCAgICARAgICAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - "AAAAAAAAAAAAAAAAAAA">>). - -logo() -> - jlib:decode_base64(<<"iVBORw0KGgoAAAANSUhEUgAAAVcAAAA3CAMAAACPbPnEA" - "AAAAXNSR0IArs4c6QAAAEtQTFRFcTIA1XcE/YsA/40E/p" - "IH/JYc/5kg/54i/KIu/6U6/apE/61H/61P/bFX/7Vh/bd" - "a/rpq/L5s/8J2/cJ8/8qI/86Y/9aj/9mt/+bJ7EGiPwAA" - "AZRJREFUeNrt28lug0AQhGHajrPv+/s/aVwpDlgE0gQ3t" - "qO/DhxihMg33VJ7JmmCVKSJlVJ4bZQ93Jl/zjJv+8tzcM" - "UVV1xxLXIlRfPAZptYrbf5YeW618PWyvG8w/g9ZwquuJ6" - "Y6+bbdY0rrifhSmrmgUulVXbVDq3H39Zy6Cf9+8c7JNM/" - "mXeY8+SMRmuIK6644oprkSupmQdulLhQdup1qJKmrmWmV" - "pb5NN9LUyddu7nnLYkrrrjiimuVK6mZB+6VuFbiXJk8v/" - "bnv0PVa+Yd5tdr/x7vCfqbgPsfV1xxxRXXKldSMw+8KPG" - "gxJWyU7WZE538p0vOr/lOm/q7dPf+bOVKvVXiUcEVV1xx" - "xbXMldTMA29KPCtxp7T6XpvxE6/9nm/l987mnG9l5u/8j" - "O4Ot9uTEq8KrrjiiiuuZa6kZh74UFpli3sO61btMfyHyW" - "Gv/RMs7wB67ne32/BdwRVXXHHFtcyV1MwDn0qrbHHvyPT" - "/Dsarla/R/1GpQydYPhf0bqC/A7jz7YkrrrjiimuVK6nI" - "F5dWoNvcLcs/AAAAAElFTkSuQmCC">>). - -logo_fill() -> - jlib:decode_base64(<<"iVBORw0KGgoAAAANSUhEUgAAAAYAAAA3BAMAAADdxCZzA" - "AAAAXNSR0IArs4c6QAAAB5QTFRF1nYO/ooC/o4O/pIS/p" - "4q/q5K/rpq/sqM/tam/ubGzn/S/AAAAEFJREFUCNdlw0s" - "RwCAQBUE+gSRHLGABC1jAAhbWAhZwC+88XdXOXb4UlFAr" - "SmwN5ekdJY2BkudEec1QvrVQ/r3xOlK9HsTvertmAAAAA" - "ElFTkSuQmCC">>). - -%%%================================== -%%%% process_admin - -process_admin(global, - #request{path = [], auth = {_, _, AJID}, - lang = Lang}) -> - make_xhtml((?H1GL((?T(<<"Administration">>)), <<"toc">>, - <<"Contents">>)) - ++ - [?XE(<<"ul">>, - [?LI([?ACT(MIU, MIN)]) - || {MIU, MIN} - <- get_menu_items(global, cluster, Lang, AJID)])], - global, Lang, AJID); -process_admin(Host, - #request{path = [], auth = {_, _Auth, AJID}, - lang = Lang}) -> - make_xhtml([?XCT(<<"h1">>, <<"Administration">>), - ?XE(<<"ul">>, - [?LI([?ACT(MIU, MIN)]) - || {MIU, MIN} - <- get_menu_items(Host, cluster, Lang, AJID)])], - Host, Lang, AJID); -process_admin(Host, - #request{path = [<<"style.css">>]}) -> - {200, - [{<<"Content-Type">>, <<"text/css">>}, last_modified(), - cache_control_public()], - css(Host)}; -process_admin(_Host, - #request{path = [<<"favicon.ico">>]}) -> - {200, - [{<<"Content-Type">>, <<"image/x-icon">>}, - last_modified(), cache_control_public()], - favicon()}; -process_admin(_Host, - #request{path = [<<"logo.png">>]}) -> - {200, - [{<<"Content-Type">>, <<"image/png">>}, last_modified(), - cache_control_public()], - logo()}; -process_admin(_Host, - #request{path = [<<"logo-fill.png">>]}) -> - {200, - [{<<"Content-Type">>, <<"image/png">>}, last_modified(), - cache_control_public()], - logo_fill()}; -process_admin(_Host, - #request{path = [<<"additions.js">>]}) -> - {200, - [{<<"Content-Type">>, <<"text/javascript">>}, - last_modified(), cache_control_public()], - additions_js()}; -process_admin(Host, - #request{path = [<<"acls-raw">>], q = Query, - auth = {_, _Auth, AJID}, lang = Lang}) -> - Res = case lists:keysearch(<<"acls">>, 1, Query) of - {value, {_, String}} -> - case erl_scan:string(binary_to_list(String)) of - {ok, Tokens, _} -> - case erl_parse:parse_term(Tokens) of - {ok, NewACLs} -> - case acl:add_list(Host, NewACLs, true) of - ok -> ok; - _ -> error - end; - _ -> error - end; - _ -> error - end; - _ -> nothing - end, - ACLs = lists:keysort(2, - ets:select(acl, - [{{acl, {'$1', Host}, '$2'}, [], - [{{acl, '$1', '$2'}}]}])), - {NumLines, ACLsP} = term_to_paragraph(ACLs, 80), - make_xhtml((?H1GL((?T(<<"Access Control Lists">>)), - <<"ACLDefinition">>, <<"ACL Definition">>)) - ++ - case Res of - ok -> [?XREST(<<"Submitted">>)]; - error -> [?XREST(<<"Bad format">>)]; - nothing -> [] - end - ++ - [?XAE(<<"form">>, - [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], - [?TEXTAREA(<<"acls">>, - (iolist_to_binary(integer_to_list(lists:max([16, - NumLines])))), - <<"80">>, <<(iolist_to_binary(ACLsP))/binary, ".">>), - ?BR, - ?INPUTT(<<"submit">>, <<"submit">>, <<"Submit">>)])], - Host, Lang, AJID); -process_admin(Host, - #request{method = Method, path = [<<"acls">>], - auth = {_, _Auth, AJID}, q = Query, lang = Lang}) -> - ?DEBUG("query: ~p", [Query]), - Res = case Method of - 'POST' -> - case catch acl_parse_query(Host, Query) of - {'EXIT', _} -> error; - NewACLs -> - ?INFO_MSG("NewACLs at ~s: ~p", [Host, NewACLs]), - case acl:add_list(Host, NewACLs, true) of - ok -> ?INFO_MSG("NewACLs: ok", []), ok; - _ -> error - end - end; - _ -> nothing - end, - ACLs = lists:keysort(2, - ets:select(acl, - [{{acl, {'$1', Host}, '$2'}, [], - [{{acl, '$1', '$2'}}]}])), - make_xhtml((?H1GL((?T(<<"Access Control Lists">>)), - <<"ACLDefinition">>, <<"ACL Definition">>)) - ++ - case Res of - ok -> [?XREST(<<"Submitted">>)]; - error -> [?XREST(<<"Bad format">>)]; - nothing -> [] - end - ++ - [?XE(<<"p">>, [?ACT(<<"../acls-raw/">>, <<"Raw">>)])] ++ - [?XAE(<<"form">>, - [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], - [acls_to_xhtml(ACLs), ?BR, - ?INPUTT(<<"submit">>, <<"delete">>, - <<"Delete Selected">>), - ?C(<<" ">>), - ?INPUTT(<<"submit">>, <<"submit">>, - <<"Submit">>)])], - Host, Lang, AJID); -process_admin(Host, - #request{path = [<<"access-raw">>], - auth = {_, _Auth, AJID}, q = Query, lang = Lang}) -> - SetAccess = fun (Rs) -> - mnesia:transaction(fun () -> - Os = mnesia:select(config, - [{{config, - {access, - '$1', - Host}, - '$2'}, - [], - ['$_']}]), - lists:foreach(fun (O) -> - mnesia:delete_object(O) - end, - Os), - lists:foreach(fun ({access, - Name, - Rules}) -> - mnesia:write({config, - {access, - Name, - Host}, - Rules}) - end, - Rs) - end) - end, - Res = case lists:keysearch(<<"access">>, 1, Query) of - {value, {_, String}} -> - case erl_scan:string(binary_to_list(String)) of - {ok, Tokens, _} -> - case erl_parse:parse_term(Tokens) of - {ok, Rs} -> - case SetAccess(Rs) of - {atomic, _} -> ok; - _ -> error - end; - _ -> error - end; - _ -> error - end; - _ -> nothing - end, - Access = ets:select(config, - [{{config, {access, '$1', Host}, '$2'}, [], - [{{access, '$1', '$2'}}]}]), - {NumLines, AccessP} = term_to_paragraph(lists:keysort(2,Access), 80), - make_xhtml((?H1GL((?T(<<"Access Rules">>)), - <<"AccessRights">>, <<"Access Rights">>)) - ++ - case Res of - ok -> [?XREST(<<"Submitted">>)]; - error -> [?XREST(<<"Bad format">>)]; - nothing -> [] - end - ++ - [?XAE(<<"form">>, - [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], - [?TEXTAREA(<<"access">>, - (iolist_to_binary(integer_to_list(lists:max([16, - NumLines])))), - <<"80">>, <<(iolist_to_binary(AccessP))/binary, ".">>), - ?BR, - ?INPUTT(<<"submit">>, <<"submit">>, <<"Submit">>)])], - Host, Lang, AJID); -process_admin(Host, - #request{method = Method, path = [<<"access">>], - q = Query, auth = {_, _Auth, AJID}, lang = Lang}) -> - ?DEBUG("query: ~p", [Query]), - Res = case Method of - 'POST' -> - case catch access_parse_query(Host, Query) of - {'EXIT', _} -> error; - ok -> ok - end; - _ -> nothing - end, - AccessRules = ets:select(config, - [{{config, {access, '$1', Host}, '$2'}, [], - [{{access, '$1', '$2'}}]}]), - make_xhtml((?H1GL((?T(<<"Access Rules">>)), - <<"AccessRights">>, <<"Access Rights">>)) - ++ - case Res of - ok -> [?XREST(<<"Submitted">>)]; - error -> [?XREST(<<"Bad format">>)]; - nothing -> [] - end - ++ - [?XE(<<"p">>, [?ACT(<<"../access-raw/">>, <<"Raw">>)])] - ++ - [?XAE(<<"form">>, - [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], - [access_rules_to_xhtml(AccessRules, Lang), ?BR, - ?INPUTT(<<"submit">>, <<"delete">>, - <<"Delete Selected">>)])], - Host, Lang, AJID); -process_admin(Host, - #request{path = [<<"access">>, SName], q = Query, - auth = {_, _Auth, AJID}, lang = Lang}) -> - ?DEBUG("query: ~p", [Query]), - Name = jlib:binary_to_atom(SName), - Res = case lists:keysearch(<<"rules">>, 1, Query) of - {value, {_, String}} -> - case parse_access_rule(String) of - {ok, Rs} -> - ejabberd_config:add_global_option({access, Name, Host}, - Rs), - ok; - _ -> error - end; - _ -> nothing - end, - Rules = case ejabberd_config:get_global_option( - {access, Name, Host}, fun(V) -> V end) - of - undefined -> []; - Rs1 -> Rs1 - end, - make_xhtml([?XC(<<"h1">>, - list_to_binary(io_lib:format( - ?T(<<"~s access rule configuration">>), - [SName])))] - ++ - case Res of - ok -> [?XREST(<<"Submitted">>)]; - error -> [?XREST(<<"Bad format">>)]; - nothing -> [] - end - ++ - [?XAE(<<"form">>, - [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], - [access_rule_to_xhtml(Rules), ?BR, - ?INPUTT(<<"submit">>, <<"submit">>, <<"Submit">>)])], - Host, Lang, AJID); -process_admin(global, - #request{path = [<<"vhosts">>], auth = {_, _Auth, AJID}, - lang = Lang}) -> - Res = list_vhosts(Lang, AJID), - make_xhtml((?H1GL((?T(<<"Virtual Hosts">>)), - <<"virtualhost">>, <<"Virtual Hosting">>)) - ++ Res, - global, Lang, AJID); -process_admin(Host, - #request{path = [<<"users">>], q = Query, - auth = {_, _Auth, AJID}, lang = Lang}) - when is_binary(Host) -> - Res = list_users(Host, Query, Lang, fun url_func/1), - make_xhtml([?XCT(<<"h1">>, <<"Users">>)] ++ Res, Host, - Lang, AJID); -process_admin(Host, - #request{path = [<<"users">>, Diap], - auth = {_, _Auth, AJID}, lang = Lang}) - when is_binary(Host) -> - Res = list_users_in_diapason(Host, Diap, Lang, - fun url_func/1), - make_xhtml([?XCT(<<"h1">>, <<"Users">>)] ++ Res, Host, - Lang, AJID); -process_admin(Host, - #request{path = [<<"online-users">>], - auth = {_, _Auth, AJID}, lang = Lang}) - when is_binary(Host) -> - Res = list_online_users(Host, Lang), - make_xhtml([?XCT(<<"h1">>, <<"Online Users">>)] ++ Res, - Host, Lang, AJID); -process_admin(Host, - #request{path = [<<"last-activity">>], - auth = {_, _Auth, AJID}, q = Query, lang = Lang}) - when is_binary(Host) -> - ?DEBUG("query: ~p", [Query]), - Month = case lists:keysearch(<<"period">>, 1, Query) of - {value, {_, Val}} -> Val; - _ -> <<"month">> - end, - Res = case lists:keysearch(<<"ordinary">>, 1, Query) of - {value, {_, _}} -> - list_last_activity(Host, Lang, false, Month); - _ -> list_last_activity(Host, Lang, true, Month) - end, - make_xhtml([?XCT(<<"h1">>, <<"Users Last Activity">>)] - ++ - [?XAE(<<"form">>, - [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], - [?CT(<<"Period: ">>), - ?XAE(<<"select">>, [{<<"name">>, <<"period">>}], - (lists:map(fun ({O, V}) -> - Sel = if O == Month -> - [{<<"selected">>, - <<"selected">>}]; - true -> [] - end, - ?XAC(<<"option">>, - (Sel ++ - [{<<"value">>, O}]), - V) - end, - [{<<"month">>, ?T(<<"Last month">>)}, - {<<"year">>, ?T(<<"Last year">>)}, - {<<"all">>, - ?T(<<"All activity">>)}]))), - ?C(<<" ">>), - ?INPUTT(<<"submit">>, <<"ordinary">>, - <<"Show Ordinary Table">>), - ?C(<<" ">>), - ?INPUTT(<<"submit">>, <<"integral">>, - <<"Show Integral Table">>)])] - ++ Res, - Host, Lang, AJID); -process_admin(Host, - #request{path = [<<"stats">>], auth = {_, _Auth, AJID}, - lang = Lang}) -> - Res = get_stats(Host, Lang), - make_xhtml([?XCT(<<"h1">>, <<"Statistics">>)] ++ Res, - Host, Lang, AJID); -process_admin(Host, - #request{path = [<<"user">>, U], - auth = {_, _Auth, AJID}, q = Query, lang = Lang}) -> - case ejabberd_auth:is_user_exists(U, Host) of - true -> - Res = user_info(U, Host, Query, Lang), - make_xhtml(Res, Host, Lang, AJID); - false -> - make_xhtml([?XCT(<<"h1">>, <<"Not Found">>)], Host, - Lang, AJID) - end; -process_admin(Host, - #request{path = [<<"nodes">>], auth = {_, _Auth, AJID}, - lang = Lang}) -> - Res = get_nodes(Lang), - make_xhtml(Res, Host, Lang, AJID); -process_admin(Host, - #request{path = [<<"node">>, SNode | NPath], - auth = {_, _Auth, AJID}, q = Query, lang = Lang}) -> - case search_running_node(SNode) of - false -> - make_xhtml([?XCT(<<"h1">>, <<"Node not found">>)], Host, - Lang, AJID); - Node -> - Res = get_node(Host, Node, NPath, Query, Lang), - make_xhtml(Res, Host, Node, Lang, AJID) - end; -%%%================================== -%%%% process_admin default case -process_admin(Host, - #request{lang = Lang, auth = {_, _Auth, AJID}} = - Request) -> - {Hook, Opts} = case Host of - global -> {webadmin_page_main, [Request]}; - Host -> {webadmin_page_host, [Host, Request]} - end, - case ejabberd_hooks:run_fold(Hook, Host, [], Opts) of - [] -> - setelement(1, - make_xhtml([?XC(<<"h1">>, <<"Not Found">>)], Host, Lang, - AJID), - 404); - Res -> make_xhtml(Res, Host, Lang, AJID) - end. - -%%%================================== -%%%% acl - -acls_to_xhtml(ACLs) -> - ?XAE(<<"table">>, [], - [?XE(<<"tbody">>, - (lists:map(fun ({acl, Name, Spec} = ACL) -> - SName = iolist_to_binary(atom_to_list(Name)), - ID = term_to_id(ACL), - ?XE(<<"tr">>, - ([?XE(<<"td">>, - [?INPUT(<<"checkbox">>, - <<"selected">>, ID)]), - ?XC(<<"td">>, SName)] - ++ acl_spec_to_xhtml(ID, Spec))) - end, - ACLs) - ++ - [?XE(<<"tr">>, - ([?X(<<"td">>), - ?XE(<<"td">>, - [?INPUT(<<"text">>, <<"namenew">>, <<"">>)])] - ++ acl_spec_to_xhtml(<<"new">>, {user, <<"">>})))]))]). - -acl_spec_to_text({user, U}) -> {user, U}; -acl_spec_to_text({server, S}) -> {server, S}; -acl_spec_to_text({user, U, S}) -> - {user, <<U/binary, "@", S/binary>>}; -acl_spec_to_text({user_regexp, RU}) -> - {user_regexp, RU}; -acl_spec_to_text({user_regexp, RU, S}) -> - {user_regexp, <<RU/binary, "@", S/binary>>}; -acl_spec_to_text({server_regexp, RS}) -> - {server_regexp, RS}; -acl_spec_to_text({node_regexp, RU, RS}) -> - {node_regexp, <<RU/binary, "@", RS/binary>>}; -acl_spec_to_text({user_glob, RU}) -> {user_glob, RU}; -acl_spec_to_text({user_glob, RU, S}) -> - {user_glob, <<RU/binary, "@", S/binary>>}; -acl_spec_to_text({server_glob, RS}) -> - {server_glob, RS}; -acl_spec_to_text({node_glob, RU, RS}) -> - {node_glob, <<RU/binary, "@", RS/binary>>}; -acl_spec_to_text(all) -> {all, <<"">>}; -acl_spec_to_text(Spec) -> {raw, term_to_string(Spec)}. - -acl_spec_to_xhtml(ID, Spec) -> - {Type, Str} = acl_spec_to_text(Spec), - [acl_spec_select(ID, Type), ?ACLINPUT(Str)]. - -acl_spec_select(ID, Opt) -> - ?XE(<<"td">>, - [?XAE(<<"select">>, - [{<<"name">>, <<"type", ID/binary>>}], - (lists:map(fun (O) -> - Sel = if O == Opt -> - [{<<"selected">>, - <<"selected">>}]; - true -> [] - end, - ?XAC(<<"option">>, - (Sel ++ - [{<<"value">>, - iolist_to_binary(atom_to_list(O))}]), - (iolist_to_binary(atom_to_list(O)))) - end, - [user, server, user_regexp, server_regexp, node_regexp, - user_glob, server_glob, node_glob, all, raw])))]). - -%% @spec (T::any()) -> StringLine::string() -term_to_string(T) -> - StringParagraph = - iolist_to_binary(io_lib:format("~1000000p", [T])), - ejabberd_regexp:greplace(StringParagraph, <<"\\n ">>, - <<"">>). - -%% @spec (T::any(), Cols::integer()) -> {NumLines::integer(), Paragraph::string()} -term_to_paragraph(T, Cols) -> - Paragraph = list_to_binary(erl_prettypr:format(erl_syntax:abstract(T), - [{paper, Cols}])), - FieldList = ejabberd_regexp:split(Paragraph, <<"\n">>), - NumLines = length(FieldList), - {NumLines, Paragraph}. - -term_to_id(T) -> jlib:encode_base64((term_to_binary(T))). - -acl_parse_query(Host, Query) -> - ACLs = ets:select(acl, - [{{acl, {'$1', Host}, '$2'}, [], - [{{acl, '$1', '$2'}}]}]), - case lists:keysearch(<<"submit">>, 1, Query) of - {value, _} -> acl_parse_submit(ACLs, Query); - _ -> - case lists:keysearch(<<"delete">>, 1, Query) of - {value, _} -> acl_parse_delete(ACLs, Query) - end - end. - -acl_parse_submit(ACLs, Query) -> - NewACLs = lists:map(fun ({acl, Name, Spec} = ACL) -> - ID = term_to_id(ACL), - case {lists:keysearch(<<"type", ID/binary>>, 1, - Query), - lists:keysearch(<<"value", ID/binary>>, 1, - Query)} - of - {{value, {_, T}}, {value, {_, V}}} -> - {Type, Str} = acl_spec_to_text(Spec), - case - {iolist_to_binary(atom_to_list(Type)), - Str} - of - {T, V} -> ACL; - _ -> - NewSpec = string_to_spec(T, V), - {acl, Name, NewSpec} - end; - _ -> ACL - end - end, - ACLs), - NewACL = case {lists:keysearch(<<"namenew">>, 1, Query), - lists:keysearch(<<"typenew">>, 1, Query), - lists:keysearch(<<"valuenew">>, 1, Query)} - of - {{value, {_, <<"">>}}, _, _} -> []; - {{value, {_, N}}, {value, {_, T}}, {value, {_, V}}} -> - NewName = jlib:binary_to_atom(N), - NewSpec = string_to_spec(T, V), - [{acl, NewName, NewSpec}]; - _ -> [] - end, - NewACLs ++ NewACL. - -string_to_spec(<<"user">>, Val) -> - string_to_spec2(user, Val); -string_to_spec(<<"server">>, Val) -> {server, Val}; -string_to_spec(<<"user_regexp">>, Val) -> - string_to_spec2(user_regexp, Val); -string_to_spec(<<"server_regexp">>, Val) -> - {server_regexp, Val}; -string_to_spec(<<"node_regexp">>, Val) -> - #jid{luser = U, lserver = S, resource = <<"">>} = - jlib:string_to_jid(Val), - {node_regexp, U, S}; -string_to_spec(<<"user_glob">>, Val) -> - string_to_spec2(user_glob, Val); -string_to_spec(<<"server_glob">>, Val) -> - {server_glob, Val}; -string_to_spec(<<"node_glob">>, Val) -> - #jid{luser = U, lserver = S, resource = <<"">>} = - jlib:string_to_jid(Val), - {node_glob, U, S}; -string_to_spec(<<"all">>, _) -> all; -string_to_spec(<<"raw">>, Val) -> - {ok, Tokens, _} = erl_scan:string(binary_to_list(<<Val/binary, ".">>)), - {ok, NewSpec} = erl_parse:parse_term(Tokens), - NewSpec. - -string_to_spec2(ACLName, Val) -> - #jid{luser = U, lserver = S, resource = <<"">>} = - jlib:string_to_jid(Val), - case U of - <<"">> -> {ACLName, S}; - _ -> {ACLName, U, S} - end. - -acl_parse_delete(ACLs, Query) -> - NewACLs = lists:filter(fun ({acl, _Name, _Spec} = - ACL) -> - ID = term_to_id(ACL), - not lists:member({<<"selected">>, ID}, Query) - end, - ACLs), - NewACLs. - -access_rules_to_xhtml(AccessRules, Lang) -> - ?XAE(<<"table">>, [], - [?XE(<<"tbody">>, - (lists:map(fun ({access, Name, Rules} = Access) -> - SName = iolist_to_binary(atom_to_list(Name)), - ID = term_to_id(Access), - ?XE(<<"tr">>, - [?XE(<<"td">>, - [?INPUT(<<"checkbox">>, - <<"selected">>, ID)]), - ?XE(<<"td">>, - [?AC(<<SName/binary, "/">>, SName)]), - ?XC(<<"td">>, (term_to_string(Rules)))]) - end, - lists:keysort(2,AccessRules)) - ++ - [?XE(<<"tr">>, - [?X(<<"td">>), - ?XE(<<"td">>, - [?INPUT(<<"text">>, <<"namenew">>, <<"">>)]), - ?XE(<<"td">>, - [?INPUTT(<<"submit">>, <<"addnew">>, - <<"Add New">>)])])]))]). - -access_parse_query(Host, Query) -> - AccessRules = ets:select(config, - [{{config, {access, '$1', Host}, '$2'}, [], - [{{access, '$1', '$2'}}]}]), - case lists:keysearch(<<"addnew">>, 1, Query) of - {value, _} -> - access_parse_addnew(AccessRules, Host, Query); - _ -> - case lists:keysearch(<<"delete">>, 1, Query) of - {value, _} -> - access_parse_delete(AccessRules, Host, Query) - end - end. - -access_parse_addnew(_AccessRules, Host, Query) -> - case lists:keysearch(<<"namenew">>, 1, Query) of - {value, {_, String}} when String /= <<"">> -> - Name = jlib:binary_to_atom(String), - ejabberd_config:add_global_option({access, Name, Host}, - []), - ok - end. - -access_parse_delete(AccessRules, Host, Query) -> - lists:foreach(fun ({access, Name, _Rules} = - AccessRule) -> - ID = term_to_id(AccessRule), - case lists:member({<<"selected">>, ID}, Query) of - true -> - mnesia:transaction(fun () -> - mnesia:delete({config, - {access, - Name, - Host}}) - end); - _ -> ok - end - end, - AccessRules), - ok. - -access_rule_to_xhtml(Rules) -> - Text = lists:flatmap(fun ({Access, ACL} = _Rule) -> - SAccess = element_to_list(Access), - SACL = atom_to_list(ACL), - [SAccess, " \t", SACL, "\n"] - end, - Rules), - ?XAC(<<"textarea">>, - [{<<"name">>, <<"rules">>}, {<<"rows">>, <<"16">>}, - {<<"cols">>, <<"80">>}], - list_to_binary(Text)). - -parse_access_rule(Text) -> - Strings = str:tokens(Text, <<"\r\n">>), - case catch lists:flatmap(fun (String) -> - case str:tokens(String, <<" \t">>) of - [Access, ACL] -> - [{list_to_element(Access), - jlib:binary_to_atom(ACL)}]; - [] -> [] - end - end, - Strings) - of - {'EXIT', _Reason} -> error; - Rs -> {ok, Rs} - end. - -%%%================================== -%%%% list_vhosts - -list_vhosts(Lang, JID) -> - Hosts = (?MYHOSTS), - HostsAllowed = lists:filter(fun (Host) -> - is_acl_match(Host, - [configure, webadmin_view], - JID) - end, - Hosts), - list_vhosts2(Lang, HostsAllowed). - -list_vhosts2(Lang, Hosts) -> - SHosts = lists:sort(Hosts), - [?XE(<<"table">>, - [?XE(<<"thead">>, - [?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Host">>), - ?XCT(<<"td">>, <<"Registered Users">>), - ?XCT(<<"td">>, <<"Online Users">>)])]), - ?XE(<<"tbody">>, - (lists:map(fun (Host) -> - OnlineUsers = - length(ejabberd_sm:get_vh_session_list(Host)), - RegisteredUsers = - ejabberd_auth:get_vh_registered_users_number(Host), - ?XE(<<"tr">>, - [?XE(<<"td">>, - [?AC(<<"../server/", Host/binary, - "/">>, - Host)]), - ?XC(<<"td">>, - (pretty_string_int(RegisteredUsers))), - ?XC(<<"td">>, - (pretty_string_int(OnlineUsers)))]) - end, - SHosts)))])]. - -%%%================================== -%%%% list_users - -list_users(Host, Query, Lang, URLFunc) -> - Res = list_users_parse_query(Query, Host), - Users = ejabberd_auth:get_vh_registered_users(Host), - SUsers = lists:sort([{S, U} || {U, S} <- Users]), - FUsers = case length(SUsers) of - N when N =< 100 -> - [list_given_users(Host, SUsers, <<"../">>, Lang, - URLFunc)]; - N -> - NParts = trunc(math:sqrt(N * 6.17999999999999993783e-1)) - + 1, - M = trunc(N / NParts) + 1, - lists:flatmap(fun (K) -> - L = K + M - 1, - Last = if L < N -> - su_to_list(lists:nth(L, - SUsers)); - true -> - su_to_list(lists:last(SUsers)) - end, - Name = <<(su_to_list(lists:nth(K, - SUsers)))/binary, - $\s, 226, 128, 148, $\s, - Last/binary>>, - [?AC((URLFunc({user_diapason, K, L})), - Name), - ?BR] - end, - lists:seq(1, N, M)) - end, - case Res of -%% Parse user creation query and try register: - ok -> [?XREST(<<"Submitted">>)]; - error -> [?XREST(<<"Bad format">>)]; - nothing -> [] - end - ++ - [?XAE(<<"form">>, - [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], - ([?XE(<<"table">>, - [?XE(<<"tr">>, - [?XC(<<"td">>, <<(?T(<<"User">>))/binary, ":">>), - ?XE(<<"td">>, - [?INPUT(<<"text">>, <<"newusername">>, <<"">>)]), - ?XE(<<"td">>, [?C(<<" @ ", Host/binary>>)])]), - ?XE(<<"tr">>, - [?XC(<<"td">>, <<(?T(<<"Password">>))/binary, ":">>), - ?XE(<<"td">>, - [?INPUT(<<"password">>, <<"newuserpassword">>, - <<"">>)]), - ?X(<<"td">>)]), - ?XE(<<"tr">>, - [?X(<<"td">>), - ?XAE(<<"td">>, [{<<"class">>, <<"alignright">>}], - [?INPUTT(<<"submit">>, <<"addnewuser">>, - <<"Add User">>)]), - ?X(<<"td">>)])]), - ?P] - ++ FUsers))]. - -list_users_parse_query(Query, Host) -> - case lists:keysearch(<<"addnewuser">>, 1, Query) of - {value, _} -> - {value, {_, Username}} = - lists:keysearch(<<"newusername">>, 1, Query), - {value, {_, Password}} = - lists:keysearch(<<"newuserpassword">>, 1, Query), - case jlib:string_to_jid(<<Username/binary, "@", - Host/binary>>) - of - error -> error; - #jid{user = User, server = Server} -> - case ejabberd_auth:try_register(User, Server, Password) - of - {error, _Reason} -> error; - _ -> ok - end - end; - false -> nothing - end. - -list_users_in_diapason(Host, Diap, Lang, URLFunc) -> - Users = ejabberd_auth:get_vh_registered_users(Host), - SUsers = lists:sort([{S, U} || {U, S} <- Users]), - [S1, S2] = ejabberd_regexp:split(Diap, <<"-">>), - N1 = jlib:binary_to_integer(S1), - N2 = jlib:binary_to_integer(S2), - Sub = lists:sublist(SUsers, N1, N2 - N1 + 1), - [list_given_users(Host, Sub, <<"../../">>, Lang, - URLFunc)]. - -list_given_users(Host, Users, Prefix, Lang, URLFunc) -> - ModOffline = get_offlinemsg_module(Host), - ?XE(<<"table">>, - [?XE(<<"thead">>, - [?XE(<<"tr">>, - [?XCT(<<"td">>, <<"User">>), - ?XCT(<<"td">>, <<"Offline Messages">>), - ?XCT(<<"td">>, <<"Last Activity">>)])]), - ?XE(<<"tbody">>, - (lists:map(fun (_SU = {Server, User}) -> - US = {User, Server}, - QueueLenStr = get_offlinemsg_length(ModOffline, - User, - Server), - FQueueLen = [?AC((URLFunc({users_queue, Prefix, - User, Server})), - QueueLenStr)], - FLast = case - ejabberd_sm:get_user_resources(User, - Server) - of - [] -> - case mod_last:get_last_info(User, - Server) - of - not_found -> ?T(<<"Never">>); - {ok, Shift, _Status} -> - TimeStamp = {Shift div - 1000000, - Shift rem - 1000000, - 0}, - {{Year, Month, Day}, - {Hour, Minute, Second}} = - calendar:now_to_local_time(TimeStamp), - iolist_to_binary(io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w", - [Year, - Month, - Day, - Hour, - Minute, - Second])) - end; - _ -> ?T(<<"Online">>) - end, - ?XE(<<"tr">>, - [?XE(<<"td">>, - [?AC((URLFunc({user, Prefix, - ejabberd_http:url_encode(User), - Server})), - (us_to_list(US)))]), - ?XE(<<"td">>, FQueueLen), - ?XC(<<"td">>, FLast)]) - end, - Users)))]). - -get_offlinemsg_length(ModOffline, User, Server) -> - case ModOffline of - none -> <<"disabled">>; - _ -> - pretty_string_int(ModOffline:get_queue_length(User, - Server)) - end. - -get_offlinemsg_module(Server) -> - case gen_mod:is_loaded(Server, mod_offline) of - true -> mod_offline; - false -> none - end. - -get_lastactivity_menuitem_list(Server) -> - case gen_mod:db_type(Server, mod_last) of - mnesia -> [{<<"last-activity">>, <<"Last Activity">>}]; - _ -> [] - end. - -us_to_list({User, Server}) -> - jlib:jid_to_string({User, Server, <<"">>}). - -su_to_list({Server, User}) -> - jlib:jid_to_string({User, Server, <<"">>}). - -%%%================================== -%%%% get_stats - -get_stats(global, Lang) -> - OnlineUsers = mnesia:table_info(session, size), - RegisteredUsers = lists:foldl(fun (Host, Total) -> - ejabberd_auth:get_vh_registered_users_number(Host) - + Total - end, - 0, ?MYHOSTS), - S2SConns = ejabberd_s2s:dirty_get_connections(), - S2SConnections = length(S2SConns), - S2SServers = length(lists:usort([element(2, C) - || C <- S2SConns])), - [?XAE(<<"table">>, [], - [?XE(<<"tbody">>, - [?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Registered Users:">>), - ?XC(<<"td">>, (pretty_string_int(RegisteredUsers)))]), - ?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Online Users:">>), - ?XC(<<"td">>, (pretty_string_int(OnlineUsers)))]), - ?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Outgoing s2s Connections:">>), - ?XC(<<"td">>, (pretty_string_int(S2SConnections)))]), - ?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Outgoing s2s Servers:">>), - ?XC(<<"td">>, (pretty_string_int(S2SServers)))])])])]; -get_stats(Host, Lang) -> - OnlineUsers = - length(ejabberd_sm:get_vh_session_list(Host)), - RegisteredUsers = - ejabberd_auth:get_vh_registered_users_number(Host), - [?XAE(<<"table">>, [], - [?XE(<<"tbody">>, - [?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Registered Users:">>), - ?XC(<<"td">>, (pretty_string_int(RegisteredUsers)))]), - ?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Online Users:">>), - ?XC(<<"td">>, (pretty_string_int(OnlineUsers)))])])])]. - -list_online_users(Host, _Lang) -> - Users = [{S, U} - || {U, S, _R} <- ejabberd_sm:get_vh_session_list(Host)], - SUsers = lists:usort(Users), - lists:flatmap(fun ({_S, U} = SU) -> - [?AC(<<"../user/", - (ejabberd_http:url_encode(U))/binary, "/">>, - (su_to_list(SU))), - ?BR] - end, - SUsers). - -user_info(User, Server, Query, Lang) -> - LServer = jlib:nameprep(Server), - US = {jlib:nodeprep(User), LServer}, - Res = user_parse_query(User, Server, Query), - Resources = ejabberd_sm:get_user_resources(User, - Server), - FResources = - case Resources of - [] -> [?CT(<<"None">>)]; - _ -> - [?XE(<<"ul">>, - (lists:map( - fun (R) -> - FIP = case - ejabberd_sm:get_user_info(User, - Server, - R) - of - offline -> <<"">>; - Info - when - is_list(Info) -> - Node = - proplists:get_value(node, - Info), - Conn = - proplists:get_value(conn, - Info), - {IP, Port} = - proplists:get_value(ip, - Info), - ConnS = case Conn of - c2s -> - <<"plain">>; - c2s_tls -> - <<"tls">>; - c2s_compressed -> - <<"zlib">>; - c2s_compressed_tls -> - <<"tls+zlib">>; - http_bind -> - <<"http-bind">>; - http_poll -> - <<"http-poll">> - end, - <<" (", ConnS/binary, - "://", - (jlib:ip_to_list(IP))/binary, - ":", - (jlib:integer_to_binary(Port))/binary, - "#", - (jlib:atom_to_binary(Node))/binary, - ")">> - end, - ?LI([?C((<<R/binary, FIP/binary>>))]) - end, - lists:sort(Resources))))] - end, - Password = ejabberd_auth:get_password_s(User, Server), - FPassword = [?INPUT(<<"password">>, <<"password">>, - Password), - ?C(<<" ">>), - ?INPUTT(<<"submit">>, <<"chpassword">>, - <<"Change Password">>)], - UserItems = ejabberd_hooks:run_fold(webadmin_user, - LServer, [], [User, Server, Lang]), - LastActivity = case ejabberd_sm:get_user_resources(User, - Server) - of - [] -> - case mod_last:get_last_info(User, Server) of - not_found -> ?T(<<"Never">>); - {ok, Shift, _Status} -> - TimeStamp = {Shift div 1000000, - Shift rem 1000000, 0}, - {{Year, Month, Day}, {Hour, Minute, Second}} = - calendar:now_to_local_time(TimeStamp), - iolist_to_binary(io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w", - [Year, Month, Day, - Hour, Minute, - Second])) - end; - _ -> ?T(<<"Online">>) - end, - [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"User ~s">>), - [us_to_list(US)])))] - ++ - case Res of - ok -> [?XREST(<<"Submitted">>)]; - error -> [?XREST(<<"Bad format">>)]; - nothing -> [] - end - ++ - [?XAE(<<"form">>, - [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], - ([?XCT(<<"h3">>, <<"Connected Resources:">>)] ++ - FResources ++ - [?XCT(<<"h3">>, <<"Password:">>)] ++ - FPassword ++ - [?XCT(<<"h3">>, <<"Last Activity">>)] ++ - [?C(LastActivity)] ++ - UserItems ++ - [?P, - ?INPUTT(<<"submit">>, <<"removeuser">>, - <<"Remove User">>)]))]. - -user_parse_query(User, Server, Query) -> - lists:foldl(fun ({Action, _Value}, Acc) - when Acc == nothing -> - user_parse_query1(Action, User, Server, Query); - ({_Action, _Value}, Acc) -> Acc - end, - nothing, Query). - -user_parse_query1(<<"password">>, _User, _Server, - _Query) -> - nothing; -user_parse_query1(<<"chpassword">>, User, Server, - Query) -> - case lists:keysearch(<<"password">>, 1, Query) of - {value, {_, Password}} -> - ejabberd_auth:set_password(User, Server, Password), ok; - _ -> error - end; -user_parse_query1(<<"removeuser">>, User, Server, - _Query) -> - ejabberd_auth:remove_user(User, Server), ok; -user_parse_query1(Action, User, Server, Query) -> - case ejabberd_hooks:run_fold(webadmin_user_parse_query, - Server, [], [Action, User, Server, Query]) - of - [] -> nothing; - Res -> Res - end. - -list_last_activity(Host, Lang, Integral, Period) -> - {MegaSecs, Secs, _MicroSecs} = now(), - TimeStamp = MegaSecs * 1000000 + Secs, - case Period of - <<"all">> -> TS = 0, Days = infinity; - <<"year">> -> TS = TimeStamp - 366 * 86400, Days = 366; - _ -> TS = TimeStamp - 31 * 86400, Days = 31 - end, - case catch mnesia:dirty_select(last_activity, - [{{last_activity, {'_', Host}, '$1', '_'}, - [{'>', '$1', TS}], - [{trunc, - {'/', {'-', TimeStamp, '$1'}, 86400}}]}]) - of - {'EXIT', _Reason} -> []; - Vals -> - Hist = histogram(Vals, Integral), - if Hist == [] -> [?CT(<<"No Data">>)]; - true -> - Left = if Days == infinity -> 0; - true -> Days - length(Hist) - end, - Tail = if Integral -> - lists:duplicate(Left, lists:last(Hist)); - true -> lists:duplicate(Left, 0) - end, - Max = lists:max(Hist), - [?XAE(<<"ol">>, - [{<<"id">>, <<"lastactivity">>}, - {<<"start">>, <<"0">>}], - [?XAE(<<"li">>, - [{<<"style">>, - <<"width:", - (iolist_to_binary(integer_to_list(trunc(90 * V - / - Max))))/binary, - "%;">>}], - [{xmlcdata, pretty_string_int(V)}]) - || V <- Hist ++ Tail])] - end - end. - -histogram(Values, Integral) -> - histogram(lists:sort(Values), Integral, 0, 0, []). - -histogram([H | T], Integral, Current, Count, Hist) - when Current == H -> - histogram(T, Integral, Current, Count + 1, Hist); -histogram([H | _] = Values, Integral, Current, Count, - Hist) - when Current < H -> - if Integral -> - histogram(Values, Integral, Current + 1, Count, - [Count | Hist]); - true -> - histogram(Values, Integral, Current + 1, 0, - [Count | Hist]) - end; -histogram([], _Integral, _Current, Count, Hist) -> - if Count > 0 -> lists:reverse([Count | Hist]); - true -> lists:reverse(Hist) - end. - -%%%================================== -%%%% get_nodes - -get_nodes(Lang) -> - RunningNodes = mnesia:system_info(running_db_nodes), - StoppedNodes = lists:usort(mnesia:system_info(db_nodes) - ++ mnesia:system_info(extra_db_nodes)) - -- RunningNodes, - FRN = if RunningNodes == [] -> ?CT(<<"None">>); - true -> - ?XE(<<"ul">>, - (lists:map(fun (N) -> - S = iolist_to_binary(atom_to_list(N)), - ?LI([?AC(<<"../node/", S/binary, "/">>, - S)]) - end, - lists:sort(RunningNodes)))) - end, - FSN = if StoppedNodes == [] -> ?CT(<<"None">>); - true -> - ?XE(<<"ul">>, - (lists:map(fun (N) -> - S = iolist_to_binary(atom_to_list(N)), - ?LI([?C(S)]) - end, - lists:sort(StoppedNodes)))) - end, - [?XCT(<<"h1">>, <<"Nodes">>), - ?XCT(<<"h3">>, <<"Running Nodes">>), FRN, - ?XCT(<<"h3">>, <<"Stopped Nodes">>), FSN]. - -search_running_node(SNode) -> - search_running_node(SNode, - mnesia:system_info(running_db_nodes)). - -search_running_node(_, []) -> false; -search_running_node(SNode, [Node | Nodes]) -> - case iolist_to_binary(atom_to_list(Node)) of - SNode -> Node; - _ -> search_running_node(SNode, Nodes) - end. - -get_node(global, Node, [], Query, Lang) -> - Res = node_parse_query(Node, Query), - Base = get_base_path(global, Node), - MenuItems2 = make_menu_items(global, Node, Base, Lang), - [?XC(<<"h1">>, - list_to_binary(io_lib:format(?T(<<"Node ~p">>), [Node])))] - ++ - case Res of - ok -> [?XREST(<<"Submitted">>)]; - error -> [?XREST(<<"Bad format">>)]; - nothing -> [] - end - ++ - [?XE(<<"ul">>, - ([?LI([?ACT(<<Base/binary, "db/">>, <<"Database">>)]), - ?LI([?ACT(<<Base/binary, "backup/">>, <<"Backup">>)]), - ?LI([?ACT(<<Base/binary, "ports/">>, - <<"Listened Ports">>)]), - ?LI([?ACT(<<Base/binary, "stats/">>, - <<"Statistics">>)]), - ?LI([?ACT(<<Base/binary, "update/">>, <<"Update">>)])] - ++ MenuItems2)), - ?XAE(<<"form">>, - [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], - [?INPUTT(<<"submit">>, <<"restart">>, <<"Restart">>), - ?C(<<" ">>), - ?INPUTT(<<"submit">>, <<"stop">>, <<"Stop">>)])]; -get_node(Host, Node, [], _Query, Lang) -> - Base = get_base_path(Host, Node), - MenuItems2 = make_menu_items(Host, Node, Base, Lang), - [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"Node ~p">>), [Node]))), - ?XE(<<"ul">>, - ([?LI([?ACT(<<Base/binary, "modules/">>, - <<"Modules">>)])] - ++ MenuItems2))]; -get_node(global, Node, [<<"db">>], Query, Lang) -> - case rpc:call(Node, mnesia, system_info, [tables]) of - {badrpc, _Reason} -> - [?XCT(<<"h1">>, <<"RPC Call Error">>)]; - Tables -> - ResS = case node_db_parse_query(Node, Tables, Query) of - nothing -> []; - ok -> [?XREST(<<"Submitted">>)] - end, - STables = lists:sort(Tables), - Rows = lists:map(fun (Table) -> - STable = - iolist_to_binary(atom_to_list(Table)), - TInfo = case rpc:call(Node, mnesia, - table_info, - [Table, all]) - of - {badrpc, _} -> []; - I -> I - end, - {Type, Size, Memory} = case - {lists:keysearch(storage_type, - 1, - TInfo), - lists:keysearch(size, - 1, - TInfo), - lists:keysearch(memory, - 1, - TInfo)} - of - {{value, - {storage_type, - T}}, - {value, {size, S}}, - {value, - {memory, M}}} -> - {T, S, M}; - _ -> {unknown, 0, 0} - end, - ?XE(<<"tr">>, - [?XC(<<"td">>, STable), - ?XE(<<"td">>, - [db_storage_select(STable, Type, - Lang)]), - ?XAC(<<"td">>, - [{<<"class">>, <<"alignright">>}], - (pretty_string_int(Size))), - ?XAC(<<"td">>, - [{<<"class">>, <<"alignright">>}], - (pretty_string_int(Memory)))]) - end, - STables), - [?XC(<<"h1">>, - list_to_binary(io_lib:format(?T(<<"Database Tables at ~p">>), - [Node])) - )] - ++ - ResS ++ - [?XAE(<<"form">>, - [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], - [?XAE(<<"table">>, [], - [?XE(<<"thead">>, - [?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Name">>), - ?XCT(<<"td">>, <<"Storage Type">>), - ?XCT(<<"td">>, <<"Elements">>), - ?XCT(<<"td">>, <<"Memory">>)])]), - ?XE(<<"tbody">>, - (Rows ++ - [?XE(<<"tr">>, - [?XAE(<<"td">>, - [{<<"colspan">>, <<"4">>}, - {<<"class">>, <<"alignright">>}], - [?INPUTT(<<"submit">>, - <<"submit">>, - <<"Submit">>)])])]))])])] - end; -get_node(global, Node, [<<"backup">>], Query, Lang) -> - HomeDirRaw = case {os:getenv("HOME"), os:type()} of - {EnvHome, _} when is_list(EnvHome) -> list_to_binary(EnvHome); - {false, {win32, _Osname}} -> <<"C:/">>; - {false, _} -> <<"/tmp/">> - end, - HomeDir = filename:nativename(HomeDirRaw), - ResS = case node_backup_parse_query(Node, Query) of - nothing -> []; - ok -> [?XREST(<<"Submitted">>)]; - {error, Error} -> - [?XRES(<<(?T(<<"Error">>))/binary, ": ", - (list_to_binary(io_lib:format("~p", [Error])))/binary>>)] - end, - (?H1GL(list_to_binary(io_lib:format(?T(<<"Backup of ~p">>), [Node])), - <<"list-eja-commands">>, - <<"List of ejabberd Commands">>)) - ++ - ResS ++ - [?XCT(<<"p">>, - <<"Please note that these options will " - "only backup the builtin Mnesia database. " - "If you are using the ODBC module, you " - "also need to backup your SQL database " - "separately.">>), - ?XAE(<<"form">>, - [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], - [?XAE(<<"table">>, [], - [?XE(<<"tbody">>, - [?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Store binary backup:">>), - ?XE(<<"td">>, - [?INPUT(<<"text">>, <<"storepath">>, - (filename:join(HomeDir, - "ejabberd.backup")))]), - ?XE(<<"td">>, - [?INPUTT(<<"submit">>, <<"store">>, - <<"OK">>)])]), - ?XE(<<"tr">>, - [?XCT(<<"td">>, - <<"Restore binary backup immediately:">>), - ?XE(<<"td">>, - [?INPUT(<<"text">>, <<"restorepath">>, - (filename:join(HomeDir, - "ejabberd.backup")))]), - ?XE(<<"td">>, - [?INPUTT(<<"submit">>, <<"restore">>, - <<"OK">>)])]), - ?XE(<<"tr">>, - [?XCT(<<"td">>, - <<"Restore binary backup after next ejabberd " - "restart (requires less memory):">>), - ?XE(<<"td">>, - [?INPUT(<<"text">>, <<"fallbackpath">>, - (filename:join(HomeDir, - "ejabberd.backup")))]), - ?XE(<<"td">>, - [?INPUTT(<<"submit">>, <<"fallback">>, - <<"OK">>)])]), - ?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Store plain text backup:">>), - ?XE(<<"td">>, - [?INPUT(<<"text">>, <<"dumppath">>, - (filename:join(HomeDir, - "ejabberd.dump")))]), - ?XE(<<"td">>, - [?INPUTT(<<"submit">>, <<"dump">>, - <<"OK">>)])]), - ?XE(<<"tr">>, - [?XCT(<<"td">>, - <<"Restore plain text backup immediately:">>), - ?XE(<<"td">>, - [?INPUT(<<"text">>, <<"loadpath">>, - (filename:join(HomeDir, - "ejabberd.dump")))]), - ?XE(<<"td">>, - [?INPUTT(<<"submit">>, <<"load">>, - <<"OK">>)])]), - ?XE(<<"tr">>, - [?XCT(<<"td">>, - <<"Import users data from a PIEFXIS file " - "(XEP-0227):">>), - ?XE(<<"td">>, - [?INPUT(<<"text">>, - <<"import_piefxis_filepath">>, - (filename:join(HomeDir, - "users.xml")))]), - ?XE(<<"td">>, - [?INPUTT(<<"submit">>, - <<"import_piefxis_file">>, - <<"OK">>)])]), - ?XE(<<"tr">>, - [?XCT(<<"td">>, - <<"Export data of all users in the server " - "to PIEFXIS files (XEP-0227):">>), - ?XE(<<"td">>, - [?INPUT(<<"text">>, - <<"export_piefxis_dirpath">>, - HomeDir)]), - ?XE(<<"td">>, - [?INPUTT(<<"submit">>, - <<"export_piefxis_dir">>, - <<"OK">>)])]), - ?XE(<<"tr">>, - [?XE(<<"td">>, - [?CT(<<"Export data of users in a host to PIEFXIS " - "files (XEP-0227):">>), - ?C(<<" ">>), - ?INPUT(<<"text">>, - <<"export_piefxis_host_dirhost">>, - (?MYNAME))]), - ?XE(<<"td">>, - [?INPUT(<<"text">>, - <<"export_piefxis_host_dirpath">>, - HomeDir)]), - ?XE(<<"td">>, - [?INPUTT(<<"submit">>, - <<"export_piefxis_host_dir">>, - <<"OK">>)])]), - ?XE(<<"tr">>, - [?XE(<<"td">>, - [?CT(<<"Export all tables as SQL queries " - "to a file:">>), - ?C(<<" ">>), - ?INPUT(<<"text">>, - <<"export_sql_filehost">>, - (?MYNAME))]), - ?XE(<<"td">>, - [?INPUT(<<"text">>, - <<"export_sql_filepath">>, - (filename:join(HomeDir, - "db.sql")))]), - ?XE(<<"td">>, - [?INPUTT(<<"submit">>, <<"export_sql_file">>, - <<"OK">>)])]), - ?XE(<<"tr">>, - [?XCT(<<"td">>, - <<"Import user data from jabberd14 spool " - "file:">>), - ?XE(<<"td">>, - [?INPUT(<<"text">>, <<"import_filepath">>, - (filename:join(HomeDir, - "user1.xml")))]), - ?XE(<<"td">>, - [?INPUTT(<<"submit">>, <<"import_file">>, - <<"OK">>)])]), - ?XE(<<"tr">>, - [?XCT(<<"td">>, - <<"Import users data from jabberd14 spool " - "directory:">>), - ?XE(<<"td">>, - [?INPUT(<<"text">>, <<"import_dirpath">>, - <<"/var/spool/jabber/">>)]), - ?XE(<<"td">>, - [?INPUTT(<<"submit">>, <<"import_dir">>, - <<"OK">>)])])])])])]; -get_node(global, Node, [<<"ports">>], Query, Lang) -> - Ports = rpc:call(Node, ejabberd_config, - get_local_option, [listen, - {ejabberd_listener, validate_cfg}, - []]), - Res = case catch node_ports_parse_query(Node, Ports, - Query) - of - submitted -> ok; - {'EXIT', _Reason} -> error; - {is_added, ok} -> ok; - {is_added, {error, Reason}} -> - {error, iolist_to_binary(io_lib:format("~p", [Reason]))}; - _ -> nothing - end, - NewPorts = lists:sort(rpc:call(Node, ejabberd_config, - get_local_option, - [listen, - {ejabberd_listener, validate_cfg}, - []])), - H1String = <<(?T(<<"Listened Ports at ">>))/binary, - (iolist_to_binary(atom_to_list(Node)))/binary>>, - (?H1GL(H1String, <<"listened">>, <<"Listening Ports">>)) - ++ - case Res of - ok -> [?XREST(<<"Submitted">>)]; - error -> [?XREST(<<"Bad format">>)]; - {error, ReasonT} -> - [?XRES(<<(?T(<<"Error">>))/binary, ": ", - ReasonT/binary>>)]; - nothing -> [] - end - ++ - [?XAE(<<"form">>, - [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], - [node_ports_to_xhtml(NewPorts, Lang)])]; -get_node(Host, Node, [<<"modules">>], Query, Lang) - when is_binary(Host) -> - Modules = rpc:call(Node, gen_mod, - loaded_modules_with_opts, [Host]), - Res = case catch node_modules_parse_query(Host, Node, - Modules, Query) - of - submitted -> ok; - {'EXIT', Reason} -> ?INFO_MSG("~p~n", [Reason]), error; - _ -> nothing - end, - NewModules = lists:sort(rpc:call(Node, gen_mod, - loaded_modules_with_opts, [Host])), - H1String = list_to_binary(io_lib:format(?T(<<"Modules at ~p">>), [Node])), - (?H1GL(H1String, <<"modoverview">>, - <<"Modules Overview">>)) - ++ - case Res of - ok -> [?XREST(<<"Submitted">>)]; - error -> [?XREST(<<"Bad format">>)]; - nothing -> [] - end - ++ - [?XAE(<<"form">>, - [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], - [node_modules_to_xhtml(NewModules, Lang)])]; -get_node(global, Node, [<<"stats">>], _Query, Lang) -> - UpTime = rpc:call(Node, erlang, statistics, - [wall_clock]), - UpTimeS = list_to_binary(io_lib:format("~.3f", - [element(1, UpTime) / 1000])), - CPUTime = rpc:call(Node, erlang, statistics, [runtime]), - CPUTimeS = list_to_binary(io_lib:format("~.3f", - [element(1, CPUTime) / 1000])), - OnlineUsers = mnesia:table_info(session, size), - TransactionsCommitted = rpc:call(Node, mnesia, - system_info, [transaction_commits]), - TransactionsAborted = rpc:call(Node, mnesia, - system_info, [transaction_failures]), - TransactionsRestarted = rpc:call(Node, mnesia, - system_info, [transaction_restarts]), - TransactionsLogged = rpc:call(Node, mnesia, system_info, - [transaction_log_writes]), - [?XC(<<"h1">>, - list_to_binary(io_lib:format(?T(<<"Statistics of ~p">>), [Node]))), - ?XAE(<<"table">>, [], - [?XE(<<"tbody">>, - [?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Uptime:">>), - ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}], - UpTimeS)]), - ?XE(<<"tr">>, - [?XCT(<<"td">>, <<"CPU Time:">>), - ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}], - CPUTimeS)]), - ?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Online Users:">>), - ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}], - (pretty_string_int(OnlineUsers)))]), - ?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Transactions Committed:">>), - ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}], - (pretty_string_int(TransactionsCommitted)))]), - ?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Transactions Aborted:">>), - ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}], - (pretty_string_int(TransactionsAborted)))]), - ?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Transactions Restarted:">>), - ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}], - (pretty_string_int(TransactionsRestarted)))]), - ?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Transactions Logged:">>), - ?XAC(<<"td">>, [{<<"class">>, <<"alignright">>}], - (pretty_string_int(TransactionsLogged)))])])])]; -get_node(global, Node, [<<"update">>], Query, Lang) -> - rpc:call(Node, code, purge, [ejabberd_update]), - Res = node_update_parse_query(Node, Query), - rpc:call(Node, code, load_file, [ejabberd_update]), - {ok, _Dir, UpdatedBeams, Script, LowLevelScript, - Check} = - rpc:call(Node, ejabberd_update, update_info, []), - Mods = case UpdatedBeams of - [] -> ?CT(<<"None">>); - _ -> - BeamsLis = lists:map(fun (Beam) -> - BeamString = - iolist_to_binary(atom_to_list(Beam)), - ?LI([?INPUT(<<"checkbox">>, - <<"selected">>, - BeamString), - ?C(BeamString)]) - end, - UpdatedBeams), - SelectButtons = [?BR, - ?INPUTATTRS(<<"button">>, <<"selectall">>, - <<"Select All">>, - [{<<"onClick">>, - <<"selectAll()">>}]), - ?C(<<" ">>), - ?INPUTATTRS(<<"button">>, <<"unselectall">>, - <<"Unselect All">>, - [{<<"onClick">>, - <<"unSelectAll()">>}])], - ?XAE(<<"ul">>, [{<<"class">>, <<"nolistyle">>}], - (BeamsLis ++ SelectButtons)) - end, - FmtScript = (?XC(<<"pre">>, - list_to_binary(io_lib:format("~p", [Script])))), - FmtLowLevelScript = (?XC(<<"pre">>, - list_to_binary(io_lib:format("~p", [LowLevelScript])))), - [?XC(<<"h1">>, - list_to_binary(io_lib:format(?T(<<"Update ~p">>), [Node])))] - ++ - case Res of - ok -> [?XREST(<<"Submitted">>)]; - {error, ErrorText} -> - [?XREST(<<"Error: ", ErrorText/binary>>)]; - nothing -> [] - end - ++ - [?XAE(<<"form">>, - [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], - [?XCT(<<"h2">>, <<"Update plan">>), - ?XCT(<<"h3">>, <<"Modified modules">>), Mods, - ?XCT(<<"h3">>, <<"Update script">>), FmtScript, - ?XCT(<<"h3">>, <<"Low level update script">>), - FmtLowLevelScript, ?XCT(<<"h3">>, <<"Script check">>), - ?XC(<<"pre">>, (iolist_to_binary(Check))), - ?BR, - ?INPUTT(<<"submit">>, <<"update">>, <<"Update">>)])]; -get_node(Host, Node, NPath, Query, Lang) -> - {Hook, Opts} = case Host of - global -> - {webadmin_page_node, [Node, NPath, Query, Lang]}; - Host -> - {webadmin_page_hostnode, - [Host, Node, NPath, Query, Lang]} - end, - case ejabberd_hooks:run_fold(Hook, Host, [], Opts) of - [] -> [?XC(<<"h1">>, <<"Not Found">>)]; - Res -> Res - end. - -%%%================================== -%%%% node parse - -node_parse_query(Node, Query) -> - case lists:keysearch(<<"restart">>, 1, Query) of - {value, _} -> - case rpc:call(Node, init, restart, []) of - {badrpc, _Reason} -> error; - _ -> ok - end; - _ -> - case lists:keysearch(<<"stop">>, 1, Query) of - {value, _} -> - case rpc:call(Node, init, stop, []) of - {badrpc, _Reason} -> error; - _ -> ok - end; - _ -> nothing - end - end. - -db_storage_select(ID, Opt, Lang) -> - ?XAE(<<"select">>, - [{<<"name">>, <<"table", ID/binary>>}], - (lists:map(fun ({O, Desc}) -> - Sel = if O == Opt -> - [{<<"selected">>, <<"selected">>}]; - true -> [] - end, - ?XACT(<<"option">>, - (Sel ++ - [{<<"value">>, - iolist_to_binary(atom_to_list(O))}]), - Desc) - end, - [{ram_copies, <<"RAM copy">>}, - {disc_copies, <<"RAM and disc copy">>}, - {disc_only_copies, <<"Disc only copy">>}, - {unknown, <<"Remote copy">>}, - {delete_content, <<"Delete content">>}, - {delete_table, <<"Delete table">>}]))). - -node_db_parse_query(_Node, _Tables, [{nokey, <<>>}]) -> - nothing; -node_db_parse_query(Node, Tables, Query) -> - lists:foreach(fun (Table) -> - STable = iolist_to_binary(atom_to_list(Table)), - case lists:keysearch(<<"table", STable/binary>>, 1, - Query) - of - {value, {_, SType}} -> - Type = case SType of - <<"unknown">> -> unknown; - <<"ram_copies">> -> ram_copies; - <<"disc_copies">> -> disc_copies; - <<"disc_only_copies">> -> - disc_only_copies; - <<"delete_content">> -> delete_content; - <<"delete_table">> -> delete_table; - _ -> false - end, - if Type == false -> ok; - Type == delete_content -> - mnesia:clear_table(Table); - Type == delete_table -> - mnesia:delete_table(Table); - Type == unknown -> - mnesia:del_table_copy(Table, Node); - true -> - case mnesia:add_table_copy(Table, Node, - Type) - of - {aborted, _} -> - mnesia:change_table_copy_type(Table, - Node, - Type); - _ -> ok - end - end; - _ -> ok - end - end, - Tables), - ok. - -node_backup_parse_query(_Node, [{nokey, <<>>}]) -> - nothing; -node_backup_parse_query(Node, Query) -> - lists:foldl(fun (Action, nothing) -> - case lists:keysearch(Action, 1, Query) of - {value, _} -> - case lists:keysearch(<<Action/binary, "path">>, 1, - Query) - of - {value, {_, Path}} -> - Res = case Action of - <<"store">> -> - rpc:call(Node, mnesia, backup, - [binary_to_list(Path)]); - <<"restore">> -> - rpc:call(Node, ejabberd_admin, - restore, [Path]); - <<"fallback">> -> - rpc:call(Node, mnesia, - install_fallback, - [binary_to_list(Path)]); - <<"dump">> -> - rpc:call(Node, ejabberd_admin, - dump_to_textfile, - [Path]); - <<"load">> -> - rpc:call(Node, mnesia, - load_textfile, - [binary_to_list(Path)]); - <<"import_piefxis_file">> -> - rpc:call(Node, ejabberd_piefxis, - import_file, [Path]); - <<"export_piefxis_dir">> -> - rpc:call(Node, ejabberd_piefxis, - export_server, [Path]); - <<"export_piefxis_host_dir">> -> - {value, {_, Host}} = - lists:keysearch(<<Action/binary, - "host">>, - 1, Query), - rpc:call(Node, ejabberd_piefxis, - export_host, - [Path, Host]); - <<"export_sql_file">> -> - {value, {_, Host}} = - lists:keysearch(<<Action/binary, - "host">>, - 1, Query), - rpc:call(Node, ejd2odbc, - export, [Host, Path]); - <<"import_file">> -> - rpc:call(Node, ejabberd_admin, - import_file, [Path]); - <<"import_dir">> -> - rpc:call(Node, ejabberd_admin, - import_dir, [Path]) - end, - case Res of - {error, Reason} -> {error, Reason}; - {badrpc, Reason} -> {badrpc, Reason}; - _ -> ok - end; - OtherError -> {error, OtherError} - end; - _ -> nothing - end; - (_Action, Res) -> Res - end, - nothing, - [<<"store">>, <<"restore">>, <<"fallback">>, <<"dump">>, - <<"load">>, <<"import_file">>, <<"import_dir">>, - <<"import_piefxis_file">>, <<"export_piefxis_dir">>, - <<"export_piefxis_host_dir">>, <<"export_sql_file">>]). - -node_ports_to_xhtml(Ports, Lang) -> - ?XAE(<<"table">>, [{<<"class">>, <<"withtextareas">>}], - [?XE(<<"thead">>, - [?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Port">>), ?XCT(<<"td">>, <<"IP">>), - ?XCT(<<"td">>, <<"Protocol">>), - ?XCT(<<"td">>, <<"Module">>), - ?XCT(<<"td">>, <<"Options">>)])]), - ?XE(<<"tbody">>, - (lists:map(fun ({PortIP, Module, Opts} = _E) -> - {_Port, SPort, _TIP, SIP, SSPort, NetProt, - OptsClean} = - get_port_data(PortIP, Opts), - SModule = - iolist_to_binary(atom_to_list(Module)), - {NumLines, SOptsClean} = - term_to_paragraph(OptsClean, 40), - ?XE(<<"tr">>, - [?XAE(<<"td">>, [{<<"size">>, <<"6">>}], - [?C(SPort)]), - ?XAE(<<"td">>, [{<<"size">>, <<"15">>}], - [?C(SIP)]), - ?XAE(<<"td">>, [{<<"size">>, <<"4">>}], - [?C((iolist_to_binary(atom_to_list(NetProt))))]), - ?XE(<<"td">>, - [?INPUTS(<<"text">>, - <<"module", SSPort/binary>>, - SModule, <<"15">>)]), - ?XE(<<"td">>, - [?TEXTAREA(<<"opts", SSPort/binary>>, - (iolist_to_binary(integer_to_list(NumLines))), - <<"35">>, SOptsClean)]), - ?XE(<<"td">>, - [?INPUTT(<<"submit">>, - <<"add", SSPort/binary>>, - <<"Update">>)]), - ?XE(<<"td">>, - [?INPUTT(<<"submit">>, - <<"delete", SSPort/binary>>, - <<"Delete">>)])]) - end, - Ports) - ++ - [?XE(<<"tr">>, - [?XE(<<"td">>, - [?INPUTS(<<"text">>, <<"portnew">>, <<"">>, - <<"6">>)]), - ?XE(<<"td">>, - [?INPUTS(<<"text">>, <<"ipnew">>, <<"0.0.0.0">>, - <<"15">>)]), - ?XE(<<"td">>, [make_netprot_html(<<"tcp">>)]), - ?XE(<<"td">>, - [?INPUTS(<<"text">>, <<"modulenew">>, <<"">>, - <<"15">>)]), - ?XE(<<"td">>, - [?TEXTAREA(<<"optsnew">>, <<"2">>, <<"35">>, - <<"[]">>)]), - ?XAE(<<"td">>, [{<<"colspan">>, <<"2">>}], - [?INPUTT(<<"submit">>, <<"addnew">>, - <<"Add New">>)])])]))]). - -make_netprot_html(NetProt) -> - ?XAE(<<"select">>, [{<<"name">>, <<"netprotnew">>}], - (lists:map(fun (O) -> - Sel = if O == NetProt -> - [{<<"selected">>, <<"selected">>}]; - true -> [] - end, - ?XAC(<<"option">>, (Sel ++ [{<<"value">>, O}]), O) - end, - [<<"tcp">>, <<"udp">>]))). - -get_port_data(PortIP, Opts) -> - {Port, IPT, IPS, _IPV, NetProt, OptsClean} = - ejabberd_listener:parse_listener_portip(PortIP, Opts), - SPort = jlib:integer_to_binary(Port), - SSPort = list_to_binary( - lists:map(fun (N) -> - io_lib:format("~.16b", [N]) - end, - binary_to_list( - crypto:md5( - [SPort, IPS, atom_to_list(NetProt)])))), - {Port, SPort, IPT, IPS, SSPort, NetProt, OptsClean}. - -node_ports_parse_query(Node, Ports, Query) -> - lists:foreach(fun ({PortIpNetp, Module1, Opts1}) -> - {Port, _SPort, TIP, _SIP, SSPort, NetProt, - _OptsClean} = - get_port_data(PortIpNetp, Opts1), - case lists:keysearch(<<"add", SSPort/binary>>, 1, - Query) - of - {value, _} -> - PortIpNetp2 = {Port, TIP, NetProt}, - {{value, {_, SModule}}, {value, {_, SOpts}}} = - {lists:keysearch(<<"module", - SSPort/binary>>, - 1, Query), - lists:keysearch(<<"opts", SSPort/binary>>, - 1, Query)}, - Module = jlib:binary_to_atom(SModule), - {ok, Tokens, _} = - erl_scan:string(binary_to_list(SOpts) ++ "."), - {ok, Opts} = erl_parse:parse_term(Tokens), - rpc:call(Node, ejabberd_listener, - delete_listener, - [PortIpNetp2, Module1]), - R = rpc:call(Node, ejabberd_listener, - add_listener, - [PortIpNetp2, Module, Opts]), - throw({is_added, R}); - _ -> - case lists:keysearch(<<"delete", - SSPort/binary>>, - 1, Query) - of - {value, _} -> - rpc:call(Node, ejabberd_listener, - delete_listener, - [PortIpNetp, Module1]), - throw(submitted); - _ -> ok - end - end - end, - Ports), - case lists:keysearch(<<"addnew">>, 1, Query) of - {value, _} -> - {{value, {_, SPort}}, {value, {_, STIP}}, - {value, {_, SNetProt}}, {value, {_, SModule}}, - {value, {_, SOpts}}} = - {lists:keysearch(<<"portnew">>, 1, Query), - lists:keysearch(<<"ipnew">>, 1, Query), - lists:keysearch(<<"netprotnew">>, 1, Query), - lists:keysearch(<<"modulenew">>, 1, Query), - lists:keysearch(<<"optsnew">>, 1, Query)}, - {ok, Toks, _} = erl_scan:string(binary_to_list(<<SPort/binary, ".">>)), - {ok, Port2} = erl_parse:parse_term(Toks), - {ok, ToksIP, _} = erl_scan:string(binary_to_list(<<STIP/binary, ".">>)), - STIP2 = case erl_parse:parse_term(ToksIP) of - {ok, IPTParsed} -> IPTParsed; - {error, _} -> STIP - end, - Module = jlib:binary_to_atom(SModule), - NetProt2 = jlib:binary_to_atom(SNetProt), - {ok, Tokens, _} = erl_scan:string(binary_to_list(<<SOpts/binary, ".">>)), - {ok, Opts} = erl_parse:parse_term(Tokens), - {Port2, _SPort, IP2, _SIP, _SSPort, NetProt2, - OptsClean} = - get_port_data({Port2, STIP2, NetProt2}, Opts), - R = rpc:call(Node, ejabberd_listener, add_listener, - [{Port2, IP2, NetProt2}, Module, OptsClean]), - throw({is_added, R}); - _ -> ok - end. - -node_modules_to_xhtml(Modules, Lang) -> - ?XAE(<<"table">>, [{<<"class">>, <<"withtextareas">>}], - [?XE(<<"thead">>, - [?XE(<<"tr">>, - [?XCT(<<"td">>, <<"Module">>), - ?XCT(<<"td">>, <<"Options">>)])]), - ?XE(<<"tbody">>, - (lists:map(fun ({Module, Opts} = _E) -> - SModule = - iolist_to_binary(atom_to_list(Module)), - {NumLines, SOpts} = term_to_paragraph(Opts, - 40), - ?XE(<<"tr">>, - [?XC(<<"td">>, SModule), - ?XE(<<"td">>, - [?TEXTAREA(<<"opts", SModule/binary>>, - (iolist_to_binary(integer_to_list(NumLines))), - <<"40">>, SOpts)]), - ?XE(<<"td">>, - [?INPUTT(<<"submit">>, - <<"restart", - SModule/binary>>, - <<"Restart">>)]), - ?XE(<<"td">>, - [?INPUTT(<<"submit">>, - <<"stop", SModule/binary>>, - <<"Stop">>)])]) - end, - Modules) - ++ - [?XE(<<"tr">>, - [?XE(<<"td">>, - [?INPUT(<<"text">>, <<"modulenew">>, <<"">>)]), - ?XE(<<"td">>, - [?TEXTAREA(<<"optsnew">>, <<"2">>, <<"40">>, - <<"[]">>)]), - ?XAE(<<"td">>, [{<<"colspan">>, <<"2">>}], - [?INPUTT(<<"submit">>, <<"start">>, - <<"Start">>)])])]))]). - -node_modules_parse_query(Host, Node, Modules, Query) -> - lists:foreach(fun ({Module, _Opts1}) -> - SModule = iolist_to_binary(atom_to_list(Module)), - case lists:keysearch(<<"restart", SModule/binary>>, 1, - Query) - of - {value, _} -> - {value, {_, SOpts}} = lists:keysearch(<<"opts", - SModule/binary>>, - 1, Query), - {ok, Tokens, _} = - erl_scan:string(binary_to_list(<<SOpts/binary, ".">>)), - {ok, Opts} = erl_parse:parse_term(Tokens), - rpc:call(Node, gen_mod, stop_module, - [Host, Module]), - rpc:call(Node, gen_mod, start_module, - [Host, Module, Opts]), - throw(submitted); - _ -> - case lists:keysearch(<<"stop", SModule/binary>>, - 1, Query) - of - {value, _} -> - rpc:call(Node, gen_mod, stop_module, - [Host, Module]), - throw(submitted); - _ -> ok - end - end - end, - Modules), - case lists:keysearch(<<"start">>, 1, Query) of - {value, _} -> - {{value, {_, SModule}}, {value, {_, SOpts}}} = - {lists:keysearch(<<"modulenew">>, 1, Query), - lists:keysearch(<<"optsnew">>, 1, Query)}, - Module = jlib:binary_to_atom(SModule), - {ok, Tokens, _} = erl_scan:string(binary_to_list(<<SOpts/binary, ".">>)), - {ok, Opts} = erl_parse:parse_term(Tokens), - rpc:call(Node, gen_mod, start_module, - [Host, Module, Opts]), - throw(submitted); - _ -> ok - end. - -node_update_parse_query(Node, Query) -> - case lists:keysearch(<<"update">>, 1, Query) of - {value, _} -> - ModulesToUpdateStrings = - proplists:get_all_values(<<"selected">>, Query), - ModulesToUpdate = [jlib:binary_to_atom(M) - || M <- ModulesToUpdateStrings], - case rpc:call(Node, ejabberd_update, update, - [ModulesToUpdate]) - of - {ok, _} -> ok; - {error, Error} -> - ?ERROR_MSG("~p~n", [Error]), - {error, iolist_to_binary(io_lib:format("~p", [Error]))}; - {badrpc, Error} -> - ?ERROR_MSG("Bad RPC: ~p~n", [Error]), - {error, - <<"Bad RPC: ", (iolist_to_binary(io_lib:format("~p", [Error])))/binary>>} - end; - _ -> nothing - end. - -pretty_print_xml(El) -> - list_to_binary(pretty_print_xml(El, <<"">>)). - -pretty_print_xml({xmlcdata, CData}, Prefix) -> - IsBlankCData = lists:all( - fun($\f) -> true; - ($\r) -> true; - ($\n) -> true; - ($\t) -> true; - ($\v) -> true; - ($ ) -> true; - (_) -> false - end, binary_to_list(CData)), - if IsBlankCData -> - []; - true -> - [Prefix, CData, $\n] - end; -pretty_print_xml(#xmlel{name = Name, attrs = Attrs, - children = Els}, - Prefix) -> - [Prefix, $<, Name, - case Attrs of - [] -> []; - [{Attr, Val} | RestAttrs] -> - AttrPrefix = [Prefix, - str:copies(<<" ">>, byte_size(Name) + 2)], - [$\s, Attr, $=, $', xml:crypt(Val) | [$', - lists:map(fun ({Attr1, - Val1}) -> - [$\n, - AttrPrefix, - Attr1, $=, - $', - xml:crypt(Val1), - $'] - end, - RestAttrs)]] - end, - if Els == [] -> <<"/>\n">>; - true -> - OnlyCData = lists:all(fun ({xmlcdata, _}) -> true; - (#xmlel{}) -> false - end, - Els), - if OnlyCData -> - [$>, xml:get_cdata(Els), $<, $/, Name, $>, $\n]; - true -> - [$>, $\n, - lists:map(fun (E) -> - pretty_print_xml(E, [Prefix, <<" ">>]) - end, - Els), - Prefix, $<, $/, Name, $>, $\n] - end - end]. - -element_to_list(X) when is_atom(X) -> - iolist_to_binary(atom_to_list(X)); -element_to_list(X) when is_integer(X) -> - iolist_to_binary(integer_to_list(X)). - -list_to_element(Bin) -> - {ok, Tokens, _} = erl_scan:string(binary_to_list(Bin)), - [{_, _, Element}] = Tokens, - Element. - -url_func({user_diapason, From, To}) -> - <<(iolist_to_binary(integer_to_list(From)))/binary, "-", - (iolist_to_binary(integer_to_list(To)))/binary, "/">>; -url_func({users_queue, Prefix, User, _Server}) -> - <<Prefix/binary, "user/", User/binary, "/queue/">>; -url_func({user, Prefix, User, _Server}) -> - <<Prefix/binary, "user/", User/binary, "/">>. - -last_modified() -> - {<<"Last-Modified">>, - <<"Mon, 25 Feb 2008 13:23:30 GMT">>}. - -cache_control_public() -> - {<<"Cache-Control">>, <<"public">>}. - -%% Transform 1234567890 into "1,234,567,890" -pretty_string_int(Integer) when is_integer(Integer) -> - pretty_string_int(iolist_to_binary(integer_to_list(Integer))); -pretty_string_int(String) when is_binary(String) -> - {_, Result} = lists:foldl(fun (NewNumber, {3, Result}) -> - {1, <<NewNumber, $,, Result/binary>>}; - (NewNumber, {CountAcc, Result}) -> - {CountAcc + 1, <<NewNumber, Result/binary>>} - end, - {0, <<"">>}, lists:reverse(binary_to_list(String))), - Result. - -%%%================================== -%%%% navigation menu - -%% @spec (Host, Node, Lang, JID::jid()) -> [LI] -make_navigation(Host, Node, Lang, JID) -> - Menu = make_navigation_menu(Host, Node, Lang, JID), - make_menu_items(Lang, Menu). - -%% @spec (Host, Node, Lang, JID::jid()) -> Menu -%% where Host = global | string() -%% Node = cluster | string() -%% Lang = string() -%% Menu = {URL, Title} | {URL, Title, [Menu]} -%% URL = string() -%% Title = string() -make_navigation_menu(Host, Node, Lang, JID) -> - HostNodeMenu = make_host_node_menu(Host, Node, Lang, - JID), - HostMenu = make_host_menu(Host, HostNodeMenu, Lang, - JID), - NodeMenu = make_node_menu(Host, Node, Lang), - make_server_menu(HostMenu, NodeMenu, Lang, JID). - -%% @spec (Host, Node, Base, Lang) -> [LI] -make_menu_items(global, cluster, Base, Lang) -> - HookItems = get_menu_items_hook(server, Lang), - make_menu_items(Lang, {Base, <<"">>, HookItems}); -make_menu_items(global, Node, Base, Lang) -> - HookItems = get_menu_items_hook({node, Node}, Lang), - make_menu_items(Lang, {Base, <<"">>, HookItems}); -make_menu_items(Host, cluster, Base, Lang) -> - HookItems = get_menu_items_hook({host, Host}, Lang), - make_menu_items(Lang, {Base, <<"">>, HookItems}); -make_menu_items(Host, Node, Base, Lang) -> - HookItems = get_menu_items_hook({hostnode, Host, Node}, - Lang), - make_menu_items(Lang, {Base, <<"">>, HookItems}). - -make_host_node_menu(global, _, _Lang, _JID) -> - {<<"">>, <<"">>, []}; -make_host_node_menu(_, cluster, _Lang, _JID) -> - {<<"">>, <<"">>, []}; -make_host_node_menu(Host, Node, Lang, JID) -> - HostNodeBase = get_base_path(Host, Node), - HostNodeFixed = [{<<"modules/">>, <<"Modules">>}] ++ - get_menu_items_hook({hostnode, Host, Node}, Lang), - HostNodeBasePath = url_to_path(HostNodeBase), - HostNodeFixed2 = [Tuple - || Tuple <- HostNodeFixed, - is_allowed_path(HostNodeBasePath, Tuple, JID)], - {HostNodeBase, iolist_to_binary(atom_to_list(Node)), - HostNodeFixed2}. - -make_host_menu(global, _HostNodeMenu, _Lang, _JID) -> - {<<"">>, <<"">>, []}; -make_host_menu(Host, HostNodeMenu, Lang, JID) -> - HostBase = get_base_path(Host, cluster), - HostFixed = [{<<"acls">>, <<"Access Control Lists">>}, - {<<"access">>, <<"Access Rules">>}, - {<<"users">>, <<"Users">>}, - {<<"online-users">>, <<"Online Users">>}] - ++ - get_lastactivity_menuitem_list(Host) ++ - [{<<"nodes">>, <<"Nodes">>, HostNodeMenu}, - {<<"stats">>, <<"Statistics">>}] - ++ get_menu_items_hook({host, Host}, Lang), - HostBasePath = url_to_path(HostBase), - HostFixed2 = [Tuple - || Tuple <- HostFixed, - is_allowed_path(HostBasePath, Tuple, JID)], - {HostBase, Host, HostFixed2}. - -make_node_menu(_Host, cluster, _Lang) -> - {<<"">>, <<"">>, []}; -make_node_menu(global, Node, Lang) -> - NodeBase = get_base_path(global, Node), - NodeFixed = [{<<"db/">>, <<"Database">>}, - {<<"backup/">>, <<"Backup">>}, - {<<"ports/">>, <<"Listened Ports">>}, - {<<"stats/">>, <<"Statistics">>}, - {<<"update/">>, <<"Update">>}] - ++ get_menu_items_hook({node, Node}, Lang), - {NodeBase, iolist_to_binary(atom_to_list(Node)), - NodeFixed}; -make_node_menu(_Host, _Node, _Lang) -> - {<<"">>, <<"">>, []}. - -make_server_menu(HostMenu, NodeMenu, Lang, JID) -> - Base = get_base_path(global, cluster), - Fixed = [{<<"acls">>, <<"Access Control Lists">>}, - {<<"access">>, <<"Access Rules">>}, - {<<"vhosts">>, <<"Virtual Hosts">>, HostMenu}, - {<<"nodes">>, <<"Nodes">>, NodeMenu}, - {<<"stats">>, <<"Statistics">>}] - ++ get_menu_items_hook(server, Lang), - BasePath = url_to_path(Base), - Fixed2 = [Tuple - || Tuple <- Fixed, - is_allowed_path(BasePath, Tuple, JID)], - {Base, <<"ejabberd">>, Fixed2}. - -get_menu_items_hook({hostnode, Host, Node}, Lang) -> - ejabberd_hooks:run_fold(webadmin_menu_hostnode, Host, - [], [Host, Node, Lang]); -get_menu_items_hook({host, Host}, Lang) -> - ejabberd_hooks:run_fold(webadmin_menu_host, Host, [], - [Host, Lang]); -get_menu_items_hook({node, Node}, Lang) -> - ejabberd_hooks:run_fold(webadmin_menu_node, [], - [Node, Lang]); -get_menu_items_hook(server, Lang) -> - ejabberd_hooks:run_fold(webadmin_menu_main, [], [Lang]). - -%% @spec (Lang::string(), Menu) -> [LI] -%% where Menu = {MURI::string(), MName::string(), Items::[Item]} -%% Item = {IURI::string(), IName::string()} | {IURI::string(), IName::string(), Menu} -make_menu_items(Lang, Menu) -> - lists:reverse(make_menu_items2(Lang, 1, Menu)). - -make_menu_items2(Lang, Deep, {MURI, MName, _} = Menu) -> - Res = case MName of - <<"">> -> []; - _ -> [make_menu_item(header, Deep, MURI, MName, Lang)] - end, - make_menu_items2(Lang, Deep, Menu, Res). - -make_menu_items2(_, _Deep, {_, _, []}, Res) -> Res; -make_menu_items2(Lang, Deep, - {MURI, MName, [Item | Items]}, Res) -> - Res2 = case Item of - {IURI, IName} -> - [make_menu_item(item, Deep, - <<MURI/binary, IURI/binary, "/">>, IName, Lang) - | Res]; - {IURI, IName, SubMenu} -> - ResTemp = [make_menu_item(item, Deep, - <<MURI/binary, IURI/binary, "/">>, - IName, Lang) - | Res], - ResSubMenu = make_menu_items2(Lang, Deep + 1, SubMenu), - ResSubMenu ++ ResTemp - end, - make_menu_items2(Lang, Deep, {MURI, MName, Items}, - Res2). - -make_menu_item(header, 1, URI, Name, _Lang) -> - ?LI([?XAE(<<"div">>, [{<<"id">>, <<"navhead">>}], - [?AC(URI, Name)])]); -make_menu_item(header, 2, URI, Name, _Lang) -> - ?LI([?XAE(<<"div">>, [{<<"id">>, <<"navheadsub">>}], - [?AC(URI, Name)])]); -make_menu_item(header, 3, URI, Name, _Lang) -> - ?LI([?XAE(<<"div">>, [{<<"id">>, <<"navheadsubsub">>}], - [?AC(URI, Name)])]); -make_menu_item(item, 1, URI, Name, Lang) -> - ?LI([?XAE(<<"div">>, [{<<"id">>, <<"navitem">>}], - [?ACT(URI, Name)])]); -make_menu_item(item, 2, URI, Name, Lang) -> - ?LI([?XAE(<<"div">>, [{<<"id">>, <<"navitemsub">>}], - [?ACT(URI, Name)])]); -make_menu_item(item, 3, URI, Name, Lang) -> - ?LI([?XAE(<<"div">>, [{<<"id">>, <<"navitemsubsub">>}], - [?ACT(URI, Name)])]). - -%%%================================== - -%%% vim: set foldmethod=marker foldmarker=%%%%,%%%=: - diff --git a/src/web/ejabberd_web_admin.hrl b/src/web/ejabberd_web_admin.hrl deleted file mode 100644 index 6c939f9e9..000000000 --- a/src/web/ejabberd_web_admin.hrl +++ /dev/null @@ -1,105 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% -%%% ejabberd, Copyright (C) 2002-2013 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., 59 Temple Place, Suite 330, Boston, MA -%%% 02111-1307 USA -%%% -%%%---------------------------------------------------------------------- - --define(X(Name), - #xmlel{name = Name, attrs = [], children = []}). - --define(XA(Name, Attrs), - #xmlel{name = Name, attrs = Attrs, children = []}). - --define(XE(Name, Els), - #xmlel{name = Name, attrs = [], children = Els}). - --define(XAE(Name, Attrs, Els), - #xmlel{name = Name, attrs = Attrs, children = Els}). - --define(C(Text), {xmlcdata, Text}). - --define(XC(Name, Text), ?XE(Name, [?C(Text)])). - --define(XAC(Name, Attrs, Text), - ?XAE(Name, Attrs, [?C(Text)])). - --define(T(Text), translate:translate(Lang, Text)). - --define(CT(Text), ?C((?T(Text)))). - --define(XCT(Name, Text), ?XC(Name, (?T(Text)))). - --define(XACT(Name, Attrs, Text), - ?XAC(Name, Attrs, (?T(Text)))). - --define(LI(Els), ?XE(<<"li">>, Els)). - --define(A(URL, Els), - ?XAE(<<"a">>, [{<<"href">>, URL}], Els)). - --define(AC(URL, Text), ?A(URL, [?C(Text)])). - --define(ACT(URL, Text), ?AC(URL, (?T(Text)))). - --define(P, ?X(<<"p">>)). - --define(BR, ?X(<<"br">>)). - --define(INPUT(Type, Name, Value), - ?XA(<<"input">>, - [{<<"type">>, Type}, {<<"name">>, Name}, - {<<"value">>, Value}])). - --define(INPUTT(Type, Name, Value), - ?INPUT(Type, Name, (?T(Value)))). - --define(INPUTS(Type, Name, Value, Size), - ?XA(<<"input">>, - [{<<"type">>, Type}, {<<"name">>, Name}, - {<<"value">>, Value}, {<<"size">>, Size}])). - --define(INPUTST(Type, Name, Value, Size), - ?INPUT(Type, Name, (?T(Value)), Size)). - --define(ACLINPUT(Text), - ?XE(<<"td">>, - [?INPUT(<<"text">>, <<"value", ID/binary>>, Text)])). - --define(TEXTAREA(Name, Rows, Cols, Value), - ?XAC(<<"textarea">>, - [{<<"name">>, Name}, {<<"rows">>, Rows}, - {<<"cols">>, Cols}], - Value)). - -%% Build an xmlelement for result --define(XRES(Text), - ?XAC(<<"p">>, [{<<"class">>, <<"result">>}], Text)). - -%% Guide Link --define(XREST(Text), ?XRES((?T(Text)))). - --define(GL(Ref, Title), - ?XAE(<<"div">>, [{<<"class">>, <<"guidelink">>}], - [?XAE(<<"a">>, - [{<<"href">>, <<"/admin/doc/guide.html#", Ref/binary>>}, - {<<"target">>, <<"_blank">>}], - [?C(<<"[Guide: ", Title/binary, "]">>)])])). - -%% h1 with a Guide Link --define(H1GL(Name, Ref, Title), - [?XC(<<"h1">>, Name), ?GL(Ref, Title)]). diff --git a/src/web/http_bind.hrl b/src/web/http_bind.hrl deleted file mode 100644 index 2eac91e9d..000000000 --- a/src/web/http_bind.hrl +++ /dev/null @@ -1,47 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% -%%% ejabberd, Copyright (C) 2002-2013 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., 59 Temple Place, Suite 330, Boston, MA -%%% 02111-1307 USA -%%% -%%%---------------------------------------------------------------------- - --define(CT_XML, - {<<"Content-Type">>, <<"text/xml; charset=utf-8">>}). - --define(CT_PLAIN, - {<<"Content-Type">>, <<"text/plain">>}). - --define(AC_ALLOW_ORIGIN, - {<<"Access-Control-Allow-Origin">>, <<"*">>}). - --define(AC_ALLOW_METHODS, - {<<"Access-Control-Allow-Methods">>, - <<"GET, POST, OPTIONS">>}). - --define(AC_ALLOW_HEADERS, - {<<"Access-Control-Allow-Headers">>, - <<"Content-Type">>}). - --define(AC_MAX_AGE, - {<<"Access-Control-Max-Age">>, <<"86400">>}). - --define(OPTIONS_HEADER, - [?CT_PLAIN, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_METHODS, - ?AC_ALLOW_HEADERS, ?AC_MAX_AGE]). - --define(HEADER, - [?CT_XML, ?AC_ALLOW_ORIGIN, ?AC_ALLOW_HEADERS]). diff --git a/src/web/mod_http_bind.erl b/src/web/mod_http_bind.erl deleted file mode 100644 index 164c07d33..000000000 --- a/src/web/mod_http_bind.erl +++ /dev/null @@ -1,144 +0,0 @@ -%%%---------------------------------------------------------------------- -%%% File : mod_http_bind.erl -%%% Author : Stefan Strigler <steve@zeank.in-berlin.de> -%%% Purpose : Implementation of XMPP over BOSH (XEP-0206) -%%% Created : Tue Feb 20 13:15:52 CET 2007 -%%% -%%% -%%% ejabberd, Copyright (C) 2002-2013 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., 59 Temple Place, Suite 330, Boston, MA -%%% 02111-1307 USA -%%% -%%%---------------------------------------------------------------------- - -%%%---------------------------------------------------------------------- -%%% This module acts as a bridge to ejabberd_http_bind which implements -%%% the real stuff, this is to handle the new pluggable architecture for -%%% extending ejabberd's http service. -%%%---------------------------------------------------------------------- - --module(mod_http_bind). - --author('steve@zeank.in-berlin.de'). - -%%-define(ejabberd_debug, true). - --behaviour(gen_mod). - --export([start/2, stop/1, process/2]). - --include("ejabberd.hrl"). - --include("jlib.hrl"). - --include("ejabberd_http.hrl"). - --include("http_bind.hrl"). - --define(PROCNAME_MHB, ejabberd_mod_http_bind). - -%% Duplicated from ejabberd_http_bind. -%% TODO: move to hrl file. --record(http_bind, - {id, pid, to, hold, wait, process_delay, version}). - -%%%---------------------------------------------------------------------- -%%% API -%%%---------------------------------------------------------------------- - -process([], #request{method = 'POST', data = <<>>}) -> - ?DEBUG("Bad Request: no data", []), - {400, ?HEADER, - #xmlel{name = <<"h1">>, children = [{xmlcdata, <<"400 Bad Request">>}]}}; -process([], - #request{method = 'POST', data = Data, ip = IP}) -> - ?DEBUG("Incoming data: ~s", [Data]), - ejabberd_http_bind:process_request(Data, IP); -process([], #request{method = 'GET', data = <<>>}) -> - {200, ?HEADER, get_human_html_xmlel()}; -process([], #request{method = 'OPTIONS', data = <<>>}) -> - {200, ?OPTIONS_HEADER, <<>>}; -process([], #request{method = 'HEAD'}) -> - {200, ?HEADER, <<>>}; -process(_Path, _Request) -> - ?DEBUG("Bad Request: ~p", [_Request]), - {400, ?HEADER, - #xmlel{name = <<"h1">>, children = [{xmlcdata, <<"400 Bad Request">>}]}}. - -get_human_html_xmlel() -> - Heading = <<"ejabberd ", - (iolist_to_binary(atom_to_list(?MODULE)))/binary>>, - #xmlel{name = <<"html">>, - attrs = - [{<<"xmlns">>, <<"http://www.w3.org/1999/xhtml">>}], - children = - [#xmlel{name = <<"head">>, - children = - [#xmlel{name = <<"title">>, - children = [{xmlcdata, Heading}]}]}, - #xmlel{name = <<"body">>, - children = - [#xmlel{name = <<"h1">>, - children = [{xmlcdata, Heading}]}, - #xmlel{name = <<"p">>, - children = - [{xmlcdata, <<"An implementation of ">>}, - #xmlel{name = <<"a">>, - attrs = - [{<<"href">>, - <<"http://xmpp.org/extensions/xep-0206.html">>}], - children = - [{xmlcdata, - <<"XMPP over BOSH (XEP-0206)">>}]}]}, - #xmlel{name = <<"p">>, - children = - [{xmlcdata, - <<"This web page is only informative. To " - "use HTTP-Bind you need a Jabber/XMPP " - "client that supports it.">>}]}]}]}. - -%%%---------------------------------------------------------------------- -%%% BEHAVIOUR CALLBACKS -%%%---------------------------------------------------------------------- -start(Host, _Opts) -> - setup_database(), - Proc = gen_mod:get_module_proc(Host, ?PROCNAME_MHB), - ChildSpec = {Proc, - {ejabberd_tmp_sup, start_link, - [Proc, ejabberd_http_bind]}, - permanent, infinity, supervisor, [ejabberd_tmp_sup]}, - supervisor:start_child(ejabberd_sup, ChildSpec). - -stop(Host) -> - Proc = gen_mod:get_module_proc(Host, ?PROCNAME_MHB), - supervisor:terminate_child(ejabberd_sup, Proc), - supervisor:delete_child(ejabberd_sup, Proc). - -setup_database() -> - migrate_database(), - mnesia:create_table(http_bind, - [{ram_copies, [node()]}, - {attributes, record_info(fields, http_bind)}]). - -migrate_database() -> - case catch mnesia:table_info(http_bind, attributes) of - [id, pid, to, hold, wait, process_delay, version] -> - ok; - _ -> - %% Since the stored information is not important, instead - %% of actually migrating data, let's just destroy the table - mnesia:delete_table(http_bind) - end. diff --git a/src/web/mod_http_fileserver.erl b/src/web/mod_http_fileserver.erl deleted file mode 100644 index 98206a5f0..000000000 --- a/src/web/mod_http_fileserver.erl +++ /dev/null @@ -1,454 +0,0 @@ -%%%------------------------------------------------------------------- -%%% File : mod_http_fileserver.erl -%%% Author : Massimiliano Mirra <mmirra [at] process-one [dot] net> -%%% Purpose : Simple file server plugin for embedded ejabberd web server -%%% Created : -%%% -%%% -%%% ejabberd, Copyright (C) 2002-2013 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., 59 Temple Place, Suite 330, Boston, MA -%%% 02111-1307 USA -%%% -%%%---------------------------------------------------------------------- - --module(mod_http_fileserver). - --author('mmirra@process-one.net'). - --behaviour(gen_mod). --behaviour(gen_server). - -%% gen_mod callbacks --export([start/2, stop/1]). - -%% API --export([start_link/2]). - -%% gen_server callbacks --export([init/1, handle_call/3, handle_cast/2, handle_info/2, - terminate/2, code_change/3]). - -%% request_handlers callbacks --export([process/2]). - -%% ejabberd_hooks callbacks --export([reopen_log/1]). - --include("ejabberd.hrl"). - --include("jlib.hrl"). - --include_lib("kernel/include/file.hrl"). - -%%-include("ejabberd_http.hrl"). -%% TODO: When ejabberd-modules SVN gets the new ejabberd_http.hrl, delete this code: --record(request, - {method, path, q = [], us, auth, lang = <<"">>, - data = <<"">>, ip, host, port, tp, headers}). - --record(state, - {host, docroot, accesslog, accesslogfd, - directory_indices, custom_headers, default_content_type, - content_types = []}). - --define(PROCNAME, ejabberd_mod_http_fileserver). - -%% Response is {DataSize, Code, [{HeaderKey, HeaderValue}], Data} --define(HTTP_ERR_FILE_NOT_FOUND, - {-1, 404, [], <<"Not found">>}). - --define(HTTP_ERR_FORBIDDEN, - {-1, 403, [], <<"Forbidden">>}). - --define(DEFAULT_CONTENT_TYPE, - <<"application/octet-stream">>). - --define(DEFAULT_CONTENT_TYPES, - [{<<".css">>, <<"text/css">>}, - {<<".gif">>, <<"image/gif">>}, - {<<".html">>, <<"text/html">>}, - {<<".jar">>, <<"application/java-archive">>}, - {<<".jpeg">>, <<"image/jpeg">>}, - {<<".jpg">>, <<"image/jpeg">>}, - {<<".js">>, <<"text/javascript">>}, - {<<".png">>, <<"image/png">>}, - {<<".svg">>, <<"image/svg+xml">>}, - {<<".txt">>, <<"text/plain">>}, - {<<".xml">>, <<"application/xml">>}, - {<<".xpi">>, <<"application/x-xpinstall">>}, - {<<".xul">>, <<"application/vnd.mozilla.xul+xml">>}]). - --compile(export_all). - -%%==================================================================== -%% gen_mod callbacks -%%==================================================================== - -start(Host, Opts) -> - Proc = get_proc_name(Host), - ChildSpec = - {Proc, - {?MODULE, start_link, [Host, Opts]}, - transient, % if process crashes abruptly, it gets restarted - 1000, - worker, - [?MODULE]}, - supervisor:start_child(ejabberd_sup, ChildSpec). - -stop(Host) -> - Proc = get_proc_name(Host), - gen_server:call(Proc, stop), - supervisor:terminate_child(ejabberd_sup, Proc), - supervisor:delete_child(ejabberd_sup, Proc). - -%%==================================================================== -%% API -%%==================================================================== -%%-------------------------------------------------------------------- -%% Function: start_link() -> {ok,Pid} | ignore | {error,Error} -%% Description: Starts the server -%%-------------------------------------------------------------------- -start_link(Host, Opts) -> - Proc = get_proc_name(Host), - gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []). - -%%==================================================================== -%% gen_server callbacks -%%==================================================================== -%%-------------------------------------------------------------------- -%% Function: init(Args) -> {ok, State} | -%% {ok, State, Timeout} | -%% ignore | -%% {stop, Reason} -%% Description: Initiates the server -%%-------------------------------------------------------------------- -init([Host, Opts]) -> - try initialize(Host, Opts) of - {DocRoot, AccessLog, AccessLogFD, DirectoryIndices, - CustomHeaders, DefaultContentType, ContentTypes} -> - {ok, #state{host = Host, - accesslog = AccessLog, - accesslogfd = AccessLogFD, - docroot = DocRoot, - directory_indices = DirectoryIndices, - custom_headers = CustomHeaders, - default_content_type = DefaultContentType, - content_types = ContentTypes}} - catch - throw:Reason -> - {stop, Reason} - end. - -initialize(Host, Opts) -> - DocRoot = gen_mod:get_opt(docroot, Opts, fun(A) -> A end, undefined), - check_docroot_defined(DocRoot, Host), - DRInfo = check_docroot_exists(DocRoot), - check_docroot_is_dir(DRInfo, DocRoot), - check_docroot_is_readable(DRInfo, DocRoot), - AccessLog = gen_mod:get_opt(accesslog, Opts, - fun iolist_to_binary/1, - undefined), - AccessLogFD = try_open_log(AccessLog, Host), - DirectoryIndices = gen_mod:get_opt(directory_indices, Opts, - fun(L) when is_list(L) -> L end, - []), - CustomHeaders = gen_mod:get_opt(custom_headers, Opts, - fun(L) when is_list(L) -> L end, - []), - DefaultContentType = gen_mod:get_opt(default_content_type, Opts, - fun iolist_to_binary/1, - ?DEFAULT_CONTENT_TYPE), - ContentTypes = build_list_content_types( - gen_mod:get_opt(content_types, Opts, - fun(L) when is_list(L) -> L end, - []), - ?DEFAULT_CONTENT_TYPES), - ?INFO_MSG("initialize: ~n ~p", [ContentTypes]),%+++ - {DocRoot, AccessLog, AccessLogFD, DirectoryIndices, - CustomHeaders, DefaultContentType, ContentTypes}. - - -%% @spec (AdminCTs::[CT], Default::[CT]) -> [CT] -%% where CT = {Extension::string(), Value} -%% Value = string() | undefined -%% @doc Return a unified list without duplicates. -%% Elements of AdminCTs have more priority. -%% If a CT is declared as 'undefined', then it is not included in the result. -build_list_content_types(AdminCTsUnsorted, DefaultCTsUnsorted) -> - AdminCTs = lists:ukeysort(1, AdminCTsUnsorted), - DefaultCTs = lists:ukeysort(1, DefaultCTsUnsorted), - CTsUnfiltered = lists:ukeymerge(1, AdminCTs, - DefaultCTs), - [{Extension, Value} - || {Extension, Value} <- CTsUnfiltered, - Value /= undefined]. - -check_docroot_defined(DocRoot, Host) -> - case DocRoot of - undefined -> throw({undefined_docroot_option, Host}); - _ -> ok - end. - -check_docroot_exists(DocRoot) -> - case file:read_file_info(DocRoot) of - {error, Reason} -> - throw({error_access_docroot, DocRoot, Reason}); - {ok, FI} -> FI - end. - -check_docroot_is_dir(DRInfo, DocRoot) -> - case DRInfo#file_info.type of - directory -> ok; - _ -> throw({docroot_not_directory, DocRoot}) - end. - -check_docroot_is_readable(DRInfo, DocRoot) -> - case DRInfo#file_info.access of - read -> ok; - read_write -> ok; - _ -> throw({docroot_not_readable, DocRoot}) - end. - -try_open_log(undefined, _Host) -> - undefined; -try_open_log(FN, Host) -> - FD = try open_log(FN) of - FD1 -> FD1 - catch - throw:{cannot_open_accesslog, FN, Reason} -> - ?ERROR_MSG("Cannot open access log file: ~p~nReason: ~p", [FN, Reason]), - undefined - end, - ejabberd_hooks:add(reopen_log_hook, Host, ?MODULE, reopen_log, 50), - FD. - -%%-------------------------------------------------------------------- -%% Function: handle_call(Request, From, State) -> {reply, Reply, State} | -%% {reply, Reply, State, Timeout} | -%% {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, Reply, State} | -%% {stop, Reason, State} -%% Description: Handling call messages -%%-------------------------------------------------------------------- -handle_call({serve, LocalPath}, _From, State) -> - Reply = serve(LocalPath, State#state.docroot, State#state.directory_indices, - State#state.custom_headers, - State#state.default_content_type, State#state.content_types), - {reply, Reply, State}; -handle_call(_Request, _From, State) -> - {reply, ok, State}. - -%%-------------------------------------------------------------------- -%% Function: handle_cast(Msg, State) -> {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} -%% Description: Handling cast messages -%%-------------------------------------------------------------------- -handle_cast({add_to_log, FileSize, Code, Request}, State) -> - add_to_log(State#state.accesslogfd, FileSize, Code, Request), - {noreply, State}; -handle_cast(reopen_log, State) -> - FD2 = reopen_log(State#state.accesslog, State#state.accesslogfd), - {noreply, State#state{accesslogfd = FD2}}; -handle_cast(_Msg, State) -> - {noreply, State}. - -%%-------------------------------------------------------------------- -%% Function: handle_info(Info, State) -> {noreply, State} | -%% {noreply, State, Timeout} | -%% {stop, Reason, State} -%% Description: Handling all non call/cast messages -%%-------------------------------------------------------------------- -handle_info(_Info, State) -> - {noreply, State}. - -%%-------------------------------------------------------------------- -%% Function: terminate(Reason, State) -> void() -%% Description: This function is called by a gen_server when it is about to -%% terminate. It should be the opposite of Module:init/1 and do any necessary -%% cleaning up. When it returns, the gen_server terminates with Reason. -%% The return value is ignored. -%%-------------------------------------------------------------------- -terminate(_Reason, State) -> - close_log(State#state.accesslogfd), - ejabberd_hooks:delete(reopen_log_hook, State#state.host, ?MODULE, reopen_log, 50), - ok. - -%%-------------------------------------------------------------------- -%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} -%% Description: Convert process state when code is changed -%%-------------------------------------------------------------------- -code_change(_OldVsn, State, _Extra) -> - {ok, State}. - -%%==================================================================== -%% request_handlers callbacks -%%==================================================================== - -%% @spec (LocalPath, Request) -> {HTTPCode::integer(), [Header], Page::string()} -%% @doc Handle an HTTP request. -%% LocalPath is the part of the requested URL path that is "local to the module". -%% Returns the page to be sent back to the client and/or HTTP status code. -process(LocalPath, Request) -> - ?DEBUG("Requested ~p", [LocalPath]), - try gen_server:call(get_proc_name(Request#request.host), {serve, LocalPath}) of - {FileSize, Code, Headers, Contents} -> - add_to_log(FileSize, Code, Request), - {Code, Headers, Contents} - catch - exit:{noproc, _} -> - ?ERROR_MSG("Received an HTTP request with Host ~p, but couldn't find the related " - "ejabberd virtual host", [Request#request.host]), - ejabberd_web:error(not_found) - end. - -serve(LocalPath, DocRoot, DirectoryIndices, CustomHeaders, DefaultContentType, ContentTypes) -> - FileName = filename:join(filename:split(DocRoot) ++ LocalPath), - case file:read_file_info(FileName) of - {error, enoent} -> ?HTTP_ERR_FILE_NOT_FOUND; - {error, eacces} -> ?HTTP_ERR_FORBIDDEN; - {ok, #file_info{type = directory}} -> serve_index(FileName, - DirectoryIndices, - CustomHeaders, - DefaultContentType, - ContentTypes); - {ok, FileInfo} -> serve_file(FileInfo, FileName, - CustomHeaders, - DefaultContentType, - ContentTypes) - end. - -%% Troll through the directory indices attempting to find one which -%% works, if none can be found, return a 404. -serve_index(_FileName, [], _CH, _DefaultContentType, _ContentTypes) -> - ?HTTP_ERR_FILE_NOT_FOUND; -serve_index(FileName, [Index | T], CH, DefaultContentType, ContentTypes) -> - IndexFileName = filename:join([FileName] ++ [Index]), - case file:read_file_info(IndexFileName) of - {error, _Error} -> serve_index(FileName, T, CH, DefaultContentType, ContentTypes); - {ok, #file_info{type = directory}} -> serve_index(FileName, T, CH, DefaultContentType, ContentTypes); - {ok, FileInfo} -> serve_file(FileInfo, IndexFileName, CH, DefaultContentType, ContentTypes) - end. - -%% Assume the file exists if we got this far and attempt to read it in -%% and serve it up. -serve_file(FileInfo, FileName, CustomHeaders, DefaultContentType, ContentTypes) -> - ?DEBUG("Delivering: ~s", [FileName]), - ContentType = content_type(FileName, DefaultContentType, - ContentTypes), - {ok, FileContents} = file:read_file(FileName), - {FileInfo#file_info.size, 200, - [{<<"Server">>, <<"ejabberd">>}, - {<<"Last-Modified">>, last_modified(FileInfo)}, - {<<"Content-Type">>, ContentType} - | CustomHeaders], - FileContents}. - -%%---------------------------------------------------------------------- -%% Log file -%%---------------------------------------------------------------------- - -open_log(FN) -> - case file:open(FN, [append]) of - {ok, FD} -> - FD; - {error, Reason} -> - throw({cannot_open_accesslog, FN, Reason}) - end. - -close_log(FD) -> - file:close(FD). - -reopen_log(undefined, undefined) -> - ok; -reopen_log(FN, FD) -> - close_log(FD), - open_log(FN). - -reopen_log(Host) -> - gen_server:cast(get_proc_name(Host), reopen_log). - -add_to_log(FileSize, Code, Request) -> - gen_server:cast(get_proc_name(Request#request.host), - {add_to_log, FileSize, Code, Request}). - -add_to_log(undefined, _FileSize, _Code, _Request) -> - ok; -add_to_log(File, FileSize, Code, Request) -> - {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:local_time(), - IP = ip_to_string(element(1, Request#request.ip)), - Path = join(Request#request.path, "/"), - Query = case join(lists:map(fun(E) -> lists:concat([element(1, E), "=", binary_to_list(element(2, E))]) end, - Request#request.q), "&") of - [] -> - ""; - String -> - [$? | String] - end, - UserAgent = find_header('User-Agent', Request#request.headers, "-"), - Referer = find_header('Referer', Request#request.headers, "-"), - %% Pseudo Combined Apache log format: - %% 127.0.0.1 - - [28/Mar/2007:18:41:55 +0200] "GET / HTTP/1.1" 302 303 "-" "tsung" - %% TODO some fields are harcoded/missing: - %% The date/time integers should have always 2 digits. For example day "7" should be "07" - %% Month should be 3*letter, not integer 1..12 - %% Missing time zone = (`+' | `-') 4*digit - %% Missing protocol version: HTTP/1.1 - %% For reference: http://httpd.apache.org/docs/2.2/logs.html - io:format(File, "~s - - [~p/~p/~p:~p:~p:~p] \"~s /~s~s\" ~p ~p ~p ~p~n", - [IP, Day, Month, Year, Hour, Minute, Second, Request#request.method, Path, Query, Code, - FileSize, Referer, UserAgent]). - -find_header(Header, Headers, Default) -> - case lists:keysearch(Header, 1, Headers) of - {value, {_, Value}} -> Value; - false -> Default - end. - -%%---------------------------------------------------------------------- -%% Utilities -%%---------------------------------------------------------------------- - -get_proc_name(Host) -> gen_mod:get_module_proc(Host, ?PROCNAME). - -join([], _) -> - <<"">>; -join([E], _) -> - E; -join([H | T], Separator) -> - [H2 | T2] = case is_binary(H) of true -> [binary_to_list(I)||I<-[H|T]]; false -> [H | T] end, - Res=lists:foldl(fun(E, Acc) -> lists:concat([Acc, Separator, E]) end, H2, T2), - case is_binary(H) of true -> list_to_binary(Res); false -> Res end. - -content_type(Filename, DefaultContentType, ContentTypes) -> - Extension = str:to_lower(filename:extension(Filename)), - case lists:keysearch(Extension, 1, ContentTypes) of - {value, {_, ContentType}} -> ContentType; - false -> DefaultContentType - end. - -last_modified(FileInfo) -> - Then = FileInfo#file_info.mtime, - httpd_util:rfc1123_date(Then). - -%% Convert IP address tuple to string representation. Accepts either -%% IPv4 or IPv6 address tuples. -ip_to_string(Address) when size(Address) == 4 -> - join(tuple_to_list(Address), "."); -ip_to_string(Address) when size(Address) == 8 -> - Parts = lists:map(fun (Int) -> io_lib:format("~.16B", [Int]) end, tuple_to_list(Address)), - string:to_lower(lists:flatten(join(Parts, ":"))). diff --git a/src/web/mod_register_web.erl b/src/web/mod_register_web.erl deleted file mode 100644 index 0ccba9945..000000000 --- a/src/web/mod_register_web.erl +++ /dev/null @@ -1,565 +0,0 @@ -%%%------------------------------------------------------------------- -%%% File : mod_register_web.erl -%%% Author : Badlop <badlop@process-one.net> -%%% Purpose : Web page to register account and related tasks -%%% Created : 4 May 2008 by Badlop <badlop@process-one.net> -%%% -%%% -%%% ejabberd, Copyright (C) 2002-2013 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., 59 Temple Place, Suite 330, Boston, MA -%%% 02111-1307 USA -%%% -%%%---------------------------------------------------------------------- - -%%% IDEAS: -%%% -%%% * Implement those options, already present in mod_register: -%%% + access -%%% + captcha_protected -%%% + password_strength -%%% + welcome_message -%%% + registration_timeout -%%% -%%% * Improve this module to allow each virtual host to have different -%%% options. See http://support.process-one.net/browse/EJAB-561 -%%% -%%% * Check that all the text is translatable. -%%% -%%% * Add option to use a custom CSS file, or custom CSS lines. -%%% -%%% * Don't hardcode the "register" path in URL. -%%% -%%% * Allow private email during register, and store in custom table. -%%% * Optionally require private email to register. -%%% * Optionally require email confirmation to register. -%%% * Allow to set a private email address anytime. -%%% * Allow to recover password using private email to confirm (mod_passrecover) -%%% * Optionally require invitation -%%% * Optionally register request is forwarded to admin, no account created. - --module(mod_register_web). - --author('badlop@process-one.net'). - --behaviour(gen_mod). - --export([start/2, stop/1, process/2]). - --include("ejabberd.hrl"). - --include("jlib.hrl"). - --include("ejabberd_http.hrl"). - --include("ejabberd_web_admin.hrl"). - -%%%---------------------------------------------------------------------- -%%% gen_mod callbacks -%%%---------------------------------------------------------------------- - -start(_Host, _Opts) -> - %% case gen_mod:get_opt(docroot, Opts, fun(A) -> A end, undefined) of - ok. - -stop(_Host) -> ok. - -%%%---------------------------------------------------------------------- -%%% HTTP handlers -%%%---------------------------------------------------------------------- - -process([], #request{method = 'GET', lang = Lang}) -> - index_page(Lang); -process([<<"register.css">>], - #request{method = 'GET'}) -> - serve_css(); -process([<<"new">>], - #request{method = 'GET', lang = Lang, host = Host, - ip = IP}) -> - {Addr, _Port} = IP, form_new_get(Host, Lang, Addr); -process([<<"delete">>], - #request{method = 'GET', lang = Lang, host = Host}) -> - form_del_get(Host, Lang); -process([<<"change_password">>], - #request{method = 'GET', lang = Lang, host = Host}) -> - form_changepass_get(Host, Lang); -process([<<"new">>], - #request{method = 'POST', q = Q, ip = {Ip, _Port}, - lang = Lang, host = Host}) -> - case form_new_post(Q, Host) of - {success, ok, {Username, Host, _Password}} -> - Jid = jlib:make_jid(Username, Host, <<"">>), - mod_register:send_registration_notifications(?MODULE, Jid, Ip), - Text = (?T(<<"Your Jabber account was successfully " - "created.">>)), - {200, [], Text}; - Error -> - ErrorText = - list_to_binary([?T(<<"There was an error creating the account: ">>), - ?T(get_error_text(Error))]), - {404, [], ErrorText} - end; -process([<<"delete">>], - #request{method = 'POST', q = Q, lang = Lang, - host = Host}) -> - case form_del_post(Q, Host) of - {atomic, ok} -> - Text = (?T(<<"Your Jabber account was successfully " - "deleted.">>)), - {200, [], Text}; - Error -> - ErrorText = - list_to_binary([?T(<<"There was an error deleting the account: ">>), - ?T(get_error_text(Error))]), - {404, [], ErrorText} - end; -%% TODO: Currently only the first vhost is usable. The web request record -%% should include the host where the POST was sent. -process([<<"change_password">>], - #request{method = 'POST', q = Q, lang = Lang, - host = Host}) -> - case form_changepass_post(Q, Host) of - {atomic, ok} -> - Text = (?T(<<"The password of your Jabber account " - "was successfully changed.">>)), - {200, [], Text}; - Error -> - ErrorText = - list_to_binary([?T(<<"There was an error changing the password: ">>), - ?T(get_error_text(Error))]), - {404, [], ErrorText} - end. - -%%%---------------------------------------------------------------------- -%%% CSS -%%%---------------------------------------------------------------------- - -serve_css() -> - {200, - [{<<"Content-Type">>, <<"text/css">>}, last_modified(), - cache_control_public()], - css()}. - -last_modified() -> - {<<"Last-Modified">>, - <<"Mon, 25 Feb 2008 13:23:30 GMT">>}. - -cache_control_public() -> - {<<"Cache-Control">>, <<"public">>}. - -css() -> - <<"html,body {\nbackground: white;\nmargin: " - "0;\npadding: 0;\nheight: 100%;\n}">>. - -%%%---------------------------------------------------------------------- -%%% Index page -%%%---------------------------------------------------------------------- - -index_page(Lang) -> - HeadEls = [?XCT(<<"title">>, - <<"Jabber Account Registration">>), - ?XA(<<"link">>, - [{<<"href">>, <<"/register/register.css">>}, - {<<"type">>, <<"text/css">>}, - {<<"rel">>, <<"stylesheet">>}])], - Els = [?XACT(<<"h1">>, - [{<<"class">>, <<"title">>}, - {<<"style">>, <<"text-align:center;">>}], - <<"Jabber Account Registration">>), - ?XE(<<"ul">>, - [?XE(<<"li">>, - [?ACT(<<"new">>, <<"Register a Jabber account">>)]), - ?XE(<<"li">>, - [?ACT(<<"change_password">>, <<"Change Password">>)]), - ?XE(<<"li">>, - [?ACT(<<"delete">>, - <<"Unregister a Jabber account">>)])])], - {200, - [{<<"Server">>, <<"ejabberd">>}, - {<<"Content-Type">>, <<"text/html">>}], - ejabberd_web:make_xhtml(HeadEls, Els)}. - -%%%---------------------------------------------------------------------- -%%% Formulary new account GET -%%%---------------------------------------------------------------------- - -form_new_get(Host, Lang, IP) -> - CaptchaEls = build_captcha_li_list(Lang, IP), - HeadEls = [?XCT(<<"title">>, - <<"Register a Jabber account">>), - ?XA(<<"link">>, - [{<<"href">>, <<"/register/register.css">>}, - {<<"type">>, <<"text/css">>}, - {<<"rel">>, <<"stylesheet">>}])], - Els = [?XACT(<<"h1">>, - [{<<"class">>, <<"title">>}, - {<<"style">>, <<"text-align:center;">>}], - <<"Register a Jabber account">>), - ?XCT(<<"p">>, - <<"This page allows to create a Jabber " - "account in this Jabber server. Your " - "JID (Jabber IDentifier) will be of the " - "form: username@server. Please read carefully " - "the instructions to fill correctly the " - "fields.">>), - ?XAE(<<"form">>, - [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], - [?XE(<<"ol">>, - ([?XE(<<"li">>, - [?CT(<<"Username:">>), ?C(<<" ">>), - ?INPUTS(<<"text">>, <<"username">>, <<"">>, - <<"20">>), - ?BR, - ?XE(<<"ul">>, - [?XCT(<<"li">>, - <<"This is case insensitive: macbeth is " - "the same that MacBeth and Macbeth.">>), - ?XC(<<"li">>, - <<(?T(<<"Characters not allowed:">>))/binary, - " \" & ' / : < > @ ">>)])]), - ?XE(<<"li">>, - [?CT(<<"Server:">>), ?C(<<" ">>), ?C(Host)]), - ?XE(<<"li">>, - [?CT(<<"Password:">>), ?C(<<" ">>), - ?INPUTS(<<"password">>, <<"password">>, <<"">>, - <<"20">>), - ?BR, - ?XE(<<"ul">>, - [?XCT(<<"li">>, - <<"Don't tell your password to anybody, " - "not even the administrators of the Jabber " - "server.">>), - ?XCT(<<"li">>, - <<"You can later change your password using " - "a Jabber client.">>), - ?XCT(<<"li">>, - <<"Some Jabber clients can store your password " - "in your computer. Use that feature only " - "if you trust your computer is safe.">>), - ?XCT(<<"li">>, - <<"Memorize your password, or write it " - "in a paper placed in a safe place. In " - "Jabber there isn't an automated way " - "to recover your password if you forget " - "it.">>)])]), - ?XE(<<"li">>, - [?CT(<<"Password Verification:">>), ?C(<<" ">>), - ?INPUTS(<<"password">>, <<"password2">>, <<"">>, - <<"20">>)])] - ++ - CaptchaEls ++ - [?XE(<<"li">>, - [?INPUTT(<<"submit">>, <<"register">>, - <<"Register">>)])]))])], - {200, - [{<<"Server">>, <<"ejabberd">>}, - {<<"Content-Type">>, <<"text/html">>}], - ejabberd_web:make_xhtml(HeadEls, Els)}. - -%% Copied from mod_register.erl -%% Function copied from ejabberd_logger_h.erl and customized -%%%---------------------------------------------------------------------- -%%% Formulary new POST -%%%---------------------------------------------------------------------- - -form_new_post(Q, Host) -> - case catch get_register_parameters(Q) of - [Username, Password, Password, Id, Key] -> - form_new_post(Username, Host, Password, {Id, Key}); - [_Username, _Password, _Password2, false, false] -> - {error, passwords_not_identical}; - [_Username, _Password, _Password2, Id, Key] -> - ejabberd_captcha:check_captcha(Id, Key), - {error, passwords_not_identical}; - _ -> {error, wrong_parameters} - end. - -get_register_parameters(Q) -> - lists:map(fun (Key) -> - case lists:keysearch(Key, 1, Q) of - {value, {_Key, Value}} -> Value; - false -> false - end - end, - [<<"username">>, <<"password">>, <<"password2">>, - <<"id">>, <<"key">>]). - -form_new_post(Username, Host, Password, - {false, false}) -> - register_account(Username, Host, Password); -form_new_post(Username, Host, Password, {Id, Key}) -> - case ejabberd_captcha:check_captcha(Id, Key) of - captcha_valid -> - register_account(Username, Host, Password); - captcha_non_valid -> {error, captcha_non_valid}; - captcha_not_found -> {error, captcha_non_valid} - end. - -%%%---------------------------------------------------------------------- -%%% Formulary Captcha support for new GET/POST -%%%---------------------------------------------------------------------- - -build_captcha_li_list(Lang, IP) -> - case ejabberd_captcha:is_feature_available() of - true -> build_captcha_li_list2(Lang, IP); - false -> [] - end. - -build_captcha_li_list2(Lang, IP) -> - SID = <<"">>, - From = #jid{user = <<"">>, server = <<"test">>, - resource = <<"">>}, - To = #jid{user = <<"">>, server = <<"test">>, - resource = <<"">>}, - Args = [], - case ejabberd_captcha:create_captcha(SID, From, To, - Lang, IP, Args) - of - {ok, Id, _} -> - {_, {CImg, CText, CId, CKey}} = - ejabberd_captcha:build_captcha_html(Id, Lang), - [?XE(<<"li">>, - [CText, ?C(<<" ">>), CId, CKey, ?BR, CImg])]; - _ -> [] - end. - -%%%---------------------------------------------------------------------- -%%% Formulary change password GET -%%%---------------------------------------------------------------------- - -form_changepass_get(Host, Lang) -> - HeadEls = [?XCT(<<"title">>, <<"Change Password">>), - ?XA(<<"link">>, - [{<<"href">>, <<"/register/register.css">>}, - {<<"type">>, <<"text/css">>}, - {<<"rel">>, <<"stylesheet">>}])], - Els = [?XACT(<<"h1">>, - [{<<"class">>, <<"title">>}, - {<<"style">>, <<"text-align:center;">>}], - <<"Change Password">>), - ?XAE(<<"form">>, - [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], - [?XE(<<"ol">>, - [?XE(<<"li">>, - [?CT(<<"Username:">>), ?C(<<" ">>), - ?INPUTS(<<"text">>, <<"username">>, <<"">>, - <<"20">>)]), - ?XE(<<"li">>, - [?CT(<<"Server:">>), ?C(<<" ">>), ?C(Host)]), - ?XE(<<"li">>, - [?CT(<<"Old Password:">>), ?C(<<" ">>), - ?INPUTS(<<"password">>, <<"passwordold">>, <<"">>, - <<"20">>)]), - ?XE(<<"li">>, - [?CT(<<"New Password:">>), ?C(<<" ">>), - ?INPUTS(<<"password">>, <<"password">>, <<"">>, - <<"20">>)]), - ?XE(<<"li">>, - [?CT(<<"Password Verification:">>), ?C(<<" ">>), - ?INPUTS(<<"password">>, <<"password2">>, <<"">>, - <<"20">>)]), - ?XE(<<"li">>, - [?INPUTT(<<"submit">>, <<"changepass">>, - <<"Change Password">>)])])])], - {200, - [{<<"Server">>, <<"ejabberd">>}, - {<<"Content-Type">>, <<"text/html">>}], - ejabberd_web:make_xhtml(HeadEls, Els)}. - -%%%---------------------------------------------------------------------- -%%% Formulary change password POST -%%%---------------------------------------------------------------------- - -form_changepass_post(Q, Host) -> - case catch get_changepass_parameters(Q) of - [Username, PasswordOld, Password, Password] -> - try_change_password(Username, Host, PasswordOld, - Password); - [_Username, _PasswordOld, _Password, _Password2] -> - {error, passwords_not_identical}; - _ -> {error, wrong_parameters} - end. - -get_changepass_parameters(Q) -> -%% @spec(Username,Host,PasswordOld,Password) -> {atomic, ok} | -%% {error, account_doesnt_exist} | -%% {error, password_not_changed} | -%% {error, password_incorrect} - lists:map(fun (Key) -> - {value, {_Key, Value}} = lists:keysearch(Key, 1, Q), - Value - end, - [<<"username">>, <<"passwordold">>, <<"password">>, - <<"password2">>]). - -try_change_password(Username, Host, PasswordOld, - Password) -> - try change_password(Username, Host, PasswordOld, - Password) - of - {atomic, ok} -> {atomic, ok} - catch - error:{badmatch, Error} -> {error, Error} - end. - -change_password(Username, Host, PasswordOld, - Password) -> - account_exists = check_account_exists(Username, Host), - password_correct = check_password(Username, Host, - PasswordOld), - ok = ejabberd_auth:set_password(Username, Host, - Password), - case check_password(Username, Host, Password) of - password_correct -> {atomic, ok}; - password_incorrect -> {error, password_not_changed} - end. - -check_account_exists(Username, Host) -> - case ejabberd_auth:is_user_exists(Username, Host) of - true -> account_exists; - false -> account_doesnt_exist - end. - -check_password(Username, Host, Password) -> - case ejabberd_auth:check_password(Username, Host, - Password) - of - true -> password_correct; - false -> password_incorrect - end. - -%%%---------------------------------------------------------------------- -%%% Formulary delete account GET -%%%---------------------------------------------------------------------- - -form_del_get(Host, Lang) -> - HeadEls = [?XCT(<<"title">>, - <<"Unregister a Jabber account">>), - ?XA(<<"link">>, - [{<<"href">>, <<"/register/register.css">>}, - {<<"type">>, <<"text/css">>}, - {<<"rel">>, <<"stylesheet">>}])], - Els = [?XACT(<<"h1">>, - [{<<"class">>, <<"title">>}, - {<<"style">>, <<"text-align:center;">>}], - <<"Unregister a Jabber account">>), - ?XCT(<<"p">>, - <<"This page allows to unregister a Jabber " - "account in this Jabber server.">>), - ?XAE(<<"form">>, - [{<<"action">>, <<"">>}, {<<"method">>, <<"post">>}], - [?XE(<<"ol">>, - [?XE(<<"li">>, - [?CT(<<"Username:">>), ?C(<<" ">>), - ?INPUTS(<<"text">>, <<"username">>, <<"">>, - <<"20">>)]), - ?XE(<<"li">>, - [?CT(<<"Server:">>), ?C(<<" ">>), ?C(Host)]), - ?XE(<<"li">>, - [?CT(<<"Password:">>), ?C(<<" ">>), - ?INPUTS(<<"password">>, <<"password">>, <<"">>, - <<"20">>)]), - ?XE(<<"li">>, - [?INPUTT(<<"submit">>, <<"unregister">>, - <<"Unregister">>)])])])], - {200, - [{<<"Server">>, <<"ejabberd">>}, - {<<"Content-Type">>, <<"text/html">>}], - ejabberd_web:make_xhtml(HeadEls, Els)}. - -%% @spec(Username, Host, Password) -> {success, ok, {Username, Host, Password} | -%% {success, exists, {Username, Host, Password}} | -%% {error, not_allowed} | -%% {error, invalid_jid} -register_account(Username, Host, Password) -> - case jlib:make_jid(Username, Host, <<"">>) of - error -> {error, invalid_jid}; - _ -> register_account2(Username, Host, Password) - end. - -register_account2(Username, Host, Password) -> - case ejabberd_auth:try_register(Username, Host, - Password) - of - {atomic, Res} -> - {success, Res, {Username, Host, Password}}; - Other -> Other - end. - -%%%---------------------------------------------------------------------- -%%% Formulary delete POST -%%%---------------------------------------------------------------------- - -form_del_post(Q, Host) -> - case catch get_unregister_parameters(Q) of - [Username, Password] -> - try_unregister_account(Username, Host, Password); - _ -> {error, wrong_parameters} - end. - -get_unregister_parameters(Q) -> -%% @spec(Username, Host, Password) -> {atomic, ok} | -%% {error, account_doesnt_exist} | -%% {error, account_exists} | -%% {error, password_incorrect} - lists:map(fun (Key) -> - {value, {_Key, Value}} = lists:keysearch(Key, 1, Q), - Value - end, - [<<"username">>, <<"password">>]). - -try_unregister_account(Username, Host, Password) -> - try unregister_account(Username, Host, Password) of - {atomic, ok} -> {atomic, ok} - catch - error:{badmatch, Error} -> {error, Error} - end. - -unregister_account(Username, Host, Password) -> - account_exists = check_account_exists(Username, Host), - password_correct = check_password(Username, Host, - Password), - ok = ejabberd_auth:remove_user(Username, Host, - Password), - account_doesnt_exist = check_account_exists(Username, - Host), - {atomic, ok}. - -%%%---------------------------------------------------------------------- -%%% Error texts -%%%---------------------------------------------------------------------- - -get_error_text({error, captcha_non_valid}) -> - <<"The captcha you entered is wrong">>; -get_error_text({success, exists, _}) -> - get_error_text({atomic, exists}); -get_error_text({atomic, exists}) -> - <<"The account already exists">>; -get_error_text({error, password_incorrect}) -> - <<"Incorrect password">>; -get_error_text({error, invalid_jid}) -> - <<"The username is not valid">>; -get_error_text({error, not_allowed}) -> - <<"Not allowed">>; -get_error_text({error, account_doesnt_exist}) -> - <<"Account doesn't exist">>; -get_error_text({error, account_exists}) -> - <<"The account was not deleted">>; -get_error_text({error, password_not_changed}) -> - <<"The password was not changed">>; -get_error_text({error, passwords_not_identical}) -> - <<"The passwords are different">>; -get_error_text({error, wrong_parameters}) -> - <<"Wrong parameters in the web formulary">>. |