From 4e7e67c8b5d9c57d149592d05e7e040f59dc660b Mon Sep 17 00:00:00 2001 From: Alexey Shchepin Date: Sat, 25 Sep 2004 20:52:20 +0000 Subject: [PATCH] * src/jlib.hrl: Added namespace for iq-register stream feature * src/ejabberd_c2s.erl: Send iq-register feature * src/ejabberd_config.erl: Config file can be configured from the command line (thanks to Mickael Remond) * src/web/ejabberd_http.erl: Added SSL support (thanks to Sergei Golovan) * src/msgs/*.msg: Updated (thanks to Sergei Golovan) * src/jlib.hrl: Updated error codes (thanks to Sergei Golovan) * src/ejabberd_c2s.erl: Likewise SVN Revision: 266 --- ChangeLog | 16 ++ src/ejabberd_c2s.erl | 11 +- src/ejabberd_config.erl | 10 +- src/jlib.hrl | 23 +-- src/msgs/es.msg | 5 + src/msgs/fr.msg | 5 + src/msgs/nl.msg | 5 + src/msgs/ru.msg | 5 + src/msgs/ua.msg | 7 +- src/web/ejabberd_http.erl | 343 +++++++++++++++++++++++++++++++++----- 10 files changed, 372 insertions(+), 58 deletions(-) diff --git a/ChangeLog b/ChangeLog index d2f599d0b..9c0caa600 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,19 @@ +2004-09-25 Alexey Shchepin + + * src/jlib.hrl: Added namespace for iq-register stream feature + * src/ejabberd_c2s.erl: Send iq-register feature + + * src/ejabberd_config.erl: Config file can be configured from the + command line (thanks to Mickael Remond) + + * src/web/ejabberd_http.erl: Added SSL support (thanks to Sergei + Golovan) + + * src/msgs/*.msg: Updated (thanks to Sergei Golovan) + + * src/jlib.hrl: Updated error codes (thanks to Sergei Golovan) + * src/ejabberd_c2s.erl: Likewise + 2004-09-17 Alexey Shchepin * src/mod_muc/mod_muc_room.erl: Send password in room invitation diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 7452eb9c2..05177c97c 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -195,7 +195,10 @@ wait_for_stream({xmlstreamstart, _Name, Attrs}, StateData) -> TLSFeature ++ [{xmlelement, "mechanisms", [{"xmlns", ?NS_SASL}], - Mechs}]}), + Mechs}, + {xmlelement, "register", + [{"xmlns", ?NS_FEATURE_IQREGISTER}], + []}]}), {next_state, wait_for_feature_request, StateData#state{sasl_state = SASLState, lang = Lang}}; @@ -274,7 +277,9 @@ wait_for_auth({xmlstreamelement, El}, StateData) -> send_element(StateData, Res), {next_state, wait_for_auth, StateData}; {auth, _ID, set, {U, P, D, ""}} -> - Err = jlib:make_error_reply(El, ?ERR_AUTH_NO_RESOURCE_PROVIDED), + Err = jlib:make_error_reply( + El, + ?ERR_AUTH_NO_RESOURCE_PROVIDED(StateData#state.lang)), send_element(StateData, Err), {next_state, wait_for_auth, StateData}; {auth, _ID, set, {U, P, D, R}} -> @@ -318,7 +323,7 @@ wait_for_auth({xmlstreamelement, El}, StateData) -> [StateData#state.socket, jlib:jid_to_string(JID)]), Err = jlib:make_error_reply( - El, ?ERR_FORBIDDEN), + El, ?ERR_NOT_AUTHORIZED), send_element(StateData, Err), {next_state, wait_for_auth, StateData} end; diff --git a/src/ejabberd_config.erl b/src/ejabberd_config.erl index 2642e2ad0..a2822a388 100644 --- a/src/ejabberd_config.erl +++ b/src/ejabberd_config.erl @@ -34,9 +34,17 @@ start() -> {local_content, true}, {attributes, record_info(fields, local_config)}]), mnesia:add_table_copy(local_config, node(), ram_copies), + + %% mremond: Config file can be configured from the command line Config = case application:get_env(config) of {ok, Path} -> Path; - undefined -> ?CONFIG_PATH + undefined -> + case os:getenv("EJABBERD_CONFIG_PATH") of + false -> + ?CONFIG_PATH; + Path -> + Path + end end, load_file(Config). diff --git a/src/jlib.hrl b/src/jlib.hrl index 1e0b31992..c2a9ba43a 100644 --- a/src/jlib.hrl +++ b/src/jlib.hrl @@ -47,6 +47,7 @@ -define(NS_BIND, "urn:ietf:params:xml:ns:xmpp-bind"). -define(NS_FEATURE_IQAUTH, "http://jabber.org/features/iq-auth"). +-define(NS_FEATURE_IQREGISTER, "http://jabber.org/features/iq-register"). % TODO: remove "code" attribute (currently it used for backward-compatibility) -define(STANZA_ERROR(Code, Type, Condition), @@ -149,21 +150,13 @@ -define(ERRT_UNEXPECTED_REQUEST(Lang, Text), ?STANZA_ERRORT("400", "wait", "unexpected-request", Lang, Text)). -% TODO: update to new-style -% Application-specific stanza errors --define(AUTH_STANZA_ERROR(Condition), - {xmlelement, "error", - [{"code", "406"}, {"class", "app"}], - [{xmlelement, "auth-condition", - [{"xmlns", ?NS_AUTH_ERROR}], - [{xmlelement, Condition, [], []}]}]}). - --define(ERR_AUTH_NO_RESOURCE_PROVIDED, - ?AUTH_STANZA_ERROR("no-resource-provided")). --define(ERR_AUTH_BAD_RESOURCE_FORMAT, - ?AUTH_STANZA_ERROR("bad-resource-format")). --define(ERR_AUTH_RESOURCE_CONFLICT, - ?AUTH_STANZA_ERROR("resource-conflict")). +% Auth stanza errors +-define(ERR_AUTH_NO_RESOURCE_PROVIDED(Lang), + ?ERRT_NOT_ACCEPTABLE(Lang, "No resource provided")). +-define(ERR_AUTH_BAD_RESOURCE_FORMAT(Lang), + ?ERRT_NOT_ACCEPTABLE(Lang, "Illegal resource format")). +-define(ERR_AUTH_RESOURCE_CONFLICT(Lang), + ?ERRT_CONFLICT(Lang, "Resource conflict")). -define(STREAM_ERROR(Condition), diff --git a/src/msgs/es.msg b/src/msgs/es.msg index 866917aed..7c04dec6d 100644 --- a/src/msgs/es.msg +++ b/src/msgs/es.msg @@ -2,6 +2,11 @@ % Ejabberd - Spanish - badlop AT jabberes.org +% jlib.hrl +{"No resource provided", ""}. +{"Illegal resource format", ""}. +{"Resource conflict", ""}. + % mod_configure.erl {"DB Tables Configuration at ", "Configuración de tablas de la BD en "}. {"Choose storage type of tables", "Selecciona tipo de almacenamiento de las tablas"}. diff --git a/src/msgs/fr.msg b/src/msgs/fr.msg index aaf5d0825..5d7509394 100644 --- a/src/msgs/fr.msg +++ b/src/msgs/fr.msg @@ -1,5 +1,10 @@ % $Id$ +% jlib.hrl +{"No resource provided", ""}. +{"Illegal resource format", ""}. +{"Resource conflict", ""}. + % mod_configure.erl {"DB Tables Configuration", "Configuration des tables de BD"}. {"Choose storage type of tables", "Choisissez un type de stockage pour les tables"}. diff --git a/src/msgs/nl.msg b/src/msgs/nl.msg index 54bd3ff41..242f140ac 100644 --- a/src/msgs/nl.msg +++ b/src/msgs/nl.msg @@ -1,5 +1,10 @@ % $Id$ +% jlib.hrl +{"No resource provided", ""}. +{"Illegal resource format", ""}. +{"Resource conflict", ""}. + % mod_configure.erl {"DB Tables Configuration at ", "Databasetabellenconfiguratie op "}. {"Choose storage type of tables", "Opslagmethode voor tabellen kiezen"}. diff --git a/src/msgs/ru.msg b/src/msgs/ru.msg index 551048060..6092b44af 100644 --- a/src/msgs/ru.msg +++ b/src/msgs/ru.msg @@ -1,5 +1,10 @@ % $Id$ +% jlib.hrl +{"No resource provided", "Не указан ресурс"}. +{"Illegal resource format", "Неправильный формат ресурса"}. +{"Resource conflict", "Конфликт ресурсов"}. + % mod_configure.erl {"DB Tables Configuration at ", "Конфигурация таблиц БД на "}. {"Choose storage type of tables", "Выберите тип хранения таблиц"}. diff --git a/src/msgs/ua.msg b/src/msgs/ua.msg index 283707184..f4db92cca 100644 --- a/src/msgs/ua.msg +++ b/src/msgs/ua.msg @@ -1,5 +1,10 @@ % $Id$ +% jlib.hrl +{"No resource provided", ""}. +{"Illegal resource format", ""}. +{"Resource conflict", ""}. + % mod_configure.erl {"DB Tables Configuration at ", "Конфігурація таблиць БД на "}. {"Choose storage type of tables", "Оберіть тип збереження таблиць"}. @@ -247,4 +252,4 @@ % Local Variables: % mode: erlang -% End: \ No newline at end of file +% End: diff --git a/src/web/ejabberd_http.erl b/src/web/ejabberd_http.erl index ddd164fc3..ed66e0a92 100644 --- a/src/web/ejabberd_http.erl +++ b/src/web/ejabberd_http.erl @@ -28,7 +28,9 @@ request_content_length, request_lang = "en", use_http_poll = false, - use_web_admin = false + use_web_admin = false, + end_of_request = false, + trail = "" }). @@ -45,20 +47,33 @@ 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 + TLSEnabled = lists:member(tls, Opts), + TLSOpts = lists:filter(fun({certfile, _}) -> true; + (_) -> false + end, Opts), + {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(Socket, [{packet, http}, {recbuf, 8192}]); - ssl -> - ssl:setopts(Socket, [{packet, http}, {recbuf, 8192}]) + inet:setopts(Socket1, [{packet, http}, {recbuf, 8192}]); + _ -> + ok end, UseHTTPPoll = lists:member(http_poll, Opts), UseWebAdmin = lists:member(web_admin, Opts), - io:format("S: ~p~n", [{UseHTTPPoll, UseWebAdmin}]), + ?DEBUG("S: ~p~n", [{UseHTTPPoll, UseWebAdmin}]), + ?INFO_MSG("started: ~p", [{SockMod1, Socket1}]), {ok, proc_lib:spawn_link(ejabberd_http, receive_headers, - [#state{sockmod = SockMod, - socket = Socket, + [#state{sockmod = SockMod1, + socket = Socket1, use_http_poll = UseHTTPPoll, use_web_admin = UseWebAdmin}])}. @@ -71,25 +86,63 @@ receive_headers(State) -> SockMod = State#state.sockmod, Socket = State#state.socket, Data = SockMod:recv(Socket, 0, 300000), - ?DEBUG("recv: ~p~n", [Data]), + 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, Binary} -> + {Request, Trail} = parse_request( + State, + State#state.trail ++ binary_to_list(Binary)), + State1 = State#state{trail = Trail}, + NewState = lists:foldl( + fun(D, S) -> + case S#state.end_of_request of + true -> + S; + _ -> + process_header(S, D) + end + end, State1, Request), + case NewState#state.end_of_request of + true -> + ok; + _ -> + receive_headers(NewState) + end; + _ -> + ok + end + end. + +process_header(State, Data) -> + SockMod = State#state.sockmod, + Socket = State#state.socket, case Data of {ok, {http_request, Method, Path, Version}} -> - receive_headers(State#state{request_method = Method, - request_version = Version, - request_path = Path}); + State#state{request_method = Method, + request_version = Version, + request_path = Path}; {ok, {http_header, _, 'Authorization', _, Auth}} -> - receive_headers(State#state{request_auth = parse_auth(Auth)}); + 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}); + State#state{request_content_length = Len}; _ -> - receive_headers(State) + State end; {ok, {http_header, _, 'Accept-Language', _, Langs}} -> - receive_headers(State#state{request_lang = parse_lang(Langs)}); + State#state{request_lang = parse_lang(Langs)}; {ok, {http_header, _, _, _, _}} -> - receive_headers(State); + State; {ok, http_eoh} -> ?INFO_MSG("(~w) http query: ~w ~s~n", [State#state.socket, @@ -102,25 +155,22 @@ receive_headers(State) -> case SockMod of gen_tcp -> inet:setopts(Socket, [{packet, http}]); - ssl -> - ssl:setopts(Socket, [{packet, http}]) + _ -> + ok end, - receive_headers( - #state{sockmod = SockMod, - socket = Socket, - use_http_poll = State#state.use_http_poll, - use_web_admin = State#state.use_web_admin}); + #state{sockmod = SockMod, + socket = Socket, + use_http_poll = State#state.use_http_poll, + use_web_admin = State#state.use_web_admin}; _ -> - ok + #state{end_of_request = true} end; {error, _Reason} -> - ok; + #state{end_of_request = true}; _ -> - ok + #state{end_of_request = true} end. - - process_request(#state{request_method = 'GET', request_path = {abs_path, Path}, request_auth = Auth, @@ -157,8 +207,6 @@ process_request(#state{request_method = 'GET', q = LQuery, user = User, lang = Lang}, - io:format("~p~n", [{{UseHTTPPoll, UseWebAdmin}, - Request}]), case ejabberd_web:process_get({UseHTTPPoll, UseWebAdmin}, Request) of El when element(1, El) == xmlelement -> @@ -207,8 +255,8 @@ process_request(#state{request_method = 'POST', case SockMod of gen_tcp -> inet:setopts(Socket, [{packet, 0}]); - ssl -> - ssl:setopts(Socket, [{packet, 0}]) + _ -> + ok end, Data = recv_data(State, Len), ?DEBUG("client data: ~p~n", [Data]), @@ -254,11 +302,17 @@ 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]); + case State#state.trail of + [] -> + case (State#state.sockmod):recv(State#state.socket, Len, 300000) of + {ok, Data} -> + recv_data(State, Len - size(Data), [Acc | Data]); + _ -> + "" + end; _ -> - "" + Trail = State#state.trail, + recv_data(State#state{trail = ""}, Len - length(Trail), [Acc | Trail]) end. @@ -526,3 +580,216 @@ parse_urlencoded(undefined, _, _, _) -> []. +% The following code is mostly taken from yaws_ssl.erl + +parse_request(State, Data) -> + case Data of + [] -> + {[], []}; + _ -> + ?DEBUG("GOT ssl data ~p~n", [Data]), + {R, Trail} = case State#state.request_method of + undefined -> + {R1, Trail1} = get_req(Data), + ?DEBUG("Parsed request ~p~n", [R1]), + {[R1], Trail1}; + _ -> + {[], Data} + end, + {H, Trail2} = get_headers(Trail), + {R ++ H, Trail2} + end. + +get_req("\r\n\r\n" ++ _) -> + bad_request; +get_req("\r\n" ++ Data) -> + get_req(Data); +get_req(Data) -> + {FirstLine, Trail} = lists:splitwith(fun not_eol/1, Data), + R = parse_req(FirstLine), + {R, Trail}. + + +not_eol($\r)-> + false; +not_eol($\n) -> + false; +not_eol(_) -> + true. + + +get_word(Line)-> + {Word, T} = lists:splitwith(fun(X)-> X /= $\ end, Line), + {Word, lists:dropwhile(fun(X) -> X == $\ end, T)}. + + +parse_req(Line) -> + {MethodStr, L1} = get_word(Line), + ?DEBUG("Method: ~p~n", [MethodStr]), + case L1 of + [] -> + bad_request; + _ -> + {URI, L2} = get_word(L1), + {VersionStr, L3} = get_word(L2), + ?DEBUG("URI: ~p~nVersion: ~p~nL3: ~p~n", + [URI, VersionStr, L3]), + case L3 of + [] -> + Method = case MethodStr of + "GET" -> 'GET'; + "POST" -> 'POST'; + "HEAD" -> 'HEAD'; + "OPTIONS" -> 'OPTIONS'; + "TRACE" -> 'TRACE'; + "PUT" -> 'PUT'; + "DELETE" -> 'DELETE'; + S -> S + end, + Path = case URI of + "*" -> + % Is this correct? + "*"; + P -> + % FIXME: Handle + % absolute URIs + {abs_path, P} + end, + case VersionStr of + [] -> + {ok, {http_request, Method, Path, {0,9}}}; + "HTTP/1.0" -> + {ok, {http_request, Method, Path, {1,0}}}; + "HTTP/1.1" -> + {ok, {http_request, Method, Path, {1,1}}}; + _ -> + bad_request + end; + _ -> + bad_request + end + end. + + +get_headers(Tail) -> + get_headers([], Tail). + +get_headers(H, Tail) -> + case get_line(Tail) of + {incomplete, Tail2} -> + {H, Tail2}; + {line, Line, Tail2} -> + get_headers(H ++ parse_line(Line), Tail2); + {lastline, Line, Tail2} -> + {H ++ parse_line(Line) ++ [{ok, http_eoh}], Tail2} + end. + + +parse_line("Connection:" ++ Con) -> + [{ok, {http_header, undefined, 'Connection', undefined, strip_spaces(Con)}}]; +parse_line("Host:" ++ Con) -> + [{ok, {http_header, undefined, 'Host', undefined, strip_spaces(Con)}}]; +parse_line("Accept:" ++ Con) -> + [{ok, {http_header, undefined, 'Accept', undefined, strip_spaces(Con)}}]; +parse_line("If-Modified-Since:" ++ Con) -> + [{ok, {http_header, undefined, 'If-Modified-Since', undefined, strip_spaces(Con)}}]; +parse_line("If-Match:" ++ Con) -> + [{ok, {http_header, undefined, 'If-Match', undefined, strip_spaces(Con)}}]; +parse_line("If-None-Match:" ++ Con) -> + [{ok, {http_header, undefined, 'If-None-Match', undefined, strip_spaces(Con)}}]; +parse_line("If-Range:" ++ Con) -> + [{ok, {http_header, undefined, 'If-Range', undefined, strip_spaces(Con)}}]; +parse_line("If-Unmodified-Since:" ++ Con) -> + [{ok, {http_header, undefined, 'If-Unmodified-Since', undefined, strip_spaces(Con)}}]; +parse_line("Range:" ++ Con) -> + [{ok, {http_header, undefined, 'Range', undefined, strip_spaces(Con)}}]; +parse_line("User-Agent:" ++ Con) -> + [{ok, {http_header, undefined, 'User-Agent', undefined, strip_spaces(Con)}}]; +parse_line("Accept-Ranges:" ++ Con) -> + [{ok, {http_header, undefined, 'Accept-Ranges', undefined, strip_spaces(Con)}}]; +parse_line("Authorization:" ++ Con) -> + [{ok, {http_header, undefined, 'Authorization', undefined, strip_spaces(Con)}}]; +parse_line("Keep-Alive:" ++ Con) -> + [{ok, {http_header, undefined, 'Keep-Alive', undefined, strip_spaces(Con)}}]; +parse_line("Referer:" ++ Con) -> + [{ok, {http_header, undefined, 'Referer', undefined, strip_spaces(Con)}}]; +parse_line("Content-type:"++Con) -> + [{ok, {http_header, undefined, 'Content-Type', undefined, strip_spaces(Con)}}]; +parse_line("Content-Type:"++Con) -> + [{ok, {http_header, undefined, 'Content-Type', undefined, strip_spaces(Con)}}]; +parse_line("Content-Length:"++Con) -> + [{ok, {http_header, undefined, 'Content-Length', undefined, strip_spaces(Con)}}]; +parse_line("Content-length:"++Con) -> + [{ok, {http_header, undefined, 'Content-Length', undefined, strip_spaces(Con)}}]; +parse_line("Cookie:"++Con) -> + [{ok, {http_header, undefined, 'Cookie', undefined, strip_spaces(Con)}}]; +parse_line("Accept-Language:"++Con) -> + [{ok, {http_header, undefined, 'Accept-Language', undefined, strip_spaces(Con)}}]; +parse_line("Accept-Encoding:"++Con) -> + [{ok, {http_header, undefined, 'Accept-Encoding', undefined, strip_spaces(Con)}}]; +parse_line(S) -> + case lists:splitwith(fun(C)->C /= $: end, S) of + {Name, [$:|Val]} -> + [{ok, {http_header, undefined, Name, undefined, strip_spaces(Val)}}]; + _ -> + [] + end. + + +is_space($\s) -> + true; +is_space($\r) -> + true; +is_space($\n) -> + true; +is_space($\r) -> + true; +is_space(_) -> + false. + + +strip_spaces(String) -> + strip_spaces(String, both). + +strip_spaces(String, left) -> + drop_spaces(String); +strip_spaces(String, right) -> + lists:reverse(drop_spaces(lists:reverse(String))); +strip_spaces(String, both) -> + strip_spaces(drop_spaces(String), right). + +drop_spaces([]) -> + []; +drop_spaces(YS=[X|XS]) -> + case is_space(X) of + true -> + drop_spaces(XS); + false -> + YS + end. + +is_nb_space(X) -> + lists:member(X, [$\s, $\t]). + + +% ret: {line, Line, Trail} | {lastline, Line, Trail} + +get_line(L) -> + get_line(L, []). +get_line("\r\n\r\n" ++ Tail, Cur) -> + {lastline, lists:reverse(Cur), Tail}; +get_line("\r\n" ++ Tail, Cur) -> + case Tail of + [] -> + {incomplete, lists:reverse(Cur) ++ "\r\n"}; + _ -> + case is_nb_space(hd(Tail)) of + true -> %% multiline ... continue + get_line(Tail, [$\n, $\r | Cur]); + false -> + {line, lists:reverse(Cur), Tail} + end + end; +get_line([H|T], Cur) -> + get_line(T, [H|Cur]). +