summaryrefslogtreecommitdiff
path: root/src/web
diff options
context:
space:
mode:
Diffstat (limited to 'src/web')
-rw-r--r--src/web/Makefile.in33
-rw-r--r--src/web/ejabberd_http.erl401
-rw-r--r--src/web/ejabberd_web.erl104
3 files changed, 538 insertions, 0 deletions
diff --git a/src/web/Makefile.in b/src/web/Makefile.in
new file mode 100644
index 00000000..806bbed1
--- /dev/null
+++ b/src/web/Makefile.in
@@ -0,0 +1,33 @@
+# $Id$
+
+CC = @CC@
+CFLAGS = @CFLAGS@
+CPPFLAGS = @CPPFLAGS@
+LDFLAGS = @LDFLAGS@
+LIBS = @LIBS@
+
+INCLUDES = @ERLANG_CFLAGS@
+
+LIBDIRS = @ERLANG_LIBS@
+
+SUBDIRS =
+
+
+OUTDIR = ..
+EFLAGS = -I .. -pz ..
+OBJS = \
+ $(OUTDIR)/ejabberd_http.beam \
+ $(OUTDIR)/ejabberd_web.beam
+
+all: $(OBJS)
+
+$(OUTDIR)/%.beam: %.erl
+ erlc -W $(EFLAGS) -o $(OUTDIR) $<
+
+
+clean:
+ rm -f $(OBJS)
+
+TAGS:
+ etags *.erl
+
diff --git a/src/web/ejabberd_http.erl b/src/web/ejabberd_http.erl
new file mode 100644
index 00000000..cbae8b3b
--- /dev/null
+++ b/src/web/ejabberd_http.erl
@@ -0,0 +1,401 @@
+%%%----------------------------------------------------------------------
+%%% File : ejabberd_http.erl
+%%% Author : Alexey Shchepin <alexey@sevcom.net>
+%%% Purpose :
+%%% Created : 27 Feb 2004 by Alexey Shchepin <alexey@sevcom.net>
+%%% Id : $Id$
+%%%----------------------------------------------------------------------
+
+-module(ejabberd_http).
+-author('alexey@sevcom.net').
+-vsn('$Revision$ ').
+
+%% External exports
+-export([start/2,
+ start_link/2,
+ receive_headers/1]).
+
+-include("ejabberd.hrl").
+-include("jlib.hrl").
+
+-record(state, {sockmod,
+ socket,
+ request_method,
+ request_path,
+ request_auth,
+ request_content_length
+ }).
+
+
+-define(XHTML_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({SockMod, Socket}, Opts) ->
+ ?INFO_MSG("started: ~p", [{SockMod, Socket}]),
+ case SockMod of
+ gen_tcp ->
+ inet:setopts(Socket, [{packet, http}]);
+ ssl ->
+ ssl:setopts(Socket, [{packet, http}])
+ end,
+ {ok, proc_lib:spawn_link(ejabberd_http,
+ receive_headers,
+ [#state{sockmod = SockMod, socket = Socket}])}.
+
+
+send_text(State, Text) ->
+ (State#state.sockmod):send(State#state.socket, Text).
+
+
+receive_headers(State) ->
+ Data = (State#state.sockmod):recv(State#state.socket, 0, 300000),
+ ?INFO_MSG("recv: ~p~n", [Data]),
+ case Data of
+ {ok, {http_request, Method, Path, _Version}} ->
+ receive_headers(State#state{request_method = Method,
+ request_path = Path});
+ {ok, {http_header, _, 'Authorization', _, Auth}} ->
+ receive_headers(State#state{request_auth = parse_auth(Auth)});
+ {ok, {http_header, _, 'Content-Length', _, SLen}} ->
+ case catch list_to_integer(SLen) of
+ Len when is_integer(Len) ->
+ receive_headers(State#state{request_content_length = Len});
+ _ ->
+ receive_headers(State)
+ end;
+ {ok, {http_header, _, _, _, _}} ->
+ receive_headers(State);
+ {ok, http_eoh} ->
+ Out = process_request(State),
+ send_text(State, Out),
+ ok;
+ {error, _Reason} ->
+ ok;
+ _ ->
+ ok
+ end.
+
+
+process_request(#state{request_method = 'GET',
+ request_path = {abs_path, Path},
+ request_auth = undefined}) ->
+ make_xhtml_output(
+ 401,
+ [{"WWW-Authenticate", "basic realm=\"ejabberd\""}],
+ ejabberd_web:make_xhtml([{xmlelement, "h1", [],
+ [{xmlcdata, "401 Unauthorized"}]}]));
+
+process_request(#state{request_method = 'GET',
+ request_path = {abs_path, Path},
+ request_auth = {User, Pass}}) ->
+ case ejabberd_auth:check_password(User, Pass) of
+ true ->
+ case (catch url_decode_q_split(Path)) of
+ {'EXIT', _} ->
+ process_request(false);
+ {NPath, Query} ->
+ LQuery = parse_urlencoded(Query),
+ ?INFO_MSG("path: ~p, query: ~p~n", [NPath, LQuery]),
+ LPath = string:tokens(NPath, "/"),
+ case ejabberd_web:process_get(User, LPath, LQuery) of
+ El when element(1, El) == xmlelement ->
+ make_xhtml_output(200, [], El);
+ {Status, Headers, El} ->
+ make_xhtml_output(Status, Headers, El)
+ end
+ end;
+ _ ->
+ make_xhtml_output(
+ 401,
+ [{"WWW-Authenticate", "basic realm=\"ejabberd\""}],
+ ejabberd_web:make_xhtml([{xmlelement, "h1", [],
+ [{xmlcdata, "401 Unauthorized"}]}]))
+ end;
+
+process_request(#state{request_method = 'POST',
+ request_path = {abs_path, Path},
+ request_auth = {User, Pass},
+ request_content_length = Len,
+ sockmod = SockMod,
+ socket = Socket} = State) when is_integer(Len) ->
+ case ejabberd_auth:check_password(User, Pass) of
+ true ->
+ case SockMod of
+ gen_tcp ->
+ inet:setopts(Socket, [{packet, 0}]);
+ ssl ->
+ ssl:setopts(Socket, [{packet, 0}])
+ end,
+ Data = recv_data(State, Len),
+ ?INFO_MSG("client data: ~p~n", [Data]),
+ case (catch url_decode_q_split(Path)) of
+ {'EXIT', _} ->
+ process_request(false);
+ {NPath, Query} ->
+ ?INFO_MSG("path: ~p, query: ~p~n", [NPath, Query]),
+ LPath = string:tokens(NPath, "/"),
+ LQuery = parse_urlencoded(Data),
+ ?INFO_MSG("client query: ~p~n", [LQuery]),
+ case ejabberd_web:process_get(User, LPath, LQuery) of
+ El when element(1, El) == xmlelement ->
+ make_xhtml_output(200, [], El);
+ {Status, Headers, El} ->
+ make_xhtml_output(Status, Headers, El)
+ end
+ end;
+ _ ->
+ make_xhtml_output(
+ 401,
+ [{"WWW-Authenticate", "basic realm=\"ejabberd\""}],
+ ejabberd_web:make_xhtml([{xmlelement, "h1", [],
+ [{xmlcdata, "401 Unauthorized"}]}]))
+ end;
+
+process_request(State) ->
+ make_xhtml_output(
+ 400,
+ [],
+ ejabberd_web:make_xhtml([{xmlelement, "h1", [],
+ [{xmlcdata, "400 Bad Request"}]}])).
+
+
+recv_data(State, Len) ->
+ recv_data(State, Len, []).
+
+recv_data(State, 0, Acc) ->
+ binary_to_list(list_to_binary(Acc));
+recv_data(State, Len, Acc) ->
+ case (State#state.sockmod):recv(State#state.socket, Len, 300000) of
+ {ok, Data} ->
+ recv_data(State, Len - size(Data), [Acc | Data]);
+ _ ->
+ ""
+ end.
+
+
+make_xhtml_output(Status, Headers, XHTML) ->
+ Data = list_to_binary([?XHTML_DOCTYPE, xml:element_to_string(XHTML)]),
+ Headers1 = [{"Content-Type", "text/html; charset=utf-8"},
+ {"Content-Length", integer_to_list(size(Data))} | Headers],
+ H = lists:map(fun({Attr, Val}) ->
+ [Attr, ": ", Val, "\r\n"]
+ end, Headers1),
+ SL = ["HTTP/1.1 ", integer_to_list(Status), " ",
+ code_to_phrase(Status), "\r\n"],
+ [SL, H, "\r\n", Data].
+
+
+
+% 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.
+
+
+%% url decode the path and return {Path, QueryPart}
+
+url_decode_q_split(Path) ->
+ url_decode_q_split(Path, []).
+
+url_decode_q_split([$%, $C, $2, $%, Hi, Lo | Tail], Ack) ->
+ Hex = hex_to_integer([Hi, Lo]),
+ url_decode_q_split(Tail, [Hex|Ack]);
+url_decode_q_split([$%, $C, $3, $%, Hi, Lo | Tail], Ack) when Hi > $9 ->
+ Hex = hex_to_integer([Hi+4, Lo]),
+ url_decode_q_split(Tail, [Hex|Ack]);
+url_decode_q_split([$%, $C, $3, $%, Hi, Lo | Tail], Ack) when Hi < $A ->
+ Hex = hex_to_integer([Hi+4+7, Lo]),
+ url_decode_q_split(Tail, [Hex|Ack]);
+url_decode_q_split([$%, Hi, Lo | Tail], Ack) ->
+ Hex = hex_to_integer([Hi, Lo]),
+ url_decode_q_split(Tail, [Hex|Ack]);
+url_decode_q_split([$?|T], Ack) ->
+ %% Don't decode the query string here, that is parsed separately.
+ {path_norm_reverse(Ack), T};
+url_decode_q_split([H|T], Ack) ->
+ url_decode_q_split(T, [H|Ack]);
+url_decode_q_split([], Ack) ->
+ {path_norm_reverse(Ack), []}.
+
+path_norm_reverse("/" ++ T) -> 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 ) -> start_dir(N , Path, T);
+start_dir(N, Path, "./" ++ T ) -> start_dir(N , Path, T);
+start_dir(N, Path, "../" ++ T ) -> 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 ] ) -> start_dir(0 , [ $/ | Path ], T);
+rest_dir (N, Path, [ $/ | T ] ) -> start_dir(N - 1, Path , T);
+rest_dir (0, Path, [ H | T ] ) -> rest_dir (0 , [ H | Path ], T);
+rest_dir (N, Path, [ _H | T ] ) -> rest_dir (N , Path , T).
+
+
+%% hex_to_integer
+
+
+hex_to_integer(Hex) ->
+ case catch erlang: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(Orig = "Basic " ++ Auth64) ->
+ case decode_base64(Auth64) of
+ {error, _Err} ->
+ undefined;
+ Auth ->
+ case string:tokens(Auth, ":") of
+ [User, Pass] ->
+ {User, Pass};
+ _ ->
+ undefined
+ end
+ end;
+parse_auth(_) ->
+ undefined.
+
+
+
+decode_base64([]) ->
+ [];
+decode_base64([Sextet1,Sextet2,$=,$=|Rest]) ->
+ Bits2x6=
+ (d(Sextet1) bsl 18) bor
+ (d(Sextet2) bsl 12),
+ Octet1=Bits2x6 bsr 16,
+ [Octet1|decode_base64(Rest)];
+decode_base64([Sextet1,Sextet2,Sextet3,$=|Rest]) ->
+ Bits3x6=
+ (d(Sextet1) bsl 18) bor
+ (d(Sextet2) bsl 12) bor
+ (d(Sextet3) bsl 6),
+ Octet1=Bits3x6 bsr 16,
+ Octet2=(Bits3x6 bsr 8) band 16#ff,
+ [Octet1,Octet2|decode_base64(Rest)];
+decode_base64([Sextet1,Sextet2,Sextet3,Sextet4|Rest]) ->
+ Bits4x6=
+ (d(Sextet1) bsl 18) bor
+ (d(Sextet2) bsl 12) bor
+ (d(Sextet3) bsl 6) bor
+ d(Sextet4),
+ Octet1=Bits4x6 bsr 16,
+ Octet2=(Bits4x6 bsr 8) band 16#ff,
+ Octet3=Bits4x6 band 16#ff,
+ [Octet1,Octet2,Octet3|decode_base64(Rest)];
+decode_base64(_CatchAll) ->
+ {error, bad_base64}.
+
+d(X) when X >= $A, X =<$Z ->
+ X-65;
+d(X) when X >= $a, X =<$z ->
+ X-71;
+d(X) when X >= $0, X =<$9 ->
+ X+4;
+d($+) -> 62;
+d($/) -> 63;
+d(_) -> 63.
+
+
+parse_urlencoded(S) ->
+ parse_urlencoded(S, nokey, [], key).
+
+parse_urlencoded([$%, Hi, Lo | Tail], Last, Cur, State) ->
+ Hex = hex_to_integer([Hi, Lo]),
+ parse_urlencoded(Tail, Last, [Hex | Cur], State);
+
+parse_urlencoded([$& | Tail], _Last, Cur, key) ->
+ [{lists:reverse(Cur), ""} |
+ parse_urlencoded(Tail, nokey, [], key)]; %% cont keymode
+
+parse_urlencoded([$& | Tail], Last, Cur, value) ->
+ V = {Last, lists:reverse(Cur)},
+ [V | parse_urlencoded(Tail, nokey, [], key)];
+
+parse_urlencoded([$+ | Tail], Last, Cur, State) ->
+ parse_urlencoded(Tail, Last, [$\s | Cur], State);
+
+parse_urlencoded([$= | Tail], _Last, Cur, key) ->
+ parse_urlencoded(Tail, lists:reverse(Cur), [], value); %% change mode
+
+parse_urlencoded([H | Tail], Last, Cur, State) ->
+ parse_urlencoded(Tail, Last, [H|Cur], State);
+
+parse_urlencoded([], Last, Cur, _State) ->
+ [{Last, lists:reverse(Cur)}];
+
+parse_urlencoded(undefined, _, _, _) ->
+ [].
+
+
diff --git a/src/web/ejabberd_web.erl b/src/web/ejabberd_web.erl
new file mode 100644
index 00000000..c8349439
--- /dev/null
+++ b/src/web/ejabberd_web.erl
@@ -0,0 +1,104 @@
+%%%----------------------------------------------------------------------
+%%% File : ejabberd_web.erl
+%%% Author : Alexey Shchepin <alexey@sevcom.net>
+%%% Purpose :
+%%% Created : 28 Feb 2004 by Alexey Shchepin <alexey@sevcom.net>
+%%% Id : $Id$
+%%%----------------------------------------------------------------------
+
+-module(ejabberd_web).
+-author('alexey@sevcom.net').
+-vsn('$Revision$ ').
+
+%% External exports
+-export([make_xhtml/1,
+ process_get/3]).
+
+-include("ejabberd.hrl").
+-include("jlib.hrl").
+
+
+make_xhtml(Els) ->
+ {xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"},
+ {"xml:lang", "en"},
+ {"lang", "en"}],
+ [{xmlelement, "head", [],
+ [{xmlelement, "meta", [{"http-equiv", "Content-Type"},
+ {"content", "text/html; charset=utf-8"}], []}]},
+ {xmlelement, "body", [], Els}
+ ]}.
+
+
+-define(X(Name), {xmlelement, Name, [], []}).
+-define(XA(Name, Attrs), {xmlelement, Name, Attrs, []}).
+-define(XE(Name, Els), {xmlelement, Name, [], Els}).
+-define(XAE(Name, Attrs, Els), {xmlelement, Name, Attrs, 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")).
+
+process_get(User, [], Query) ->
+ make_xhtml([?XC("h1", "ejabberd configuration"),
+ ?XE("ul",
+ [?LI([?AC("acls/", "Access Control Lists")]),
+ ?LI([?AC("access/", "Access Rules")]),
+ ?LI([?AC("users/", "Users")]),
+ ?LI([?AC("nodes/", "Nodes")])
+ ])
+ ]);
+
+process_get(User, ["acls"], Query) ->
+ case acl:match_rule(configure, jlib:make_jid(User, ?MYNAME, "")) of
+ deny ->
+ {401, [], make_xhtml([?XC("h1", "Not Allowed")])};
+ allow ->
+ Res = case lists:keysearch("acls", 1, Query) of
+ {value, {_, String}} ->
+ case erl_scan:string(String) of
+ {ok, Tokens, _} ->
+ case erl_parse:parse_term(Tokens) of
+ {ok, NewACLs} ->
+ case acl:add_list(NewACLs, true) of
+ ok ->
+ ok;
+ _ ->
+ error
+ end;
+ _ ->
+ error
+ end;
+ _ ->
+ error
+ end;
+ _ ->
+ nothing
+ end,
+ ACLs = lists:flatten(io_lib:format("~p.", [ets:tab2list(acl)])),
+ make_xhtml([?XC("h1", "ejabberd ACLs configuration")] ++
+ case Res of
+ ok -> [?C("submited"), ?P];
+ error -> [?C("bad format"), ?P];
+ nothing -> []
+ end ++
+ [?XAE("form", [{"method", "post"}],
+ [?XAC("textarea", [{"name", "acls"},
+ {"rows", "16"},
+ {"cols", "80"}],
+ ACLs),
+ ?BR,
+ ?XA("input", [{"type", "submit"}])
+ ])
+ ])
+ end;
+
+process_get(User, Path, Query) ->
+ {404, [], make_xhtml([?XC("h1", "Not found")])}.
+
+
+