From d45ec798ee75d65515538c7cae1141256e410bed Mon Sep 17 00:00:00 2001 From: Holger Weiss Date: Mon, 31 Aug 2015 20:06:22 +0200 Subject: [PATCH] mod_http_upload: Make file/dir modes configurable The new "file_mode" and "dir_mode" options allow admins to specify the permission bits of files and directories created by mod_http_upload. --- mod_http_upload/README.txt | 13 +++++++ mod_http_upload/src/mod_http_upload.erl | 51 ++++++++++++++++++++----- 2 files changed, 54 insertions(+), 10 deletions(-) diff --git a/mod_http_upload/README.txt b/mod_http_upload/README.txt index ad4be8f..8c38092 100644 --- a/mod_http_upload/README.txt +++ b/mod_http_upload/README.txt @@ -77,6 +77,19 @@ The configurable mod_http_upload options are: mod_http_upload. Otherwise, a SHA-1 hash of the user's bare JID is included instead. +- file_mode (default: 'undefined') + + This option defines the permission bits of uploaded files. The bits are + specified as an octal number (see the chmod(1) manual page) within double + quotes. For example: "0644". + +- dir_mode (default: 'undefined') + + This option defines the permission bits of the 'docroot' directory and any + directories created during file uploads. The bits are specified as an + octal number (see the chmod(1) manual page) within double quotes. For + example: "0755". + - docroot (default: "@HOME@/upload") Uploaded files are stored below the directory specified (as an absolute diff --git a/mod_http_upload/src/mod_http_upload.erl b/mod_http_upload/src/mod_http_upload.erl index c109a58..ce1db55 100644 --- a/mod_http_upload/src/mod_http_upload.erl +++ b/mod_http_upload/src/mod_http_upload.erl @@ -16,6 +16,7 @@ -define(PROCNAME, ?MODULE). -define(URL_ENC(URL), binary_to_list(ejabberd_http:url_encode(URL))). -define(ADDR_TO_STR(IP), ejabberd_config:may_hide_data(jlib:ip_to_list(IP))). +-define(STR_TO_INT(Str, B), jlib:binary_to_integer(iolist_to_binary(Str), B)). -define(DEFAULT_CONTENT_TYPE, <<"application/octet-stream">>). -define(CONTENT_TYPES, [{<<".avi">>, <<"video/avi">>}, @@ -78,6 +79,8 @@ max_size :: pos_integer() | infinity, secret_length :: pos_integer(), jid_in_url :: sha1 | node, + file_mode :: integer() | undefined, + dir_mode :: integer() | undefined, docroot :: binary(), put_url :: binary(), get_url :: binary(), @@ -164,6 +167,10 @@ mod_opt_type(jid_in_url) -> fun(sha1) -> sha1; (node) -> node end; +mod_opt_type(file_mode) -> + fun(Mode) -> ?STR_TO_INT(Mode, 8) end; +mod_opt_type(dir_mode) -> + fun(Mode) -> ?STR_TO_INT(Mode, 8) end; mod_opt_type(docroot) -> fun iolist_to_binary/1; mod_opt_type(put_url) -> @@ -181,8 +188,8 @@ mod_opt_type(service_url) -> mod_opt_type(rm_on_unregister) -> fun(B) when is_boolean(B) -> B end; mod_opt_type(_) -> - [host, name, access, max_size, secret_length, jid_in_url, docroot, - put_url, get_url, service_url, rm_on_unregister]. + [host, name, access, max_size, secret_length, jid_in_url, file_mode, + dir_mode, docroot, put_url, get_url, service_url, rm_on_unregister]. %%-------------------------------------------------------------------- %% gen_server callbacks. @@ -215,6 +222,10 @@ init({ServerHost, Opts}) -> DocRoot = gen_mod:get_opt(docroot, Opts, fun iolist_to_binary/1, <<"@HOME@/upload">>), + FileMode = gen_mod:get_opt(file_mode, Opts, + fun(Mode) -> ?STR_TO_INT(Mode, 8) end), + DirMode = gen_mod:get_opt(dir_mode, Opts, + fun(Mode) -> ?STR_TO_INT(Mode, 8) end), PutURL = gen_mod:get_opt(put_url, Opts, fun(<<"http://", _/binary>> = URL) -> URL; (<<"https://", _/binary>> = URL) -> URL @@ -240,10 +251,17 @@ init({ServerHost, Opts}) -> application:start(public_key), application:start(ssl) end, + case DirMode of + undefined -> + ok; + Mode -> + file:change_mode(DocRoot, Mode) + end, ejabberd_router:register_route(Host), {ok, #state{server_host = ServerHost, host = Host, name = Name, access = Access, max_size = MaxSize, secret_length = SecretLength, jid_in_url = JIDinURL, + file_mode = FileMode, dir_mode = DirMode, docroot = expand_home(str:strip(DocRoot, right, $/)), put_url = expand_host(str:strip(PutURL, right, $/), ServerHost), get_url = expand_host(str:strip(GetURL, right, $/), ServerHost), @@ -251,13 +269,15 @@ init({ServerHost, Opts}) -> -spec handle_call(_, {pid(), _}, state()) -> {noreply, state()}. -handle_call({use_slot, Slot}, _From, #state{docroot = DocRoot} = State) -> +handle_call({use_slot, Slot}, _From, #state{file_mode = FileMode, + dir_mode = DirMode, + 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}, NewState}; + {reply, {ok, Size, Path, FileMode, DirMode}, NewState}; error -> {reply, {error, <<"Invalid slot">>}, State} end; @@ -322,10 +342,10 @@ process(LocalPath, #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} when byte_size(Data) == Size -> + {ok, Size, Path, FileMode, DirMode} when byte_size(Data) == Size -> ?DEBUG("Storing file from ~s for ~s: ~s", [?ADDR_TO_STR(IP), Host, Path]), - case store_file(Path, Data) of + case store_file(Path, Data, FileMode, DirMode) of ok -> http_response(201); {error, Error} -> @@ -635,15 +655,26 @@ iq_disco_info(Lang, Name) -> %% HTTP request handling. --spec store_file(file:filename_all(), binary()) -> ok | {error, term()}. +-spec store_file(file:filename_all(), binary(), integer(), integer()) + -> ok | {error, term()}. -store_file(Path, Data) -> +store_file(Path, Data, FileMode, DirMode) -> try ok = filelib:ensure_dir(Path), {ok, Io} = file:open(Path, [write, exclusive, raw]), Ok = file:write(Io, Data), - ok = file:close(Io), % Close file even if file:write/2 failed. - ok = Ok % But raise an exception in that case. + ok = file:close(Io), + ok = if is_integer(FileMode) -> + file:change_mode(Path, FileMode); + FileMode == undefined -> + ok + end, + ok = if is_integer(DirMode) -> + file:change_mode(filename:dirname(Path), DirMode); + DirMode == undefined -> + ok + end, + ok = Ok % Raise an exception if file:write/2 failed. catch _:{badmatch, {error, Error}} -> {error, Error};