diff options
-rw-r--r-- | Makefile.in | 19 | ||||
-rw-r--r-- | README | 2 | ||||
-rw-r--r-- | configure.ac | 9 | ||||
-rw-r--r-- | ejabberd.yml.example | 4 | ||||
-rwxr-xr-x | ejabberdctl.template | 27 | ||||
-rw-r--r-- | include/mod_muc_room.hrl | 2 | ||||
-rw-r--r-- | rebar.config.script | 48 | ||||
-rw-r--r-- | src/ejabberd_admin.erl | 2 | ||||
-rw-r--r-- | src/ejabberd_app.erl | 1 | ||||
-rw-r--r-- | src/ejabberd_c2s.erl | 13 | ||||
-rw-r--r-- | src/ejabberd_http.erl | 122 | ||||
-rw-r--r-- | src/ejabberd_listener.erl | 21 | ||||
-rw-r--r-- | src/ejabberd_receiver.erl | 60 | ||||
-rw-r--r-- | src/ejabberd_s2s_in.erl | 19 | ||||
-rw-r--r-- | src/ejabberd_s2s_out.erl | 12 | ||||
-rw-r--r-- | src/ejabberd_sup.erl | 8 | ||||
-rw-r--r-- | src/jlib.erl | 80 | ||||
-rw-r--r-- | src/mod_client_state.erl | 4 | ||||
-rw-r--r-- | src/mod_http_upload.erl | 544 | ||||
-rw-r--r-- | src/mod_http_upload_quota.erl | 186 | ||||
-rw-r--r-- | src/mod_mam.erl | 2 | ||||
-rw-r--r-- | src/mod_muc.erl | 15 | ||||
-rw-r--r-- | src/mod_muc_room.erl | 136 | ||||
-rw-r--r-- | src/mod_ping.erl | 4 | ||||
-rw-r--r-- | src/mod_vcard.erl | 2 | ||||
-rw-r--r-- | src/mod_vcard_ldap.erl | 4 | ||||
-rw-r--r-- | vars.config.in | 1 |
27 files changed, 740 insertions, 607 deletions
diff --git a/Makefile.in b/Makefile.in index 8dd6bf58..79ac69c4 100644 --- a/Makefile.in +++ b/Makefile.in @@ -112,12 +112,19 @@ spec: $(ERL) -noinput +B -pa ebin -pa deps/*/ebin -eval \ 'case xml_gen:compile("tools/xmpp_codec.spec") of ok -> halt(0); _ -> halt(1) end.' -TO_DEST=$(foreach path,$(1),$(if $(filter deps/%,$(path)),$(patsubst deps/%,$(LIBDIR)/%,$(path)),$(patsubst %,$(LIBDIR)/ejabberd/%,$(path)))) +JOIN_PATHS=$(if $(wordlist 2,1000,$(1)),$(firstword $(1))/$(call JOIN_PATHS,$(wordlist 2,1000,$(1))),$(1)) + +ELIXIR_TO_DEST=$(LIBDIR) $(wordlist 2,2,$(1)) $(wordlist 5,1000,$(1)) +DEPS_TO_DEST=$(LIBDIR) $(wordlist 2,1000,$(1)) +MAIN_TO_DEST=$(LIBDIR) ejabberd $(1) +TO_DEST_SINGLE=$(if $(subst XdepsX,,X$(word 1,$(1))X),$(call MAIN_TO_DEST,$(1)),$(if $(subst XlibX,,X$(word 3,$(1))X),$(call DEPS_TO_DEST,$(1)),$(call ELIXIR_TO_DEST,$(1)))) +TO_DEST=$(foreach path,$(1),$(call JOIN_PATHS,$(call TO_DEST_SINGLE,$(subst /, ,$(path))))) + FILTER_DIRS=$(foreach path,$(1),$(if $(wildcard $(path)/*),,$(path))) FILES_WILDCARD=$(call FILTER_DIRS,$(foreach w,$(1),$(wildcard $(w)))) -DEPS_FILES:=$(call FILES_WILDCARD,deps/*/ebin/*.beam deps/*/ebin/*.app deps/*/priv/* deps/*/priv/lib/* deps/*/priv/bin/* deps/*/include/*.hrl) -DEPS_FILES_FILTERED:=$(filter-out %/epam,$(DEPS_FILES)) +DEPS_FILES:=$(call FILES_WILDCARD,deps/*/ebin/*.beam deps/*/ebin/*.app deps/*/priv/* deps/*/priv/lib/* deps/*/priv/bin/* deps/*/include/*.hrl deps/*/lib/*/ebin/*.beam deps/*/lib/*/ebin/*.app) +DEPS_FILES_FILTERED:=$(filter-out %/epam deps/elixir/ebin/elixir.app,$(DEPS_FILES)) DEPS_DIRS:=$(sort deps/ $(wildcard deps/*) $(dir $(DEPS_FILES))) MAIN_FILES:=$(filter-out %/configure.beam,$(call FILES_WILDCARD,ebin/*.beam ebin/*.app priv/msgs/*.msg priv/lib/* include/*.hrl)) @@ -129,7 +136,7 @@ endef $(foreach file,$(DEPS_FILES_FILTERED) $(MAIN_FILES),$(eval $(call COPY_template,$(file)))) -$(call TO_DEST,$(MAIN_DIRS) $(DEPS_DIRS)): +$(sort $(call TO_DEST,$(MAIN_DIRS) $(DEPS_DIRS))): $(INSTALL) -d $@ $(call TO_DEST,deps/p1_pam/priv/bin/epam): $(LIBDIR)/%: deps/% $(call TO_DEST,deps/p1_pam/priv/bin/) @@ -172,10 +179,6 @@ install: all copy-files -e "s*@installuser@*$(INIT_USER)*" ejabberd.init.template \ > ejabberd.init chmod 755 ejabberd.init - # Install Elixir and Elixir dependancies - -$(INSTALL) -m 644 deps/*/lib/*/ebin/*.app $(BEAMDIR) - -$(INSTALL) -m 644 deps/*/lib/*/ebin/*.beam $(BEAMDIR) - rm -f $(BEAMDIR)/configure.beam # # Binary C programs $(INSTALL) -d $(PBINDIR) @@ -107,7 +107,7 @@ To compile ejabberd you need: - Libexpat 1.95 or higher. - Libyaml 0.1.4 or higher. - Erlang/OTP 17.1 or higher. - - OpenSSL 0.9.8 or higher, for STARTTLS, SASL and SSL encryption. + - OpenSSL 1.0.0 or higher, for STARTTLS, SASL and SSL encryption. - Zlib 1.2.3 or higher, for Stream Compression support (XEP-0138). Optional. - PAM library. Optional. For Pluggable Authentication Modules (PAM). - GNU Iconv 1.8 or higher, for the IRC Transport (mod_irc). Optional. Not diff --git a/configure.ac b/configure.ac index c7e7bcd4..57b9aa74 100644 --- a/configure.ac +++ b/configure.ac @@ -83,14 +83,6 @@ AC_ARG_ENABLE(roster_gateway_workaround, *) AC_MSG_ERROR(bad value ${enableval} for --enable-roster-gateway-workaround) ;; esac],[roster_gateway_workaround=false]) -AC_ARG_ENABLE(transient_supervisors, -[AC_HELP_STRING([--disable-transient-supervisors], [disable Erlang supervision for transient processes (default: no)])], -[case "${enableval}" in - yes) transient_supervisors=true ;; - no) transient_supervisors=false ;; - *) AC_MSG_ERROR(bad value ${enableval} for --enable-transient_supervisors) ;; -esac],[transient_supervisors=true]) - AC_ARG_ENABLE(full_xml, [AC_HELP_STRING([--enable-full-xml], [use XML features in XMPP stream (ex: CDATA) (default: no, requires XML compliant clients)])], [case "${enableval}" in @@ -256,7 +248,6 @@ fi AC_SUBST(hipe) AC_SUBST(roster_gateway_workaround) -AC_SUBST(transient_supervisors) AC_SUBST(full_xml) AC_SUBST(nif) AC_SUBST(db_type) diff --git a/ejabberd.yml.example b/ejabberd.yml.example index b4278eed..e20dc0df 100644 --- a/ejabberd.yml.example +++ b/ejabberd.yml.example @@ -167,6 +167,7 @@ listen: ## - ## port: 4560 ## module: ejabberd_xmlrpc + ## access_commands: {} - port: 5280 module: ejabberd_http @@ -658,7 +659,8 @@ modules: mod_shared_roster: {} mod_stats: {} mod_time: {} - mod_vcard: {} + mod_vcard: + search: false mod_version: {} ## diff --git a/ejabberdctl.template b/ejabberdctl.template index c7d76eff..bd791ced 100755 --- a/ejabberdctl.template +++ b/ejabberdctl.template @@ -228,6 +228,21 @@ iexlive() --erl \"$ERLANG_OPTS\" --erl $ARGS --erl \"$@\"" } +# start server in the foreground +foreground() +{ + check_start + $EXEC_CMD "$ERL \ + $NAME $ERLANG_NODE \ + -noinput \ + -pa $EJABBERD_EBIN_PATH \ + $MNESIA_OPTS \ + $KERNEL_OPTS \ + $EJABBERD_OPTS \ + -s ejabberd \ + $ERLANG_OPTS $ARGS \"$@\"" +} + debugwarning() { if [ "$EJABBERD_BYPASS_WARNINGS" != "true" ] ; then @@ -300,11 +315,12 @@ help() { echo "" echo "Commands to start an ejabberd node:" - echo " start Start an ejabberd node in server mode" - echo " debug Attach an interactive Erlang shell to a running ejabberd node" - echo " iexdebug Attach an interactive Elixir shell to a running ejabberd node" - echo " live Start an ejabberd node in live (interactive) mode" - echo " iexlive Start an ejabberd node in live (interactive) mode, within an Elixir shell" + echo " start Start an ejabberd node in server mode" + echo " debug Attach an interactive Erlang shell to a running ejabberd node" + echo " iexdebug Attach an interactive Elixir shell to a running ejabberd node" + echo " live Start an ejabberd node in live (interactive) mode" + echo " iexlive Start an ejabberd node in live (interactive) mode, within an Elixir shell" + echo " foreground Start an ejabberd node in server mode (attached)" echo "" echo "Optional parameters when starting an ejabberd node:" echo " --config-dir dir Config ejabberd: $ETC_DIR" @@ -458,6 +474,7 @@ case $ARGS in ' iexdebug') iexdebug;; ' live') live;; ' iexlive') iexlive;; + ' foreground') foreground;; ' ping'*) ping ${ARGS# ping};; ' etop') etop;; ' started') wait_for_status 0 30 2;; # wait 30x2s before timeout diff --git a/include/mod_muc_room.hrl b/include/mod_muc_room.hrl index c90a5c02..51e575db 100644 --- a/include/mod_muc_room.hrl +++ b/include/mod_muc_room.hrl @@ -56,6 +56,8 @@ password_protected = false :: boolean(), password = <<"">> :: binary(), anonymous = true :: boolean(), + presence_broadcast = [moderator, participant, visitor] :: + [moderator | participant | visitor], allow_voice_requests = true :: boolean(), voice_request_min_interval = 1800 :: non_neg_integer(), max_users = ?MAX_USERS_DEFAULT :: non_neg_integer() | none, diff --git a/rebar.config.script b/rebar.config.script index 833595d4..cdd2a65c 100644 --- a/rebar.config.script +++ b/rebar.config.script @@ -19,8 +19,6 @@ Cfg = case file:consult("vars.config") of Macros = lists:flatmap( fun({roster_gateway_workaround, true}) -> [{d, 'ROSTER_GATEWAY_WORKAROUND'}]; - ({transient_supervisors, false}) -> - [{d, 'NO_TRANSIENT_SUPERVISORS'}]; ({nif, true}) -> [{d, 'NIF'}]; ({db_type, mssql}) -> @@ -54,17 +52,17 @@ SrcDirs = lists:foldl( Acc end, [], Cfg), -Deps = [{p1_cache_tab, ".*", {git, "https://github.com/processone/cache_tab", "f7ea12b0ba962a3d2f9a406d2954cf7de4e27230"}}, - {p1_tls, ".*", {git, "https://github.com/processone/tls", "e56321afd974e9da33da913cd31beebc8e73e75f"}}, - {p1_stringprep, ".*", {git, "https://github.com/processone/stringprep", "3c640237a3a7831dc39de6a6d329d3a9af25c579"}}, - {p1_xml, ".*", {git, "https://github.com/processone/xml", "1c8b016b0ac7986efb823baf1682a43565449e65"}}, - {esip, ".*", {git, "https://github.com/processone/p1_sip", "d662d3fe7f6288b444ea321d854de0bd6d40e022"}}, - {p1_stun, ".*", {git, "https://github.com/processone/stun", "061bdae484268cbf0457ad4797e74b8516df3ad1"}}, - {p1_yaml, ".*", {git, "https://github.com/processone/p1_yaml", "79f756ba73a235c4d3836ec07b5f7f2b55f49638"}}, - {p1_utils, ".*", {git, "https://github.com/processone/p1_utils", "d7800881e6702723ce58b7646b60c9e4cd25d563"}}, - {jiffy, ".*", {git, "https://github.com/davisp/jiffy", "cfc61a2e952dc3182e0f9b1473467563699992e2"}}, - {oauth2, ".*", {git, "https://github.com/prefiks/oauth2.git", "e6da9912e5d8f658e7e868f41a102d085bdbef59"}}, - {xmlrpc, ".*", {git, "https://github.com/rds13/xmlrpc.git", "42e6e96a0fe7106830274feed915125feb1056f3"}}], +Deps = [{p1_cache_tab, ".*", {git, "https://github.com/processone/cache_tab"}}, + {p1_tls, ".*", {git, "https://github.com/processone/tls"}}, + {p1_stringprep, ".*", {git, "https://github.com/processone/stringprep"}}, + {p1_xml, ".*", {git, "https://github.com/processone/xml"}}, + {esip, ".*", {git, "https://github.com/processone/p1_sip"}}, + {p1_stun, ".*", {git, "https://github.com/processone/stun"}}, + {p1_yaml, ".*", {git, "https://github.com/processone/p1_yaml"}}, + {p1_utils, ".*", {git, "https://github.com/processone/p1_utils"}}, + {jiffy, ".*", {git, "https://github.com/davisp/jiffy"}}, + {oauth2, ".*", {git, "https://github.com/prefiks/oauth2.git"}}, + {xmlrpc, ".*", {git, "https://github.com/rds13/xmlrpc.git"}}], CFLags = proplists:get_value(cflags, Cfg, ""), CPPFLags = proplists:get_value(cppflags, Cfg, ""), @@ -94,30 +92,30 @@ PostHooks = [ConfigureCmd("p1_tls", ""), CfgDeps = lists:flatmap( fun({mysql, true}) -> - [{p1_mysql, ".*", {git, "https://github.com/processone/mysql", "dfa87da95f8fdb92e270741c2a53f796b682f918"}}]; + [{p1_mysql, ".*", {git, "https://github.com/processone/mysql"}}]; ({pgsql, true}) -> - [{p1_pgsql, ".*", {git, "https://github.com/processone/pgsql", "e72c03c60bfcb56bbb5d259342021d9cb3581dac"}}]; + [{p1_pgsql, ".*", {git, "https://github.com/processone/pgsql"}}]; ({sqlite, true}) -> - [{sqlite3, ".*", {git, "https://github.com/alexeyr/erlang-sqlite3", "8350dc603804c503f99c92bfd2eab1dd6885758e"}}]; + [{sqlite3, ".*", {git, "https://github.com/alexeyr/erlang-sqlite3"}}]; ({pam, true}) -> - [{p1_pam, ".*", {git, "https://github.com/processone/epam", "d3ce290b7da75d780a03e86e7a8198a80e9826a6"}}]; + [{p1_pam, ".*", {git, "https://github.com/processone/epam"}}]; ({zlib, true}) -> - [{p1_zlib, ".*", {git, "https://github.com/processone/zlib", "e3d4222b7aae616d7ef2e7e2fa0bbf451516c602"}}]; + [{p1_zlib, ".*", {git, "https://github.com/processone/zlib"}}]; ({riak, true}) -> [{riakc, ".*", {git, "https://github.com/basho/riak-erlang-client", {tag, "1.4.2"}}}]; ({elixir, true}) -> - [{rebar_elixir_plugin, ".*", {git, "https://github.com/yrashk/rebar_elixir_plugin", "7058379b7c7e017555647f6b9cecfd87cd50f884"}}, - {elixir, ".*", {git, "https://github.com/elixir-lang/elixir", "1d9548fd285d243721b7eba71912bde2ffd1f6c3"}}]; + [{rebar_elixir_plugin, ".*", {git, "https://github.com/yrashk/rebar_elixir_plugin"}}, + {elixir, ".*", {git, "https://github.com/elixir-lang/elixir", {branch, "v1.0"}}}]; ({iconv, true}) -> - [{p1_iconv, ".*", {git, "https://github.com/processone/eiconv", "8b7542b1aaf0a851f335e464956956985af6d9a2"}}]; + [{p1_iconv, ".*", {git, "https://github.com/processone/eiconv"}}]; ({lager, true}) -> - [{lager, ".*", {git, "https://github.com/basho/lager", "4d2ec8c701e1fa2d386f92f2b83b23faf8608ac3"}}]; + [{lager, ".*", {git, "https://github.com/basho/lager"}}]; ({lager, false}) -> - [{p1_logger, ".*", {git, "https://github.com/processone/p1_logger", "3e19507fd5606a73694917158767ecb3f5704e3f"}}]; + [{p1_logger, ".*", {git, "https://github.com/processone/p1_logger"}}]; ({tools, true}) -> - [{meck, "0.*", {git, "https://github.com/eproxus/meck", "0845277398b8326f9dddddd9fc3cf73467ba6877"}}]; + [{meck, "0.*", {git, "https://github.com/eproxus/meck"}}]; ({redis, true}) -> - [{eredis, ".*", {git, "https://github.com/wooga/eredis", "bf12ecb30253c84a2331f4f0d93fd68856fcb9f4"}}]; + [{eredis, ".*", {git, "https://github.com/wooga/eredis"}}]; (_) -> [] end, Cfg), diff --git a/src/ejabberd_admin.erl b/src/ejabberd_admin.erl index c51a2754..49042f4d 100644 --- a/src/ejabberd_admin.erl +++ b/src/ejabberd_admin.erl @@ -143,7 +143,7 @@ commands() -> args = [], result = {vhosts, {list, {vhost, string}}}}, #ejabberd_commands{name = reload_config, tags = [server], - desc = "Reload ejabberd configuration file into memory", + desc = "Reload config file in memory (only affects ACL and Access)", module = ?MODULE, function = reload_config, args = [], result = {res, rescode}}, diff --git a/src/ejabberd_app.erl b/src/ejabberd_app.erl index 657785ff..ad51db7e 100644 --- a/src/ejabberd_app.erl +++ b/src/ejabberd_app.erl @@ -43,6 +43,7 @@ start(normal, _Args) -> ejabberd_logger:start(), write_pid_file(), + jlib:start(), start_apps(), ejabberd:check_app(ejabberd), randoms:start(), diff --git a/src/ejabberd_c2s.erl b/src/ejabberd_c2s.erl index 69b46523..57cf5064 100644 --- a/src/ejabberd_c2s.erl +++ b/src/ejabberd_c2s.erl @@ -129,15 +129,6 @@ -endif. -%% Module start with or without supervisor: --ifdef(NO_TRANSIENT_SUPERVISORS). --define(SUPERVISOR_START, ?GEN_FSM:start(ejabberd_c2s, [SockData, Opts], - fsm_limit_opts(Opts) ++ ?FSMOPTS)). --else. --define(SUPERVISOR_START, supervisor:start_child(ejabberd_c2s_sup, - [SockData, Opts])). --endif. - %% This is the timeout to apply between event when starting a new %% session: -define(C2S_OPEN_TIMEOUT, 60000). @@ -201,7 +192,9 @@ %%% API %%%---------------------------------------------------------------------- start(SockData, Opts) -> - ?SUPERVISOR_START. + ?GEN_FSM:start(ejabberd_c2s, + [SockData, Opts], + fsm_limit_opts(Opts) ++ ?FSMOPTS). start_link(SockData, Opts) -> (?GEN_FSM):start_link(ejabberd_c2s, diff --git a/src/ejabberd_http.erl b/src/ejabberd_http.erl index d2649846..98567777 100644 --- a/src/ejabberd_http.erl +++ b/src/ejabberd_http.erl @@ -287,16 +287,18 @@ process_header(State, Data) -> 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 + {State3, Out} = process_request(State2), + send_text(State3, Out), + case State3#state.request_keepalive of true -> #state{sockmod = SockMod, socket = Socket, + trail = State3#state.trail, options = State#state.options, default_host = State#state.default_host, request_handlers = State#state.request_handlers}; _ -> #state{end_of_request = true, + trail = State3#state.trail, options = State#state.options, default_host = State#state.default_host, request_handlers = State#state.request_handlers} @@ -366,20 +368,20 @@ process(Handlers, Request, Socket, SockMod, Trail) -> end. extract_path_query(#state{request_method = Method, - request_path = {abs_path, Path}}) + request_path = {abs_path, Path}} = State) when Method =:= 'GET' orelse Method =:= 'HEAD' orelse Method =:= 'DELETE' orelse Method =:= 'OPTIONS' -> case catch url_decode_q_split(Path) of - {'EXIT', _} -> false; - {NPath, Query} -> - LPath = normalize_path([NPE - || NPE <- str:tokens(path_decode(NPath), <<"/">>)]), - LQuery = case catch parse_urlencoded(Query) of - {'EXIT', _Reason} -> []; - LQ -> LQ - end, - {LPath, LQuery, <<"">>} + {'EXIT', _} -> {State, false}; + {NPath, Query} -> + LPath = normalize_path([NPE + || NPE <- str:tokens(path_decode(NPath), <<"/">>)]), + LQuery = case catch parse_urlencoded(Query) of + {'EXIT', _Reason} -> []; + LQ -> LQ + end, + {State, {LPath, LQuery, <<"">>}} end; extract_path_query(#state{request_method = Method, request_path = {abs_path, Path}, @@ -388,21 +390,21 @@ extract_path_query(#state{request_method = Method, socket = _Socket} = State) when (Method =:= 'POST' orelse Method =:= 'PUT') andalso is_integer(Len) -> - Data = recv_data(State, Len), + {NewState, Data} = recv_data(State, Len), ?DEBUG("client data: ~p~n", [Data]), case catch url_decode_q_split(Path) of - {'EXIT', _} -> false; - {NPath, _Query} -> - LPath = normalize_path([NPE - || NPE <- str:tokens(path_decode(NPath), <<"/">>)]), - LQuery = case catch parse_urlencoded(Data) of - {'EXIT', _Reason} -> []; - LQ -> LQ - end, - {LPath, LQuery, Data} + {'EXIT', _} -> {NewState, false}; + {NPath, _Query} -> + LPath = normalize_path([NPE + || NPE <- str:tokens(path_decode(NPath), <<"/">>)]), + LQuery = case catch parse_urlencoded(Data) of + {'EXIT', _Reason} -> []; + LQ -> LQ + end, + {NewState, {LPath, LQuery, Data}} end; -extract_path_query(_State) -> - false. +extract_path_query(State) -> + {State, false}. process_request(#state{request_method = Method, request_auth = Auth, @@ -417,9 +419,9 @@ process_request(#state{request_method = Method, request_handlers = RequestHandlers, trail = Trail} = State) -> case extract_path_query(State) of - false -> - make_bad_request(State); - {LPath, LQuery, Data} -> + {State2, false} -> + {State2, make_bad_request(State)}; + {State2, {LPath, LQuery, Data}} -> PeerName = case SockMod of gen_tcp -> @@ -445,23 +447,24 @@ process_request(#state{request_method = Method, opts = Options, headers = RequestHeaders, ip = IP}, - case process(RequestHandlers, Request, Socket, SockMod, Trail) of - El when is_record(El, xmlel) -> - make_xhtml_output(State, 200, [], El); - {Status, Headers, El} - when is_record(El, xmlel) -> - make_xhtml_output(State, Status, Headers, El); - Output when is_binary(Output) or is_list(Output) -> - make_text_output(State, 200, [], Output); - {Status, Headers, Output} - when is_binary(Output) or is_list(Output) -> - make_text_output(State, Status, Headers, Output); - {Status, Reason, Headers, Output} - when is_binary(Output) or is_list(Output) -> - make_text_output(State, Status, Reason, Headers, Output); - _ -> - none - end + Res = case process(RequestHandlers, Request, Socket, SockMod, Trail) of + El when is_record(El, xmlel) -> + make_xhtml_output(State, 200, [], El); + {Status, Headers, El} + when is_record(El, xmlel) -> + make_xhtml_output(State, Status, Headers, El); + Output when is_binary(Output) or is_list(Output) -> + make_text_output(State, 200, [], Output); + {Status, Headers, Output} + when is_binary(Output) or is_list(Output) -> + make_text_output(State, Status, Headers, Output); + {Status, Reason, Headers, Output} + when is_binary(Output) or is_list(Output) -> + make_text_output(State, Status, Reason, Headers, Output); + _ -> + none + end, + {State2, Res} end. make_bad_request(State) -> @@ -501,21 +504,24 @@ is_ipchain_trusted(UserIPs, TrustedIPs) -> recv_data(State, Len) -> recv_data(State, Len, <<>>). -recv_data(_State, 0, Acc) -> (iolist_to_binary(Acc)); +recv_data(State, 0, Acc) -> {State, Acc}; +recv_data(#state{trail = Trail} = State, Len, <<>>) when byte_size(Trail) > Len -> + <<Data:Len/binary, Rest/binary>> = Trail, + {State#state{trail = Rest}, Data}; 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>>) + <<>> -> + 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) -> diff --git a/src/ejabberd_listener.erl b/src/ejabberd_listener.erl index 0bea9c97..bebb15c4 100644 --- a/src/ejabberd_listener.erl +++ b/src/ejabberd_listener.erl @@ -225,8 +225,8 @@ listen_tcp(PortIP, Module, SockOpts, Port, IPS) -> %% so the option inet/inet6 is only used when no IP is specified at all. parse_listener_portip(PortIP, Opts) -> {IPOpt, Opts2} = strip_ip_option(Opts), - {IPVOpt, OptsClean} = case lists:member(inet6, Opts2) of - true -> {inet6, Opts2 -- [inet6]}; + {IPVOpt, OptsClean} = case proplists:get_bool(inet6, Opts2) of + true -> {inet6, proplists:delete(inet6, Opts2)}; false -> {inet, Opts2} end, {Port, IPT, IPS, Proto} = @@ -569,11 +569,8 @@ transform_option({{Port, IP, Transport}, Mod, Opts}) -> Opts1 = lists:map( fun({ip, IPT}) when is_tuple(IPT) -> {ip, list_to_binary(inet_parse:ntoa(IP))}; - (tls) -> {tls, true}; (ssl) -> {tls, true}; - (zlib) -> {zlib, true}; - (starttls) -> {starttls, true}; - (starttls_required) -> {starttls_required, true}; + (A) when is_atom(A) -> {A, true}; (Opt) -> Opt end, Opts), Opts2 = lists:foldl( @@ -593,11 +590,11 @@ transform_option({{Port, IP, Transport}, Mod, Opts}) -> IPOpt ++ TransportOpt ++ [{port, Port}, {module, Mod} | Opts2]; transform_option({{Port, Transport}, Mod, Opts}) when ?IS_TRANSPORT(Transport) -> - transform_option({{Port, {0,0,0,0}, Transport}, Mod, Opts}); + transform_option({{Port, all_zero_ip(Opts), Transport}, Mod, Opts}); transform_option({{Port, IP}, Mod, Opts}) -> transform_option({{Port, IP, tcp}, Mod, Opts}); transform_option({Port, Mod, Opts}) -> - transform_option({{Port, {0,0,0,0}, tcp}, Mod, Opts}); + transform_option({{Port, all_zero_ip(Opts), tcp}, Mod, Opts}); transform_option(Opt) -> Opt. @@ -633,7 +630,7 @@ validate_cfg(L) -> {Port, prepare_mod(Mod), Opts}; (Opt, {Port, Mod, Opts}) -> {Port, Mod, [Opt|Opts]} - end, {{5222, {0,0,0,0}, tcp}, ejabberd_c2s, []}, LOpts) + end, {{5222, all_zero_ip(LOpts), tcp}, ejabberd_c2s, []}, LOpts) end, L). prepare_ip({A, B, C, D} = IP) @@ -656,5 +653,11 @@ prepare_mod(sip) -> prepare_mod(Mod) when is_atom(Mod) -> Mod. +all_zero_ip(Opts) -> + case proplists:get_bool(inet6, Opts) of + true -> {0,0,0,0,0,0,0,0}; + false -> {0,0,0,0} + end. + opt_type(listen) -> fun validate_cfg/1; opt_type(_) -> [listen]. diff --git a/src/ejabberd_receiver.erl b/src/ejabberd_receiver.erl index cd7c1d31..71ae8e40 100644 --- a/src/ejabberd_receiver.erl +++ b/src/ejabberd_receiver.erl @@ -127,20 +127,10 @@ init([Socket, SockMod, Shaper, MaxStanzaSize]) -> shaper_state = ShaperState, max_stanza_size = MaxStanzaSize, timeout = Timeout}}. -handle_call({starttls, TLSSocket}, _From, - #state{xml_stream_state = XMLStreamState, - c2s_pid = C2SPid, - max_stanza_size = MaxStanzaSize} = State) -> - close_stream(XMLStreamState), - NewXMLStreamState = case C2SPid of - undefined -> - XMLStreamState; - _ -> - xml_stream:new(C2SPid, MaxStanzaSize) - end, - NewState = State#state{socket = TLSSocket, - sock_mod = p1_tls, - xml_stream_state = NewXMLStreamState}, +handle_call({starttls, TLSSocket}, _From, State) -> + State1 = reset_parser(State), + NewState = State1#state{socket = TLSSocket, + sock_mod = p1_tls}, case p1_tls:recv_data(TLSSocket, <<"">>) of {ok, TLSData} -> {reply, ok, @@ -149,20 +139,16 @@ handle_call({starttls, TLSSocket}, _From, {stop, normal, ok, NewState} end; handle_call({compress, Data}, _From, - #state{xml_stream_state = XMLStreamState, - c2s_pid = C2SPid, socket = Socket, sock_mod = SockMod, - max_stanza_size = MaxStanzaSize} = + #state{socket = Socket, sock_mod = SockMod} = State) -> {ok, ZlibSocket} = ezlib:enable_zlib(SockMod, Socket), if Data /= undefined -> do_send(State, Data); true -> ok end, - close_stream(XMLStreamState), - NewXMLStreamState = xml_stream:new(C2SPid, MaxStanzaSize), - NewState = State#state{socket = ZlibSocket, - sock_mod = ezlib, - xml_stream_state = NewXMLStreamState}, + State1 = reset_parser(State), + NewState = State1#state{socket = ZlibSocket, + sock_mod = ezlib}, case ezlib:recv_data(ZlibSocket, <<"">>) of {ok, ZlibData} -> {reply, {ok, ZlibSocket}, @@ -170,16 +156,10 @@ handle_call({compress, Data}, _From, {error, _Reason} -> {stop, normal, ok, NewState} end; -handle_call(reset_stream, _From, - #state{xml_stream_state = XMLStreamState, - c2s_pid = C2SPid, max_stanza_size = MaxStanzaSize} = - State) -> - close_stream(XMLStreamState), - NewXMLStreamState = xml_stream:new(C2SPid, MaxStanzaSize), +handle_call(reset_stream, _From, State) -> + NewState = reset_parser(State), Reply = ok, - {reply, Reply, - State#state{xml_stream_state = NewXMLStreamState}, - ?HIBERNATE_TIMEOUT}; + {reply, Reply, NewState, ?HIBERNATE_TIMEOUT}; handle_call({become_controller, C2SPid}, _From, State) -> XMLStreamState = xml_stream:new(C2SPid, State#state.max_stanza_size), NewState = State#state{c2s_pid = C2SPid, @@ -332,6 +312,24 @@ close_stream(undefined) -> ok; close_stream(XMLStreamState) -> xml_stream:close(XMLStreamState). +reset_parser(#state{xml_stream_state = undefined} = State) -> + State; +reset_parser(#state{c2s_pid = C2SPid, + max_stanza_size = MaxStanzaSize, + xml_stream_state = XMLStreamState} + = State) -> + NewStreamState = try xml_stream:reset(XMLStreamState) + catch error:_ -> + close_stream(XMLStreamState), + case C2SPid of + undefined -> + undefined; + _ -> + xml_stream:new(C2SPid, MaxStanzaSize) + end + end, + State#state{xml_stream_state = NewStreamState}. + do_send(State, Data) -> (State#state.sock_mod):send(State#state.socket, Data). diff --git a/src/ejabberd_s2s_in.erl b/src/ejabberd_s2s_in.erl index d840c315..e655397b 100644 --- a/src/ejabberd_s2s_in.erl +++ b/src/ejabberd_s2s_in.erl @@ -74,21 +74,6 @@ -endif. -%% Module start with or without supervisor: --ifdef(NO_TRANSIENT_SUPERVISORS). - --define(SUPERVISOR_START, - p1_fsm:start(ejabberd_s2s_in, [SockData, Opts], - ?FSMOPTS ++ fsm_limit_opts(Opts))). - --else. - --define(SUPERVISOR_START, - supervisor:start_child(ejabberd_s2s_in_sup, - [SockData, Opts])). - --endif. - -define(STREAM_HEADER(Version), <<"<?xml version='1.0'?><stream:stream " "xmlns:stream='http://etherx.jabber.org/stream" @@ -111,7 +96,9 @@ -define(INVALID_XML_ERR, xml:element_to_binary(?SERR_XML_NOT_WELL_FORMED)). -start(SockData, Opts) -> ?SUPERVISOR_START. +start(SockData, Opts) -> + supervisor:start_child(ejabberd_s2s_in_sup, + [SockData, Opts]). start_link(SockData, Opts) -> p1_fsm:start_link(ejabberd_s2s_in, [SockData, Opts], diff --git a/src/ejabberd_s2s_out.erl b/src/ejabberd_s2s_out.erl index bc815166..73aa10ff 100644 --- a/src/ejabberd_s2s_out.erl +++ b/src/ejabberd_s2s_out.erl @@ -86,15 +86,6 @@ -endif. -%% Module start with or without supervisor: --ifdef(NO_TRANSIENT_SUPERVISORS). --define(SUPERVISOR_START, p1_fsm:start(ejabberd_s2s_out, [From, Host, Type], - fsm_limit_opts() ++ ?FSMOPTS)). --else. --define(SUPERVISOR_START, supervisor:start_child(ejabberd_s2s_out_sup, - [From, Host, Type])). --endif. - -define(FSMTIMEOUT, 30000). %% We do not block on send anymore. @@ -127,7 +118,8 @@ %%% API %%%---------------------------------------------------------------------- start(From, Host, Type) -> - ?SUPERVISOR_START. + supervisor:start_child(ejabberd_s2s_out_sup, + [From, Host, Type]). start_link(From, Host, Type) -> p1_fsm:start_link(ejabberd_s2s_out, [From, Host, Type], diff --git a/src/ejabberd_sup.erl b/src/ejabberd_sup.erl index da25af2c..e8d3ce83 100644 --- a/src/ejabberd_sup.erl +++ b/src/ejabberd_sup.erl @@ -105,13 +105,6 @@ init([]) -> infinity, supervisor, [ejabberd_tmp_sup]}, - C2SSupervisor = - {ejabberd_c2s_sup, - {ejabberd_tmp_sup, start_link, [ejabberd_c2s_sup, ejabberd_c2s]}, - permanent, - infinity, - supervisor, - [ejabberd_tmp_sup]}, S2SInSupervisor = {ejabberd_s2s_in_sup, {ejabberd_tmp_sup, start_link, @@ -170,7 +163,6 @@ init([]) -> Local, Captcha, ReceiverSupervisor, - C2SSupervisor, S2SInSupervisor, S2SOutSupervisor, ServiceSupervisor, diff --git a/src/jlib.erl b/src/jlib.erl index cc0f826a..52dd9b2c 100644 --- a/src/jlib.erl +++ b/src/jlib.erl @@ -58,11 +58,19 @@ atom_to_binary/1, binary_to_atom/1, tuple_to_binary/1, l2i/1, i2l/1, i2l/2, queue_drop_while/2]). +-export([start/0]). + -include("ejabberd.hrl"). -include("jlib.hrl"). -export_type([jid/0]). +start() -> + SplitPattern = binary:compile_pattern([<<"@">>, <<"/">>]), + catch ets:new(jlib, [named_table, protected, set, {keypos, 1}]), + ets:insert(jlib, {string_to_jid_pattern, SplitPattern}), + ok. + %send_iq(From, To, ID, SubTags) -> % ok. @@ -215,7 +223,7 @@ make_jid({User, Server, Resource}) -> make_jid(User, Server, Resource). %% This is the reverse of make_jid/1 --spec split_jid(jid()) -> {binary(), binary(), binary()} | error. +-spec split_jid(jid()) -> {binary(), binary(), binary()} | error. split_jid(#jid{user = U, server = S, resource = R}) -> {U, S, R}; split_jid(_) -> @@ -224,36 +232,44 @@ split_jid(_) -> -spec string_to_jid(binary()) -> jid() | error. string_to_jid(S) -> - string_to_jid1(binary_to_list(S), ""). - -string_to_jid1([$@ | _J], "") -> error; -string_to_jid1([$@ | J], N) -> - string_to_jid2(J, lists:reverse(N), ""); -string_to_jid1([$/ | _J], "") -> error; -string_to_jid1([$/ | J], N) -> - string_to_jid3(J, "", lists:reverse(N), ""); -string_to_jid1([C | J], N) -> - string_to_jid1(J, [C | N]); -string_to_jid1([], "") -> error; -string_to_jid1([], N) -> - make_jid(<<"">>, list_to_binary(lists:reverse(N)), <<"">>). - -%% Only one "@" is admitted per JID -string_to_jid2([$@ | _J], _N, _S) -> error; -string_to_jid2([$/ | _J], _N, "") -> error; -string_to_jid2([$/ | J], N, S) -> - string_to_jid3(J, N, lists:reverse(S), ""); -string_to_jid2([C | J], N, S) -> - string_to_jid2(J, N, [C | S]); -string_to_jid2([], _N, "") -> error; -string_to_jid2([], N, S) -> - make_jid(list_to_binary(N), list_to_binary(lists:reverse(S)), <<"">>). - -string_to_jid3([C | J], N, S, R) -> - string_to_jid3(J, N, S, [C | R]); -string_to_jid3([], N, S, R) -> - make_jid(list_to_binary(N), list_to_binary(S), - list_to_binary(lists:reverse(R))). + SplitPattern = ets:lookup_element(jlib, string_to_jid_pattern, 2), + Size = size(S), + End = Size-1, + case binary:match(S, SplitPattern) of + {0, _} -> + error; + {End, _} -> + error; + {Pos1, _} -> + case binary:at(S, Pos1) of + $/ -> + make_jid(<<>>, + binary:part(S, 0, Pos1), + binary:part(S, Pos1+1, Size-Pos1-1)); + _ -> + Pos1N = Pos1+1, + case binary:match(S, SplitPattern, [{scope, {Pos1+1, Size-Pos1-1}}]) of + {End, _} -> + error; + {Pos1N, _} -> + error; + {Pos2, _} -> + case binary:at(S, Pos2) of + $/ -> + make_jid(binary:part(S, 0, Pos1), + binary:part(S, Pos1+1, Pos2-Pos1-1), + binary:part(S, Pos2+1, Size-Pos2-1)); + _ -> error + end; + _ -> + make_jid(binary:part(S, 0, Pos1), + binary:part(S, Pos1+1, Size-Pos1-1), + <<>>) + end + end; + _ -> + make_jid(<<>>, S, <<>>) + end. -spec jid_to_string(jid() | ljid()) -> binary(). @@ -856,7 +872,7 @@ decode_base64(S) -> decode_base64_bin(S, <<>>) end. -take_without_spaces(Bin, Count) -> +take_without_spaces(Bin, Count) -> take_without_spaces(Bin, Count, <<>>). take_without_spaces(Bin, 0, Acc) -> diff --git a/src/mod_client_state.erl b/src/mod_client_state.erl index eb40db10..4934db3b 100644 --- a/src/mod_client_state.erl +++ b/src/mod_client_state.erl @@ -1,8 +1,8 @@ %%%---------------------------------------------------------------------- %%% File : mod_client_state.erl -%%% Author : Holger Weiss +%%% Author : Holger Weiss <holger@zedat.fu-berlin.de> %%% Purpose : Filter stanzas sent to inactive clients (XEP-0352) -%%% Created : 11 Sep 2014 by Holger Weiss +%%% Created : 11 Sep 2014 by Holger Weiss <holger@zedat.fu-berlin.de> %%% %%% %%% ejabberd, Copyright (C) 2014-2015 ProcessOne diff --git a/src/mod_http_upload.erl b/src/mod_http_upload.erl index 35fd6ca3..87707790 100644 --- a/src/mod_http_upload.erl +++ b/src/mod_http_upload.erl @@ -1,13 +1,33 @@ -%%%------------------------------------------------------------------- +%%%---------------------------------------------------------------------- %%% File : mod_http_upload.erl %%% Author : Holger Weiss <holger@zedat.fu-berlin.de> %%% Purpose : HTTP File Upload (XEP-0363) %%% Created : 20 Aug 2015 by Holger Weiss <holger@zedat.fu-berlin.de> -%%%------------------------------------------------------------------- +%%% +%%% +%%% ejabberd, Copyright (C) 2015 ProcessOne +%%% +%%% This program is free software; you can redistribute it and/or +%%% modify it under the terms of the GNU General Public License as +%%% published by the Free Software Foundation; either version 2 of the +%%% License, or (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%%% General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License along +%%% with this program; if not, write to the Free Software Foundation, Inc., +%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +%%% +%%%---------------------------------------------------------------------- -module(mod_http_upload). -author('holger@zedat.fu-berlin.de'). +-protocol({xep, 363, '0.1'}). + -define(GEN_SERVER, gen_server). -define(SERVICE_REQUEST_TIMEOUT, 5000). % 5 seconds. -define(SLOT_TIMEOUT, 18000000). % 5 hours. @@ -92,12 +112,12 @@ slots = dict:new() :: term()}). % dict:dict() requires Erlang 17. -record(media_info, - {type :: string(), + {type :: binary(), height :: integer(), width :: integer()}). -type state() :: #state{}. --type slot() :: [binary()]. +-type slot() :: [binary(), ...]. -type media_info() :: #media_info{}. %%-------------------------------------------------------------------- @@ -116,12 +136,13 @@ start(ServerHost, Opts) -> case gen_mod:get_opt(rm_on_unregister, Opts, fun(B) when is_boolean(B) -> B end, true) of - true -> - ejabberd_hooks:add(remove_user, ServerHost, ?MODULE, - remove_user, 50), - ejabberd_hooks:add(anonymous_purge_hook, ServerHost, ?MODULE, - remove_user, 50); - false -> ok + true -> + ejabberd_hooks:add(remove_user, ServerHost, ?MODULE, + remove_user, 50), + ejabberd_hooks:add(anonymous_purge_hook, ServerHost, ?MODULE, + remove_user, 50); + false -> + ok end, Proc = get_proc_name(ServerHost, ?PROCNAME), Spec = {Proc, @@ -138,16 +159,17 @@ stop(ServerHost) -> case gen_mod:get_module_opt(ServerHost, ?MODULE, rm_on_unregister, fun(B) when is_boolean(B) -> B end, true) of - true -> - ejabberd_hooks:delete(remove_user, ServerHost, ?MODULE, - remove_user, 50), - ejabberd_hooks:delete(anonymous_purge_hook, ServerHost, ?MODULE, - remove_user, 50); - false -> ok + true -> + ejabberd_hooks:delete(remove_user, ServerHost, ?MODULE, + remove_user, 50), + ejabberd_hooks:delete(anonymous_purge_hook, ServerHost, ?MODULE, + remove_user, 50); + false -> + ok end, Proc = get_proc_name(ServerHost, ?PROCNAME), - ok = supervisor:terminate_child(ejabberd_sup, Proc), - ok = supervisor:delete_child(ejabberd_sup, Proc). + supervisor:terminate_child(ejabberd_sup, Proc), + supervisor:delete_child(ejabberd_sup, Proc). -spec mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()]. @@ -253,22 +275,34 @@ init({ServerHost, Opts}) -> fun(B) when is_boolean(B) -> B end, true), case ServiceURL of - undefined -> - ok; - <<"http://", _/binary>> -> - application:start(inets); - <<"https://", _/binary>> -> - application:start(inets), - application:start(crypto), - application:start(asn1), - application:start(public_key), - application:start(ssl) + undefined -> + ok; + <<"http://", _/binary>> -> + application:start(inets); + <<"https://", _/binary>> -> + application:start(inets), + application:start(crypto), + application:start(asn1), + application:start(public_key), + application:start(ssl) end, case DirMode of - undefined -> - ok; - Mode -> - file:change_mode(DocRoot, Mode) + undefined -> + ok; + Mode -> + file:change_mode(DocRoot, Mode) + end, + case Thumbnail of + true -> + case string:str(os:cmd("identify"), "Magick") of + 0 -> + ?ERROR_MSG("Cannot find 'identify' command, please install " + "ImageMagick or disable thumbnail creation", []); + _ -> + ok + end; + false -> + ok end, ejabberd_router:register_route(Host), {ok, #state{server_host = ServerHost, host = Host, name = Name, @@ -293,14 +327,14 @@ handle_call({use_slot, Slot}, _From, #state{file_mode = FileMode, thumbnail = Thumbnail, docroot = DocRoot} = State) -> case get_slot(Slot, State) of - {ok, {Size, Timer}} -> - timer:cancel(Timer), - NewState = del_slot(Slot, State), - Path = str:join([DocRoot | Slot], <<$/>>), - {reply, {ok, Size, Path, FileMode, DirMode, GetPrefix, Thumbnail}, - NewState}; - error -> - {reply, {error, <<"Invalid slot">>}, State} + {ok, {Size, Timer}} -> + timer:cancel(Timer), + NewState = del_slot(Slot, State), + Path = str:join([DocRoot | Slot], <<$/>>), + {reply, {ok, Size, Path, FileMode, DirMode, GetPrefix, Thumbnail}, + NewState}; + error -> + {reply, {error, <<"Invalid slot">>}, State} end; handle_call(get_docroot, _From, #state{docroot = DocRoot} = State) -> {reply, {ok, DocRoot}, State}; @@ -319,12 +353,12 @@ handle_cast(Request, State) -> handle_info({route, From, To, #xmlel{name = <<"iq">>} = Stanza}, State) -> Request = jlib:iq_query_info(Stanza), {Reply, NewState} = case process_iq(From, Request, State) of - R when is_record(R, iq) -> - {R, State}; - {R, S} -> - {R, S}; - not_request -> - {none, State} + R when is_record(R, iq) -> + {R, State}; + {R, S} -> + {R, S}; + not_request -> + {none, State} end, if Reply /= none -> ejabberd_router:route(To, From, jlib:iq_to_xml(Reply)); @@ -359,82 +393,95 @@ code_change(_OldVsn, #state{server_host = ServerHost} = State, _Extra) -> -spec process([binary()], #request{}) -> {pos_integer(), [{binary(), binary()}], binary()}. -process(LocalPath, #request{method = 'PUT', host = Host, ip = IP, - data = Data}) -> +process([_UserDir, _RandDir, _FileName] = Slot, + #request{method = 'PUT', host = Host, ip = IP, data = Data}) -> Proc = gen_mod:get_module_proc(Host, ?PROCNAME), - case catch gen_server:call(Proc, {use_slot, LocalPath}) of - {ok, Size, Path, FileMode, DirMode, GetPrefix, Thumbnail} - when byte_size(Data) == Size -> - ?DEBUG("Storing file from ~s for ~s: ~s", - [?ADDR_TO_STR(IP), Host, Path]), - case store_file(Path, Data, FileMode, DirMode, - GetPrefix, LocalPath, Thumbnail) of - ok -> - http_response(Host, 201); - {ok, Headers, OutData} -> - http_response(Host, 201, Headers, OutData); - {error, Error} -> - ?ERROR_MSG("Cannot store file ~s from ~s for ~s: ~p", - [Path, ?ADDR_TO_STR(IP), Host, Error]), - http_response(Host, 500) - end; - {ok, Size, Path} -> - ?INFO_MSG("Rejecting file ~s from ~s for ~s: Size is ~B, not ~B", - [Path, ?ADDR_TO_STR(IP), Host, byte_size(Data), Size]), - http_response(Host, 413); - {error, Error} -> - ?INFO_MSG("Rejecting file from ~s for ~s: ~p", - [?ADDR_TO_STR(IP), Host, Error]), - http_response(Host, 403); - Error -> - ?ERROR_MSG("Cannot handle PUT request from ~s for ~s: ~p", - [?ADDR_TO_STR(IP), Host, Error]), - http_response(Host, 500) + case catch gen_server:call(Proc, {use_slot, Slot}) of + {ok, Size, Path, FileMode, DirMode, GetPrefix, Thumbnail} + when byte_size(Data) == Size -> + ?DEBUG("Storing file from ~s for ~s: ~s", + [?ADDR_TO_STR(IP), Host, Path]), + case store_file(Path, Data, FileMode, DirMode, + GetPrefix, Slot, Thumbnail) of + ok -> + http_response(Host, 201); + {ok, Headers, OutData} -> + http_response(Host, 201, Headers, OutData); + {error, Error} -> + ?ERROR_MSG("Cannot store file ~s from ~s for ~s: ~p", + [Path, ?ADDR_TO_STR(IP), Host, ?FORMAT(Error)]), + http_response(Host, 500) + end; + {ok, Size, Path} -> + ?INFO_MSG("Rejecting file ~s from ~s for ~s: Size is ~B, not ~B", + [Path, ?ADDR_TO_STR(IP), Host, byte_size(Data), Size]), + http_response(Host, 413); + {error, Error} -> + ?INFO_MSG("Rejecting file from ~s for ~s: ~p", + [?ADDR_TO_STR(IP), Host, Error]), + http_response(Host, 403); + Error -> + ?ERROR_MSG("Cannot handle PUT request from ~s for ~s: ~p", + [?ADDR_TO_STR(IP), Host, Error]), + http_response(Host, 500) end; -process(LocalPath, #request{method = Method, host = Host, ip = IP}) +process([_UserDir, _RandDir, FileName] = Slot, + #request{method = Method, host = Host, ip = IP}) when Method == 'GET'; Method == 'HEAD' -> Proc = gen_mod:get_module_proc(Host, ?PROCNAME), case catch gen_server:call(Proc, get_docroot) of - {ok, DocRoot} -> - Path = str:join([DocRoot | LocalPath], <<$/>>), - case file:read_file(Path) of - {ok, Data} -> - ?INFO_MSG("Serving ~s to ~s", [Path, ?ADDR_TO_STR(IP)]), - FileName = lists:last(LocalPath), - ContentType = guess_content_type(FileName), - Headers1 = case ContentType of - <<"image/", _SubType/binary>> -> []; - <<"text/", _SubType/binary>> -> []; - _ -> - [{<<"Content-Disposition">>, - <<"attachment; filename=", - $", FileName/binary, $">>}] - end, - Headers2 = [{<<"Content-Type">>, ContentType} | Headers1], - http_response(Host, 200, Headers2, Data); - {error, eacces} -> - ?INFO_MSG("Cannot serve ~s to ~s: Permission denied", - [Path, ?ADDR_TO_STR(IP)]), - http_response(Host, 403); - {error, enoent} -> - ?INFO_MSG("Cannot serve ~s to ~s: No such file or directory", - [Path, ?ADDR_TO_STR(IP)]), - http_response(Host, 404); - {error, eisdir} -> - ?INFO_MSG("Cannot serve ~s to ~s: Is a directory", - [Path, ?ADDR_TO_STR(IP)]), - http_response(Host, 404); - {error, Error} -> - ?INFO_MSG("Cannot serve ~s to ~s: ~s", - [Path, ?ADDR_TO_STR(IP), ?FORMAT(Error)]), - http_response(Host, 500) - end; - Error -> - ?ERROR_MSG("Cannot handle ~s request from ~s for ~s: ~p", - [Method, ?ADDR_TO_STR(IP), Host, Error]), - http_response(Host, 500) + {ok, DocRoot} -> + Path = str:join([DocRoot | Slot], <<$/>>), + case file:read_file(Path) of + {ok, Data} -> + ?INFO_MSG("Serving ~s to ~s", [Path, ?ADDR_TO_STR(IP)]), + ContentType = guess_content_type(FileName), + Headers1 = case ContentType of + <<"image/", _SubType/binary>> -> []; + <<"text/", _SubType/binary>> -> []; + _ -> + [{<<"Content-Disposition">>, + <<"attachment; filename=", + $", FileName/binary, $">>}] + end, + Headers2 = [{<<"Content-Type">>, ContentType} | Headers1], + http_response(Host, 200, Headers2, Data); + {error, eacces} -> + ?INFO_MSG("Cannot serve ~s to ~s: Permission denied", + [Path, ?ADDR_TO_STR(IP)]), + http_response(Host, 403); + {error, enoent} -> + ?INFO_MSG("Cannot serve ~s to ~s: No such file", + [Path, ?ADDR_TO_STR(IP)]), + http_response(Host, 404); + {error, eisdir} -> + ?INFO_MSG("Cannot serve ~s to ~s: Is a directory", + [Path, ?ADDR_TO_STR(IP)]), + http_response(Host, 404); + {error, Error} -> + ?INFO_MSG("Cannot serve ~s to ~s: ~s", + [Path, ?ADDR_TO_STR(IP), ?FORMAT(Error)]), + http_response(Host, 500) + end; + Error -> + ?ERROR_MSG("Cannot handle ~s request from ~s for ~s: ~p", + [Method, ?ADDR_TO_STR(IP), Host, Error]), + http_response(Host, 500) end; +process(LocalPath, #request{method = 'PUT', host = Host, ip = IP}) + when length(LocalPath) > 3 -> + ?INFO_MSG("Rejecting PUT request from ~s for ~s: Too many path components", + [?ADDR_TO_STR(IP), Host]), + ?INFO_MSG("Check whether 'request_handlers' path matches 'put_url'", []), + http_response(Host, 404); +process(_LocalPath, #request{method = Method, host = Host, ip = IP}) + when Method == 'PUT'; + Method == 'GET'; + Method == 'HEAD' -> + ?DEBUG("Rejecting ~s request from ~s for ~s: Too few/many path components", + [Method, ?ADDR_TO_STR(IP), Host]), + http_response(Host, 404); process(_LocalPath, #request{method = 'OPTIONS', host = Host, ip = IP}) -> ?DEBUG("Responding to OPTIONS request from ~s for ~s", [?ADDR_TO_STR(IP), Host]), @@ -493,31 +540,33 @@ process_iq(From, when XMLNS == ?NS_HTTP_UPLOAD; XMLNS == ?NS_HTTP_UPLOAD_OLD -> case acl:match_rule(ServerHost, Access, From) of - allow -> - case parse_request(SubEl, Lang) of - {ok, File, Size, ContentType} -> - case create_slot(State, From, File, Size, ContentType, Lang) of - {ok, Slot} -> - {ok, Timer} = timer:send_after(?SLOT_TIMEOUT, - {slot_timed_out, Slot}), - NewState = add_slot(Slot, Size, Timer, State), - SlotEl = slot_el(Slot, State, XMLNS), - {IQ#iq{type = result, sub_el = [SlotEl]}, NewState}; - {ok, PutURL, GetURL} -> - SlotEl = slot_el(PutURL, GetURL, XMLNS), - IQ#iq{type = result, sub_el = [SlotEl]}; - {error, Error} -> - IQ#iq{type = error, sub_el = [SubEl, Error]} - end; - {error, Error} -> - ?DEBUG("Cannot parse request from ~s", - [jlib:jid_to_string(From)]), - IQ#iq{type = error, sub_el = [SubEl, Error]} - end; - deny -> - ?DEBUG("Denying HTTP upload slot request from ~s", - [jlib:jid_to_string(From)]), - IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]} + allow -> + case parse_request(SubEl, Lang) of + {ok, File, Size, ContentType} -> + case create_slot(State, From, File, Size, ContentType, + Lang) of + {ok, Slot} -> + {ok, Timer} = timer:send_after(?SLOT_TIMEOUT, + {slot_timed_out, + Slot}), + NewState = add_slot(Slot, Size, Timer, State), + SlotEl = slot_el(Slot, State, XMLNS), + {IQ#iq{type = result, sub_el = [SlotEl]}, NewState}; + {ok, PutURL, GetURL} -> + SlotEl = slot_el(PutURL, GetURL, XMLNS), + IQ#iq{type = result, sub_el = [SlotEl]}; + {error, Error} -> + IQ#iq{type = error, sub_el = [SubEl, Error]} + end; + {error, Error} -> + ?DEBUG("Cannot parse request from ~s", + [jlib:jid_to_string(From)]), + IQ#iq{type = error, sub_el = [SubEl, Error]} + end; + deny -> + ?DEBUG("Denying HTTP upload slot request from ~s", + [jlib:jid_to_string(From)]), + IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]} end; process_iq(_From, #iq{sub_el = SubEl} = IQ, _State) -> IQ#iq{type = error, sub_el = [SubEl, ?ERR_NOT_ALLOWED]}; @@ -531,25 +580,25 @@ process_iq(_From, invalid, _State) -> parse_request(#xmlel{name = <<"request">>, attrs = Attrs} = Request, Lang) -> case xml:get_attr(<<"xmlns">>, Attrs) of - {value, XMLNS} when XMLNS == ?NS_HTTP_UPLOAD; - XMLNS == ?NS_HTTP_UPLOAD_OLD -> - case {xml:get_subtag_cdata(Request, <<"filename">>), - xml:get_subtag_cdata(Request, <<"size">>), - xml:get_subtag_cdata(Request, <<"content-type">>)} of - {File, SizeStr, ContentType} when byte_size(File) > 0 -> - case catch jlib:binary_to_integer(SizeStr) of - Size when is_integer(Size), Size > 0 -> - {ok, File, Size, yield_content_type(ContentType)}; - _ -> - Text = <<"Please specify file size.">>, - {error, ?ERRT_BAD_REQUEST(Lang, Text)} - end; - _ -> - Text = <<"Please specify file name.">>, - {error, ?ERRT_BAD_REQUEST(Lang, Text)} - end; - _ -> - {error, ?ERR_BAD_REQUEST} + {value, XMLNS} when XMLNS == ?NS_HTTP_UPLOAD; + XMLNS == ?NS_HTTP_UPLOAD_OLD -> + case {xml:get_subtag_cdata(Request, <<"filename">>), + xml:get_subtag_cdata(Request, <<"size">>), + xml:get_subtag_cdata(Request, <<"content-type">>)} of + {File, SizeStr, ContentType} when byte_size(File) > 0 -> + case catch jlib:binary_to_integer(SizeStr) of + Size when is_integer(Size), Size > 0 -> + {ok, File, Size, yield_content_type(ContentType)}; + _ -> + Text = <<"Please specify file size.">>, + {error, ?ERRT_BAD_REQUEST(Lang, Text)} + end; + _ -> + Text = <<"Please specify file name.">>, + {error, ?ERRT_BAD_REQUEST(Lang, Text)} + end; + _ -> + {error, ?ERR_BAD_REQUEST} end; parse_request(_El, _Lang) -> {error, ?ERR_BAD_REQUEST}. @@ -574,16 +623,16 @@ create_slot(#state{service_url = undefined, UserDir = <<DocRoot/binary, $/, UserStr/binary>>, case ejabberd_hooks:run_fold(http_upload_slot_request, ServerHost, allow, [JID, UserDir, Size, Lang]) of - allow -> - RandStr = make_rand_string(SecretLength), - FileStr = make_file_string(File), - ?INFO_MSG("Got HTTP upload slot for ~s (file: ~s)", - [jlib:jid_to_string(JID), File]), - {ok, [UserStr, RandStr, FileStr]}; - deny -> - {error, ?ERR_SERVICE_UNAVAILABLE}; - #xmlel{} = Error -> - {error, Error} + allow -> + RandStr = make_rand_string(SecretLength), + FileStr = make_file_string(File), + ?INFO_MSG("Got HTTP upload slot for ~s (file: ~s)", + [jlib:jid_to_string(JID), File]), + {ok, [UserStr, RandStr, FileStr]}; + deny -> + {error, ?ERR_SERVICE_UNAVAILABLE}; + #xmlel{} = Error -> + {error, Error} end; create_slot(#state{service_url = ServiceURL}, #jid{luser = U, lserver = S} = JID, File, Size, ContentType, @@ -597,37 +646,38 @@ create_slot(#state{service_url = ServiceURL}, "&size=" ++ ?URL_ENC(SizeStr) ++ "&content_type=" ++ ?URL_ENC(ContentType), case httpc:request(get, {GetRequest, []}, HttpOptions, Options) of - {ok, {Code, Body}} when Code >= 200, Code =< 299 -> - case binary:split(Body, <<$\n>>, [global, trim]) of - [<<"http", _/binary>> = PutURL, <<"http", _/binary>> = GetURL] -> - ?INFO_MSG("Got HTTP upload slot for ~s (file: ~s)", - [jlib:jid_to_string(JID), File]), - {ok, PutURL, GetURL}; - Lines -> - ?ERROR_MSG("Cannot parse data received for ~s from <~s>: ~p", - [jlib:jid_to_string(JID), ServiceURL, Lines]), - {error, ?ERR_SERVICE_UNAVAILABLE} - end; - {ok, {402, _Body}} -> - ?INFO_MSG("Got status code 402 for ~s from <~s>", - [jlib:jid_to_string(JID), ServiceURL]), - {error, ?ERR_RESOURCE_CONSTRAINT}; - {ok, {403, _Body}} -> - ?INFO_MSG("Got status code 403 for ~s from <~s>", - [jlib:jid_to_string(JID), ServiceURL]), - {error, ?ERR_NOT_ALLOWED}; - {ok, {413, _Body}} -> - ?INFO_MSG("Got status code 413 for ~s from <~s>", - [jlib:jid_to_string(JID), ServiceURL]), - {error, ?ERR_NOT_ACCEPTABLE}; - {ok, {Code, _Body}} -> - ?ERROR_MSG("Got unexpected status code for ~s from <~s>: ~B", - [jlib:jid_to_string(JID), ServiceURL, Code]), - {error, ?ERR_SERVICE_UNAVAILABLE}; - {error, Reason} -> - ?ERROR_MSG("Error requesting upload slot for ~s from <~s>: ~p", - [jlib:jid_to_string(JID), ServiceURL, Reason]), - {error, ?ERR_SERVICE_UNAVAILABLE} + {ok, {Code, Body}} when Code >= 200, Code =< 299 -> + case binary:split(Body, <<$\n>>, [global, trim]) of + [<<"http", _/binary>> = PutURL, + <<"http", _/binary>> = GetURL] -> + ?INFO_MSG("Got HTTP upload slot for ~s (file: ~s)", + [jlib:jid_to_string(JID), File]), + {ok, PutURL, GetURL}; + Lines -> + ?ERROR_MSG("Can't parse data received for ~s from <~s>: ~p", + [jlib:jid_to_string(JID), ServiceURL, Lines]), + {error, ?ERR_SERVICE_UNAVAILABLE} + end; + {ok, {402, _Body}} -> + ?INFO_MSG("Got status code 402 for ~s from <~s>", + [jlib:jid_to_string(JID), ServiceURL]), + {error, ?ERR_RESOURCE_CONSTRAINT}; + {ok, {403, _Body}} -> + ?INFO_MSG("Got status code 403 for ~s from <~s>", + [jlib:jid_to_string(JID), ServiceURL]), + {error, ?ERR_NOT_ALLOWED}; + {ok, {413, _Body}} -> + ?INFO_MSG("Got status code 413 for ~s from <~s>", + [jlib:jid_to_string(JID), ServiceURL]), + {error, ?ERR_NOT_ACCEPTABLE}; + {ok, {Code, _Body}} -> + ?ERROR_MSG("Got unexpected status code for ~s from <~s>: ~B", + [jlib:jid_to_string(JID), ServiceURL, Code]), + {error, ?ERR_SERVICE_UNAVAILABLE}; + {error, Reason} -> + ?ERROR_MSG("Error requesting upload slot for ~s from <~s>: ~p", + [jlib:jid_to_string(JID), ServiceURL, Reason]), + {error, ?ERR_SERVICE_UNAVAILABLE} end. -spec add_slot(slot(), pos_integer(), timer:tref(), state()) -> state(). @@ -719,10 +769,10 @@ iq_disco_info(Lang, Name) -> %% HTTP request handling. --spec store_file(file:filename_all(), binary(), +-spec store_file(binary(), binary(), integer() | undefined, integer() | undefined, - binary(), binary(), boolean()) + binary(), slot(), boolean()) -> ok | {ok, [{binary(), binary()}], binary()} | {error, term()}. store_file(Path, Data, FileMode, DirMode, GetPrefix, LocalPath, Thumbnail) -> @@ -731,10 +781,8 @@ store_file(Path, Data, FileMode, DirMode, GetPrefix, LocalPath, Thumbnail) -> case identify(Path) of {ok, MediaInfo} -> case convert(Path, MediaInfo) of - pass -> - ok; {ok, OutPath} -> - [UserDir, RandDir|_] = LocalPath, + [UserDir, RandDir | _] = LocalPath, FileName = filename:basename(OutPath), URL = str:join([GetPrefix, UserDir, RandDir, FileName], <<$/>>), @@ -742,9 +790,11 @@ store_file(Path, Data, FileMode, DirMode, GetPrefix, LocalPath, Thumbnail) -> {ok, [{<<"Content-Type">>, <<"text/xml; charset=utf-8">>}], - xml:element_to_binary(ThumbEl)} + xml:element_to_binary(ThumbEl)}; + pass -> + ok end; - {error, _} -> + pass -> ok end; ok -> @@ -779,10 +829,10 @@ do_store_file(Path, Data, FileMode, DirMode) -> end, ok = Ok % Raise an exception if file:write/2 failed. catch - _:{badmatch, {error, Error}} -> - {error, ?FORMAT(Error)}; - _:Error -> - {error, Error} + _:{badmatch, {error, Error}} -> + {error, Error}; + _:Error -> + {error, Error} end. -spec guess_content_type(binary()) -> binary(). @@ -820,11 +870,11 @@ http_response(Host, Code, ExtraHeaders, Body) -> end, []), Headers = case proplists:is_defined(<<"Content-Type">>, ExtraHeaders) of - true -> - [ServerHeader | ExtraHeaders]; - false -> - [ServerHeader, {<<"Content-Type">>, <<"text/plain">>} | - ExtraHeaders] + true -> + [ServerHeader | ExtraHeaders]; + false -> + [ServerHeader, {<<"Content-Type">>, <<"text/plain">>} | + ExtraHeaders] end ++ CustomHeaders, {Code, Headers, Body}. @@ -839,56 +889,54 @@ code_to_message(500) -> <<"Internal server error.">>; code_to_message(_Code) -> <<"">>. %%-------------------------------------------------------------------- -%% Image manipulation stuff +%% Image manipulation stuff. %%-------------------------------------------------------------------- --spec identify(string()) -> {ok, media_info()} | {error, string()}. + +-spec identify(binary()) -> {ok, media_info()} | pass. identify(Path) -> - Cmd = lists:flatten(io_lib:fwrite("identify -format \"ok %m %h %w\" ~s", - [Path])), + Cmd = io_lib:format("identify -format 'ok %m %h %w' ~s", [Path]), Res = string:strip(os:cmd(Cmd), right, $\n), case string:tokens(Res, " ") of ["ok", T, H, W] -> - {ok, #media_info{ - type = string:to_lower(T), - height = list_to_integer(H), - width = list_to_integer(W)}}; + {ok, #media_info{type = list_to_binary(string:to_lower(T)), + height = list_to_integer(H), + width = list_to_integer(W)}}; _ -> - ?ERROR_MSG("failed to identify type of ~s: ~s", [Path, Res]), - {error, Res} + ?DEBUG("Cannot identify type of ~s: ~s", [Path, Res]), + pass end. --spec convert(string(), media_info()) -> {ok, string()} | pass. +-spec convert(binary(), media_info()) -> {ok, binary()} | pass. convert(Path, #media_info{type = T, width = W, height = H}) -> - if W*H >= 25000000 -> - ?DEBUG("the image ~s is more than 25 Mbpx", [Path]), + if W * H >= 25000000 -> + ?DEBUG("The image ~s is more than 25 Mpix", [Path]), pass; - (W =< 300) and (H =< 300) -> + W =< 300, H =< 300 -> {ok, Path}; - T == "gif"; T == "jpeg"; T == "png"; T == "webp" -> + T == <<"gif">>; T == <<"jpeg">>; T == <<"png">>; T == <<"webp">> -> Dir = filename:dirname(Path), - FileName = binary_to_list(randoms:get_string()) ++ "." ++ T, + FileName = <<(randoms:get_string())/binary, $., T/binary>>, OutPath = filename:join(Dir, FileName), - Cmd = lists:flatten(io_lib:fwrite("convert -resize 300 ~s ~s", - [Path, OutPath])), + Cmd = io_lib:format("convert -resize 300 ~s ~s", [Path, OutPath]), case os:cmd(Cmd) of "" -> {ok, OutPath}; Err -> - ?ERROR_MSG("failed to convert ~s to ~s: ~s", + ?ERROR_MSG("Failed to convert ~s to ~s: ~s", [Path, OutPath, string:strip(Err, right, $\n)]), pass end; true -> - ?DEBUG("do not call 'convert' for unknown type ~s", [T]), + ?DEBUG("Won't call 'convert' for unknown type ~s", [T]), pass end. --spec thumb_el(string(), binary()) -> xmlel(). +-spec thumb_el(binary(), binary()) -> xmlel(). thumb_el(Path, URI) -> - ContentType = guess_content_type(list_to_binary(Path)), + ContentType = guess_content_type(Path), case identify(Path) of {ok, #media_info{height = H, width = W}} -> #xmlel{name = <<"thumbnail">>, @@ -897,7 +945,7 @@ thumb_el(Path, URI) -> {<<"uri">>, URI}, {<<"height">>, jlib:integer_to_binary(H)}, {<<"width">>, jlib:integer_to_binary(W)}]}; - {error, _} -> + pass -> #xmlel{name = <<"thumbnail">>, attrs = [{<<"xmlns">>, ?NS_THUMBS_1}, {<<"uri">>, URI}, @@ -929,7 +977,7 @@ remove_user(User, Server) -> ?DEBUG("Found no HTTP upload directory of ~s@~s", [User, Server]); {error, Error} -> ?ERROR_MSG("Cannot remove HTTP upload directory of ~s@~s: ~p", - [User, Server, Error]) + [User, Server, ?FORMAT(Error)]) end, ok. @@ -942,16 +990,16 @@ del_tree(Dir) -> {ok, Entries} = file:list_dir(Dir), lists:foreach(fun(Path) -> case filelib:is_dir(Path) of - true -> - ok = del_tree(Path); - false -> - ok = file:delete(Path) + true -> + ok = del_tree(Path); + false -> + ok = file:delete(Path) end end, [Dir ++ "/" ++ Entry || Entry <- Entries]), ok = file:del_dir(Dir) catch - _:{badmatch, {error, Error}} -> - {error, ?FORMAT(Error)}; - _:Error -> - {error, Error} + _:{badmatch, {error, Error}} -> + {error, Error}; + _:Error -> + {error, Error} end. diff --git a/src/mod_http_upload_quota.erl b/src/mod_http_upload_quota.erl index 1b7828a5..d7576567 100644 --- a/src/mod_http_upload_quota.erl +++ b/src/mod_http_upload_quota.erl @@ -1,9 +1,27 @@ -%%%------------------------------------------------------------------- +%%%---------------------------------------------------------------------- %%% File : mod_http_upload_quota.erl %%% Author : Holger Weiss <holger@zedat.fu-berlin.de> %%% Purpose : Quota management for HTTP File Upload (XEP-0363) %%% Created : 15 Oct 2015 by Holger Weiss <holger@zedat.fu-berlin.de> -%%%------------------------------------------------------------------- +%%% +%%% +%%% ejabberd, Copyright (C) 2015 ProcessOne +%%% +%%% This program is free software; you can redistribute it and/or +%%% modify it under the terms of the GNU General Public License as +%%% published by the Free Software Foundation; either version 2 of the +%%% License, or (at your option) any later version. +%%% +%%% This program is distributed in the hope that it will be useful, +%%% but WITHOUT ANY WARRANTY; without even the implied warranty of +%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +%%% General Public License for more details. +%%% +%%% You should have received a copy of the GNU General Public License along +%%% with this program; if not, write to the Free Software Foundation, Inc., +%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +%%% +%%%---------------------------------------------------------------------- -module(mod_http_upload_quota). -author('holger@zedat.fu-berlin.de'). @@ -75,8 +93,8 @@ start(ServerHost, Opts) -> stop(ServerHost) -> Proc = mod_http_upload:get_proc_name(ServerHost, ?PROCNAME), - ok = supervisor:terminate_child(ejabberd_sup, Proc), - ok = supervisor:delete_child(ejabberd_sup, Proc). + supervisor:terminate_child(ejabberd_sup, Proc), + supervisor:delete_child(ejabberd_sup, Proc). -spec mod_opt_type(atom()) -> fun((term()) -> term()) | [atom()]. @@ -143,47 +161,52 @@ handle_cast({handle_slot_request, #jid{user = U, server = S} = JID, Path, Size}, access_hard_quota = AccessHardQuota, disk_usage = DiskUsage} = State) -> HardQuota = case acl:match_rule(ServerHost, AccessHardQuota, JID) of - Hard when is_integer(Hard), Hard > 0 -> - Hard * 1024 * 1024; - _ -> - 0 + Hard when is_integer(Hard), Hard > 0 -> + Hard * 1024 * 1024; + _ -> + 0 end, SoftQuota = case acl:match_rule(ServerHost, AccessSoftQuota, JID) of - Soft when is_integer(Soft), Soft > 0 -> - Soft * 1024 * 1024; - _ -> - 0 + Soft when is_integer(Soft), Soft > 0 -> + Soft * 1024 * 1024; + _ -> + 0 end, OldSize = case dict:find({U, S}, DiskUsage) of - {ok, Value} -> - Value; - error -> - undefined + {ok, Value} -> + Value; + error -> + undefined end, NewSize = case {HardQuota, SoftQuota} of - {0, 0} -> - ?DEBUG("No quota specified for ~s", - [jlib:jid_to_string(JID)]), - Size; - {0, _} -> + {0, 0} -> + ?DEBUG("No quota specified for ~s", + [jlib:jid_to_string(JID)]), + undefined; + {0, _} -> ?WARNING_MSG("No hard quota specified for ~s", [jlib:jid_to_string(JID)]), enforce_quota(Path, Size, OldSize, SoftQuota, SoftQuota); - {_, 0} -> + {_, 0} -> ?WARNING_MSG("No soft quota specified for ~s", [jlib:jid_to_string(JID)]), enforce_quota(Path, Size, OldSize, HardQuota, HardQuota); - _ when SoftQuota > HardQuota -> + _ when SoftQuota > HardQuota -> ?WARNING_MSG("Bad quota for ~s (soft: ~p, hard: ~p)", [jlib:jid_to_string(JID), SoftQuota, HardQuota]), enforce_quota(Path, Size, OldSize, SoftQuota, SoftQuota); - _ -> + _ -> ?DEBUG("Enforcing quota for ~s", [jlib:jid_to_string(JID)]), enforce_quota(Path, Size, OldSize, SoftQuota, HardQuota) end, - {noreply, State#state{disk_usage = dict:store({U, S}, NewSize, DiskUsage)}}; + NewDiskUsage = if is_integer(NewSize) -> + dict:store({U, S}, NewSize, DiskUsage); + true -> + DiskUsage + end, + {noreply, State#state{disk_usage = NewDiskUsage}}; handle_cast(Request, State) -> ?ERROR_MSG("Got unexpected request: ~p", [Request]), {noreply, State}. @@ -196,17 +219,19 @@ handle_info(sweep, #state{server_host = ServerHost, when is_integer(MaxDays), MaxDays > 0 -> ?DEBUG("Got 'sweep' message for ~s", [ServerHost]), case file:list_dir(DocRoot) of - {ok, Entries} -> - BackThen = secs_since_epoch() - (MaxDays * 86400), - DocRootS = binary_to_list(DocRoot), - PathNames = lists:map(fun(Entry) -> DocRootS ++ "/" ++ Entry end, - Entries), - UserDirs = lists:filter(fun filelib:is_dir/1, PathNames), - lists:foreach(fun(UserDir) -> delete_old_files(UserDir, BackThen) end, - UserDirs); - {error, Error} -> - ?ERROR_MSG("Cannot open document root ~s: ~s", - [DocRoot, ?FORMAT(Error)]) + {ok, Entries} -> + BackThen = secs_since_epoch() - (MaxDays * 86400), + DocRootS = binary_to_list(DocRoot), + PathNames = lists:map(fun(Entry) -> + DocRootS ++ "/" ++ Entry + end, Entries), + UserDirs = lists:filter(fun filelib:is_dir/1, PathNames), + lists:foreach(fun(UserDir) -> + delete_old_files(UserDir, BackThen) + end, UserDirs); + {error, Error} -> + ?ERROR_MSG("Cannot open document root ~s: ~s", + [DocRoot, ?FORMAT(Error)]) end, {noreply, State}; handle_info(Info, State) -> @@ -276,14 +301,14 @@ enforce_quota(UserDir, SlotSize, _OldSize, MinSize, MaxSize) -> -spec delete_old_files(file:filename_all(), integer()) -> ok. -delete_old_files(UserDir, Timestamp) -> +delete_old_files(UserDir, CutOff) -> FileInfo = gather_file_info(UserDir), - case [Path || {Path, _Size, Time} <- FileInfo, Time < Timestamp] of - [] -> - ok; - OldFiles -> - lists:foreach(fun(File) -> del_file_and_dir(File) end, OldFiles), - file:del_dir(UserDir) % In case it's empty, now. + case [Path || {Path, _Size, Time} <- FileInfo, Time < CutOff] of + [] -> + ok; + OldFiles -> + lists:foreach(fun(File) -> del_file_and_dir(File) end, OldFiles), + file:del_dir(UserDir) % In case it's empty, now. end. -spec gather_file_info(file:filename_all()) @@ -293,49 +318,50 @@ gather_file_info(Dir) when is_binary(Dir) -> gather_file_info(binary_to_list(Dir)); gather_file_info(Dir) -> case file:list_dir(Dir) of - {ok, Entries} -> - lists:foldl(fun(Entry, Acc) -> - Path = Dir ++ "/" ++ Entry, - case file:read_file_info(Path, [{time, posix}]) of - {ok, #file_info{type = directory}} -> - gather_file_info(Path) ++ Acc; - {ok, #file_info{type = regular, - mtime = Time, - size = Size}} -> - [{Path, Size, Time} | Acc]; - {ok, _Info} -> - ?DEBUG("Won't stat(2) non-regular file ~s", - [Path]), - Acc; - {error, Error} -> - ?ERROR_MSG("Cannot stat(2) ~s: ~s", - [Path, ?FORMAT(Error)]), - Acc - end - end, [], Entries); - {error, enoent} -> - ?DEBUG("Directory ~s doesn't exist", [Dir]), - []; - {error, Error} -> - ?ERROR_MSG("Cannot open directory ~s: ~s", [Dir, ?FORMAT(Error)]), - [] + {ok, Entries} -> + lists:foldl(fun(Entry, Acc) -> + Path = Dir ++ "/" ++ Entry, + case file:read_file_info(Path, + [{time, posix}]) of + {ok, #file_info{type = directory}} -> + gather_file_info(Path) ++ Acc; + {ok, #file_info{type = regular, + mtime = Time, + size = Size}} -> + [{Path, Size, Time} | Acc]; + {ok, _Info} -> + ?DEBUG("Won't stat(2) non-regular file ~s", + [Path]), + Acc; + {error, Error} -> + ?ERROR_MSG("Cannot stat(2) ~s: ~s", + [Path, ?FORMAT(Error)]), + Acc + end + end, [], Entries); + {error, enoent} -> + ?DEBUG("Directory ~s doesn't exist", [Dir]), + []; + {error, Error} -> + ?ERROR_MSG("Cannot open directory ~s: ~s", [Dir, ?FORMAT(Error)]), + [] end. -spec del_file_and_dir(file:name_all()) -> ok. del_file_and_dir(File) -> case file:delete(File) of - ok -> - ?INFO_MSG("Removed ~s", [File]), - Dir = filename:dirname(File), - case file:del_dir(Dir) of - ok -> - ?DEBUG("Removed ~s", [Dir]); - {error, Error} -> - ?INFO_MSG("Cannot remove ~s: ~s", [Dir, ?FORMAT(Error)]) - end; - {error, Error} -> - ?WARNING_MSG("Cannot remove ~s: ~s", [File, ?FORMAT(Error)]) + ok -> + ?INFO_MSG("Removed ~s", [File]), + Dir = filename:dirname(File), + case file:del_dir(Dir) of + ok -> + ?DEBUG("Removed ~s", [Dir]); + {error, Error} -> + ?DEBUG("Cannot remove ~s: ~s", [Dir, ?FORMAT(Error)]) + end; + {error, Error} -> + ?WARNING_MSG("Cannot remove ~s: ~s", [File, ?FORMAT(Error)]) end. -spec secs_since_epoch() -> non_neg_integer(). diff --git a/src/mod_mam.erl b/src/mod_mam.erl index 7781cff9..c23e695d 100644 --- a/src/mod_mam.erl +++ b/src/mod_mam.erl @@ -25,7 +25,7 @@ %%%------------------------------------------------------------------- -module(mod_mam). --protocol({xep, 313, '0.3'}). +-protocol({xep, 313, '0.4'}). -behaviour(gen_mod). diff --git a/src/mod_muc.erl b/src/mod_muc.erl index 05148f8e..890687cd 100644 --- a/src/mod_muc.erl +++ b/src/mod_muc.erl @@ -91,7 +91,6 @@ start_link(Host, Opts) -> [Host, Opts], []). start(Host, Opts) -> - start_supervisor(Host), Proc = gen_mod:get_module_proc(Host, ?PROCNAME), ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]}, temporary, 1000, worker, [?MODULE]}, @@ -99,7 +98,6 @@ start(Host, Opts) -> stop(Host) -> Rooms = shutdown_rooms(Host), - stop_supervisor(Host), Proc = gen_mod:get_module_proc(Host, ?PROCNAME), gen_server:call(Proc, stop), supervisor:delete_child(ejabberd_sup, Proc), @@ -444,19 +442,6 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}. %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- -start_supervisor(Host) -> - Proc = gen_mod:get_module_proc(Host, - ejabberd_mod_muc_sup), - ChildSpec = {Proc, - {ejabberd_tmp_sup, start_link, [Proc, mod_muc_room]}, - permanent, infinity, supervisor, [ejabberd_tmp_sup]}, - supervisor:start_child(ejabberd_sup, ChildSpec). - -stop_supervisor(Host) -> - Proc = gen_mod:get_module_proc(Host, - ejabberd_mod_muc_sup), - supervisor:terminate_child(ejabberd_sup, Proc), - supervisor:delete_child(ejabberd_sup, Proc). do_route(Host, ServerHost, Access, HistorySize, RoomShaper, From, To, Packet, DefRoomOpts) -> diff --git a/src/mod_muc_room.erl b/src/mod_muc_room.erl index cf26bdea..0572bca2 100644 --- a/src/mod_muc_room.erl +++ b/src/mod_muc_room.erl @@ -70,32 +70,19 @@ -endif. -%% Module start with or without supervisor: --ifdef(NO_TRANSIENT_SUPERVISORS). --define(SUPERVISOR_START, - gen_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize, - RoomShaper, Creator, Nick, DefRoomOpts], - ?FSMOPTS)). --else. --define(SUPERVISOR_START, - Supervisor = gen_mod:get_module_proc(ServerHost, ejabberd_mod_muc_sup), - supervisor:start_child( - Supervisor, [Host, ServerHost, Access, Room, HistorySize, RoomShaper, - Creator, Nick, DefRoomOpts])). --endif. - %%%---------------------------------------------------------------------- %%% API %%%---------------------------------------------------------------------- start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Creator, Nick, DefRoomOpts) -> - ?SUPERVISOR_START. + gen_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize, + RoomShaper, Creator, Nick, DefRoomOpts], + ?FSMOPTS). start(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Opts) -> - Supervisor = gen_mod:get_module_proc(ServerHost, ejabberd_mod_muc_sup), - supervisor:start_child( - Supervisor, [Host, ServerHost, Access, Room, HistorySize, RoomShaper, - Opts]). + gen_fsm:start(?MODULE, [Host, ServerHost, Access, Room, HistorySize, + RoomShaper, Opts], + ?FSMOPTS). start_link(Host, ServerHost, Access, Room, HistorySize, RoomShaper, Creator, Nick, DefRoomOpts) -> @@ -376,7 +363,8 @@ normal_state({route, From, <<"">>, catch send_new_presence(TargetJid, Reason, - NSD), + NSD, + StateData), NSD; _ -> StateData end @@ -1044,7 +1032,7 @@ process_presence(From, Nick, StateData), case (?DICT):find(Nick, StateData#state.nicks) of {ok, [_, _ | _]} -> ok; - _ -> send_new_presence(From, NewState) + _ -> send_new_presence(From, NewState, StateData) end, Reason = case xml:get_subtag(NewPacket, <<"status">>) @@ -1122,7 +1110,8 @@ process_presence(From, Nick, end, NewState = add_user_presence(From, Stanza, StateData), - send_new_presence(From, NewState), + send_new_presence( + From, NewState, StateData), NewState end end @@ -1328,7 +1317,7 @@ expulse_participant(Packet, From, StateData, Reason1) -> [{xmlcdata, Reason2}]}]}, StateData), - send_new_presence(From, NewState), + send_new_presence(From, NewState, StateData), remove_online_user(From, NewState). set_affiliation(JID, Affiliation, StateData) -> @@ -1852,7 +1841,7 @@ add_new_user(From, Nick, add_online_user(From, Nick, Role, StateData)), send_existing_presences(From, NewState), - send_new_presence(From, NewState), + send_new_presence(From, NewState, StateData), Shift = count_stanza_shift(Nick, Els, NewState), case send_history(From, Shift, NewState) of true -> ok; @@ -2083,6 +2072,10 @@ is_room_overcrowded(StateData) -> ?DEFAULT_MAX_USERS_PRESENCE), (?DICT):size(StateData#state.users) > MaxUsersPresence. +presence_broadcast_allowed(JID, StateData) -> + Role = get_role(JID, StateData), + lists:member(Role, (StateData#state.config)#config.presence_broadcast). + send_update_presence(JID, StateData) -> send_update_presence(JID, <<"">>, StateData). @@ -2110,15 +2103,17 @@ send_update_presence1(JID, Reason, StateData) -> end end, lists:foreach(fun (J) -> - send_new_presence(J, Reason, StateData) + send_new_presence(J, Reason, StateData, StateData) end, LJIDs). -send_new_presence(NJID, StateData) -> - send_new_presence(NJID, <<"">>, StateData). +send_new_presence(NJID, StateData, OldStateData) -> + send_new_presence(NJID, <<"">>, StateData, OldStateData). -send_new_presence(NJID, Reason, StateData) -> - case is_room_overcrowded(StateData) of +send_new_presence(NJID, Reason, StateData, OldStateData) -> + case is_room_overcrowded(StateData) orelse + not (presence_broadcast_allowed(NJID, StateData) orelse + presence_broadcast_allowed(NJID, OldStateData)) of true -> ok; false -> send_new_presence1(NJID, Reason, StateData) end. @@ -2302,8 +2297,12 @@ change_nick(JID, Nick, StateData) -> end, NewStateData = StateData#state{users = Users, nicks = Nicks}, - send_nick_changing(JID, OldNick, NewStateData, - SendOldUnavailable, SendNewAvailable), + case presence_broadcast_allowed(JID, NewStateData) of + true -> + send_nick_changing(JID, OldNick, NewStateData, + SendOldUnavailable, SendNewAvailable); + false -> ok + end, add_to_log(nickchange, {OldNick, Nick}, StateData), NewStateData. @@ -2679,7 +2678,7 @@ process_item_change(E, SD, UJID) -> {JID, role, Role, Reason} -> SD1 = set_role(JID, Role, SD), catch - send_new_presence(JID, Reason, SD1), + send_new_presence(JID, Reason, SD1, SD), SD1; {JID, affiliation, A, _Reason} -> SD1 = set_affiliation(JID, A, SD), @@ -3419,6 +3418,53 @@ get_config(Lang, StateData, From) -> children = [{xmlcdata, <<"anyone">>}]}]}]}, + #xmlel{name = <<"field">>, + attrs = + [{<<"type">>, <<"list-multi">>}, + {<<"label">>, + translate:translate(Lang, + <<"Roles for which Presence is Broadcasted">>)}, + {<<"var">>, <<"muc#roomconfig_presencebroadcast">>}], + children = + lists:map( + fun(Role) -> + #xmlel{name = <<"value">>, attrs = [], + children = + [{xmlcdata, + atom_to_binary(Role, utf8)}]} + end, Config#config.presence_broadcast + ) ++ + [#xmlel{name = <<"option">>, + attrs = + [{<<"label">>, + translate:translate(Lang, + <<"Moderator">>)}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = + [{xmlcdata, + <<"moderator">>}]}]}, + #xmlel{name = <<"option">>, + attrs = + [{<<"label">>, + translate:translate(Lang, + <<"Participant">>)}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = + [{xmlcdata, + <<"participant">>}]}]}, + #xmlel{name = <<"option">>, + attrs = + [{<<"label">>, + translate:translate(Lang, + <<"Visitor">>)}], + children = + [#xmlel{name = <<"value">>, attrs = [], + children = + [{xmlcdata, + <<"visitor">>}]}]} + ]}, ?BOOLXFIELD(<<"Make room members-only">>, <<"muc#roomconfig_membersonly">>, (Config#config.members_only)), @@ -3692,6 +3738,28 @@ set_xoption([{<<"muc#roomconfig_roomsecret">>, [Val]} set_xoption([{<<"anonymous">>, [Val]} | Opts], Config) -> ?SET_BOOL_XOPT(anonymous, Val); +set_xoption([{<<"muc#roomconfig_presencebroadcast">>, Vals} | Opts], + Config) -> + Roles = + lists:foldl( + fun(_S, error) -> error; + (S, {M, P, V}) -> + case S of + <<"moderator">> -> {true, P, V}; + <<"participant">> -> {M, true, V}; + <<"visitor">> -> {M, P, true}; + _ -> error + end + end, {false, false, false}, Vals), + case Roles of + error -> {error, ?ERR_BAD_REQUEST}; + {M, P, V} -> + Res = + if M -> [moderator]; true -> [] end ++ + if P -> [participant]; true -> [] end ++ + if V -> [visitor]; true -> [] end, + set_xoption(Opts, Config#config{presence_broadcast = Res}) + end; set_xoption([{<<"muc#roomconfig_allowvoicerequests">>, [Val]} | Opts], @@ -3885,6 +3953,10 @@ set_opts([{Opt, Val} | Opts], StateData) -> StateData#state{config = (StateData#state.config)#config{anonymous = Val}}; + presence_broadcast -> + StateData#state{config = + (StateData#state.config)#config{presence_broadcast = + Val}}; logging -> StateData#state{config = (StateData#state.config)#config{logging = diff --git a/src/mod_ping.erl b/src/mod_ping.erl index 9964daf0..54aa6086 100644 --- a/src/mod_ping.erl +++ b/src/mod_ping.erl @@ -255,6 +255,8 @@ cancel_timer(TRef) -> mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1; mod_opt_type(ping_interval) -> fun (I) when is_integer(I), I > 0 -> I end; +mod_opt_type(ping_ack_timeout) -> + fun (I) when is_integer(I), I > 0 -> I end; mod_opt_type(send_pings) -> fun (B) when is_boolean(B) -> B end; mod_opt_type(timeout_action) -> @@ -262,4 +264,4 @@ mod_opt_type(timeout_action) -> (kill) -> kill end; mod_opt_type(_) -> - [iqdisc, ping_interval, send_pings, timeout_action]. + [iqdisc, ping_interval, ping_ack_timeout, send_pings, timeout_action]. diff --git a/src/mod_vcard.erl b/src/mod_vcard.erl index 306bd844..ed6bff11 100644 --- a/src/mod_vcard.erl +++ b/src/mod_vcard.erl @@ -94,7 +94,7 @@ start(Host, Opts) -> <<"vjud.@HOST@">>), Search = gen_mod:get_opt(search, Opts, fun(B) when is_boolean(B) -> B end, - true), + false), register(gen_mod:get_module_proc(Host, ?PROCNAME), spawn(?MODULE, init, [MyHost, Host, Search])). diff --git a/src/mod_vcard_ldap.erl b/src/mod_vcard_ldap.erl index f75bb071..3f6b41a7 100644 --- a/src/mod_vcard_ldap.erl +++ b/src/mod_vcard_ldap.erl @@ -55,7 +55,7 @@ {serverhost = <<"">> :: binary(), myhost = <<"">> :: binary(), eldap_id = <<"">> :: binary(), - search = true :: boolean(), + search = false :: boolean(), servers = [] :: [binary()], backups = [] :: [binary()], port = ?LDAP_PORT :: inet:port_number(), @@ -735,7 +735,7 @@ parse_options(Host, Opts) -> <<"vjud.@HOST@">>), Search = gen_mod:get_opt(search, Opts, fun(B) when is_boolean(B) -> B end, - true), + false), Matches = gen_mod:get_opt(matches, Opts, fun(infinity) -> 0; (I) when is_integer(I), I>0 -> I diff --git a/vars.config.in b/vars.config.in index 69cc516f..5c37fb97 100644 --- a/vars.config.in +++ b/vars.config.in @@ -8,7 +8,6 @@ %%%------------------------------------------------------------------- %% Macros {roster_gateway_workaround, @roster_gateway_workaround@}. -{transient_supervisors, @transient_supervisors@}. {full_xml, @full_xml@}. {nif, @nif@}. {db_type, @db_type@}. |