25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-11-22 16:20:52 +01:00

Unsafe working version of http file transfer

SVN Revision: 798
This commit is contained in:
Jérôme Sautret 2007-06-27 10:01:39 +00:00
parent ef7e43b2dc
commit 9cd3a6db07
12 changed files with 425 additions and 48 deletions

View File

@ -1,12 +1,12 @@
# $Id$ # $Id$
CC = @CC@ CC = @CC@
CFLAGS = @CFLAGS@ @ERLANG_CFLAGS@ CFLAGS = @CFLAGS@ @ERLANG_CFLAGS@
CPPFLAGS = @CPPFLAGS@ CPPFLAGS = @CPPFLAGS@
LDFLAGS = @LDFLAGS@ LDFLAGS = @LDFLAGS@
LIBS = @LIBS@ @ERLANG_LIBS@ LIBS = @LIBS@ @ERLANG_LIBS@
SUBDIRS = SUBDIRS =
OUTDIR = .. OUTDIR = ..
EFLAGS = -I .. -pz .. EFLAGS = -I .. -pz ..
@ -20,6 +20,7 @@ OBJS = \
$(OUTDIR)/mod_proxy65_service.beam \ $(OUTDIR)/mod_proxy65_service.beam \
$(OUTDIR)/mod_proxy65_sm.beam \ $(OUTDIR)/mod_proxy65_sm.beam \
$(OUTDIR)/mod_proxy65_stream.beam \ $(OUTDIR)/mod_proxy65_stream.beam \
$(OUTDIR)/mod_proxy65_http.beam \
$(OUTDIR)/mod_proxy65_lib.beam $(OUTDIR)/mod_proxy65_lib.beam
all: $(OBJS) all: $(OBJS)
@ -35,4 +36,3 @@ distclean: clean
TAGS: TAGS:
etags *.erl etags *.erl

17
src/mod_proxy65/TODO Normal file
View File

@ -0,0 +1,17 @@
HTTP transfer
-------------
* stream POST to GET in real time, whithout storing the file
* handle acl
* get base URL from ejabberd_http config
* add parameters:
- store directory
- URL of upload
* virtual host handling
* check security stuffs:
- limit file size at upload time
- acl
* transfer socks5 -> HTTP
* transfer HTTP -> socks5

View File

@ -50,8 +50,14 @@ init([Host, Opts]) ->
[gen_mod:get_module_proc(Host, ejabberd_mod_proxy65_sup), [gen_mod:get_module_proc(Host, ejabberd_mod_proxy65_sup),
mod_proxy65_stream]}, mod_proxy65_stream]},
transient, infinity, supervisor, [ejabberd_tmp_sup]}, transient, infinity, supervisor, [ejabberd_tmp_sup]},
HttpStreamSupervisor =
{ejabberd_mod_proxy65_http_sup,
{ejabberd_tmp_sup, start_link,
[gen_mod:get_module_proc(Host, ejabberd_mod_proxy65_http_sup),
mod_proxy65_http]},
transient, infinity, supervisor, [ejabberd_tmp_sup]},
StreamManager = StreamManager =
{mod_proxy65_sm, {mod_proxy65_sm, start_link, [Host, Opts]}, {mod_proxy65_sm, {mod_proxy65_sm, start_link, [Host, Opts]},
transient, 5000, worker, [mod_proxy65_sm]}, transient, 5000, worker, [mod_proxy65_sm]},
{ok, {{one_for_one, 10, 1}, {ok, {{one_for_one, 10, 1},
[StreamManager, StreamSupervisor, Service]}}. [StreamManager, StreamSupervisor, HttpStreamSupervisor, Service]}}.

View File

@ -27,20 +27,37 @@
%% RFC 1928 replies %% RFC 1928 replies
-define(SUCCESS, 0). -define(SUCCESS, 0).
-define(ERR_GENERAL_FAILURE, 1). -define(SOCKS5_ERR_GENERAL_FAILURE, 1).
-define(ERR_NOT_ALLOWED, 2). -define(SOCKS5_ERR_NOT_ALLOWED, 2).
-define(ERR_NETWORK_UNREACHABLE, 3). -define(SOCKS5_ERR_NETWORK_UNREACHABLE, 3).
-define(ERR_HOST_UNREACHABLE, 4). -define(SOCKS5_ERR_HOST_UNREACHABLE, 4).
-define(ERR_CONNECTION_REFUSED, 5). -define(SOCKS5_ERR_CONNECTION_REFUSED, 5).
-define(ERR_TTL_EXPIRED, 6). -define(SOCKS5_ERR_TTL_EXPIRED, 6).
-define(ERR_COMMAND_NOT_SUPPORTED, 7). -define(SOCKS5_ERR_COMMAND_NOT_SUPPORTED, 7).
-define(ERR_ADDRESS_TYPE_NOT_SUPPORTED, 8). -define(SOCKS5_ERR_ADDRESS_TYPE_NOT_SUPPORTED, 8).
%% RFC 1928 defined timeout. %% RFC 1928 defined timeout.
-define(SOCKS5_REPLY_TIMEOUT, 10000). -define(SOCKS5_REPLY_TIMEOUT, 10000).
-record(bytestream, {
sha1, %% SHA1 key
target, %% Target Pid
initiator, %% Initiator Pid
active = false, %% Activity flag
jid_i, %% Initiator's JID
jid_t, %% Target's JID (for http file transfert)
file, %% store status of file (for http file transfert)
myhost %% proxy's jid
}).
-record(s5_request, { -record(s5_request, {
rsv = 0, rsv = 0,
cmd, cmd,
sha1 sha1
}). }).
% For http transfer
-define(NS_HTTP_BYTESTREAMS, "http://oneteam.im/bs-proxy").
-define(DEFAULT_HTTP_BASE_PATH, "/proxy").
-define(DEFAULT_HTTP_UPLOAD_PATH, "/upload").
-define(DEFAULT_STORE_PATH, "/tmp").

View File

@ -0,0 +1,156 @@
%%%----------------------------------------------------------------------
%%% File : mod_proxy65_http
%%% Author : Jérôme Sautret <jerome.sautret@process-one.net>
%%% Purpose : HTTP bytestreams proxy for oneteam file transfert
%%% Created : 28 may 2007
%%% Id : $Id: mod_last.erl 370 2005-06-20 03:18:13Z alexey $
%%%----------------------------------------------------------------------
-module(mod_proxy65_http).
-author('jerome.sautret@process-one.net').
-behaviour(gen_mod).
%% gen_mod callbacks.
-export([
start/2,
stop/1
]).
%% mod_proxy65 api
-export([
activate/2
]).
%% ejabberd_http callback.
-export([
process/2
]).
-include("mod_proxy65.hrl").
-include("../ejabberd.hrl").
-include("../jlib.hrl").
-include("../web/ejabberd_http.hrl").
%%%----------------------------------------------------------------------
%%% gen_mod Callbacks
%%%----------------------------------------------------------------------
start(_Host, _Opts) ->
ok.
stop(_Host) ->
ok.
activate({_IPid, _IJid}, {_TPid, _TJid}) ->
ok.
%%%----------------------------------------------------------------------
%%% ejabberd_http Callbacks
%%%----------------------------------------------------------------------
% Receive File
% XXX TODO: limit file size
process(["upload"], #request{method='POST',
content_type = "multipart/form-data;"++_Boundary = ContentType,
data=Data} = _Request) ->
io:format("POST~n", []),
DataParts = ejabberd_http:parse_data(Data, ContentType),
{SHA1, {Filename, FileContentType, FileContent}} = parse_upload_data(DataParts),
case mnesia:dirty_read(bytestream, SHA1) of
[#bytestream{jid_t = TargetJID, file = BaseURL, myhost = MyHost}] when is_list(BaseURL) ->
Path = store_file(Filename, FileContent),
F = fun() ->
mnesia:write(#bytestream{sha1 = SHA1,
file = {path, Path},
target = FileContentType})
end,
mnesia:transaction(F),
URL = BaseURL ++ "/" ++ filename:join(Path),
send_activated(TargetJID, MyHost, SHA1, URL),
Result = "ok",
ejabberd_web:make_xhtml([{xmlcdata, Result}]);
_Other ->
?ERROR_MSG("Upload ~p not activated~n", [SHA1]),
ejabberd_web:error(bad_request)
end;
process([_UID, _Filename] = Path, #request{method='GET'} = _Request) ->
io:format("GET~n~p~n", [Path]),
case mnesia:dirty_index_read(bytestream, {path, Path}, #bytestream.file) of
[#bytestream{sha1=SHA1, target=ContentType}|_Tail] ->
mnesia:dirty_delete({bytestream, SHA1}),
serve_file(Path, ContentType);
_ ->
?ERROR_MSG("Bad request, GET ~p~n", [Path]),
ejabberd_web:error(bad_request)
end.
% store the data transfered in a file and return the URL of the file
store_file(Filename, FileContent) ->
HASH = sha:sha(FileContent),
% TODO store dir from a parameter
Path = ?DEFAULT_STORE_PATH ++ "/" ++ HASH ++ "/",
io:format("Path ~p~n", [Path]),
ok = filelib:ensure_dir(Path),
FilePath = Path ++ "/" ++ Filename,
%io:format("FilePath ~p~n~p", [FilePath, FileContent]),
ok = file:write_file(FilePath, list_to_binary(FileContent)),
[HASH, Filename].
% send the <activated> connexion to the target.
send_activated(TargetJID, MyJID, SHA1, URL) ->
IQ = #iq{type=set,
sub_el=[{xmlelement, "activated",
[{"xmlns", ?NS_HTTP_BYTESTREAMS}, {"sidhash", SHA1}, {"url", URL}], []}]},
ejabberd_router:route(jlib:string_to_jid(MyJID), TargetJID, jlib:iq_to_xml(IQ)).
parse_upload_data(Data) ->
parse_upload_data(Data, {undefined, undefined, undefined}).
parse_upload_data([], Result) ->
Result;
parse_upload_data([#http_data{content_disposition="form-data",
content_type=ContentType, args=Args, data=Data} | Tail],
Result) ->
Result2 = case lists:keysearch("name", 1, Args) of
{value, {"name", "SIDHASH"}} ->
{remove_cr(Data), element(2, Result)};
{value, {"name", "FILE"}} ->
case lists:keysearch("filename", 1, Args) of
{value, {"filename", Filename}} ->
{element(1, Result), {Filename, ContentType, remove_cr(Data)}}
end;
_ ->
Result
end,
parse_upload_data(Tail, Result2).
% remove last trailling carriage return
remove_cr(String) ->
lists:reverse(remove_leading_cr(lists:reverse(String))).
% remove fisrt leading inversed carriage return
remove_leading_cr([$\n, $\r|String]) ->
String;
remove_leading_cr(String) ->
String.
serve_file(Path, ContentType) ->
FileName = filename:join([?DEFAULT_STORE_PATH | Path]),
case file:read_file(FileName) of
{ok, FileContents} ->
?DEBUG("Delivering content.", []),
{200,
[{"Server", "ejabberd"},
{"Content-type", ContentType},
{"Content-disposition", "attachment; filename="++filename:basename(FileName)}],
FileContents};
{error, Error} ->
?DEBUG("Delivering error: ~p", [Error]),
case Error of
eacces -> {403, [], "Forbidden"};
enoent -> {404, [], "Not found"};
_Else -> {500, [], atom_to_list(Error)}
end
end.

View File

@ -53,7 +53,7 @@ make_init_reply(Method) ->
[?VERSION_5, Method]. [?VERSION_5, Method].
make_auth_reply(true) -> [1, ?SUCCESS]; make_auth_reply(true) -> [1, ?SUCCESS];
make_auth_reply(false) -> [1, ?ERR_NOT_ALLOWED]. make_auth_reply(false) -> [1, ?SOCKS5_ERR_NOT_ALLOWED].
%% WARNING: According to SOCKS5 RFC, this reply is _incorrect_, but %% WARNING: According to SOCKS5 RFC, this reply is _incorrect_, but
%% Psi writes junk to the beginning of the file on correct reply. %% Psi writes junk to the beginning of the file on correct reply.
@ -63,7 +63,7 @@ make_reply() ->
[?VERSION_5, ?SUCCESS, 0, 0, 0, 0]. [?VERSION_5, ?SUCCESS, 0, 0, 0, 0].
make_error_reply(Request) -> make_error_reply(Request) ->
make_error_reply(Request, ?ERR_NOT_ALLOWED). make_error_reply(Request, ?SOCKS5_ERR_NOT_ALLOWED).
make_error_reply(#s5_request{rsv = RSV, sha1 = SHA1}, Reason) -> make_error_reply(#s5_request{rsv = RSV, sha1 = SHA1}, Reason) ->
[?VERSION_5, Reason, RSV, ?ATYP_DOMAINNAME, length(SHA1), SHA1, 0,0]. [?VERSION_5, Reason, RSV, ?ATYP_DOMAINNAME, length(SHA1), SHA1, 0,0].

View File

@ -23,6 +23,7 @@
%% API. %% API.
-export([start_link/2]). -export([start_link/2]).
-include("mod_proxy65.hrl").
-include("../ejabberd.hrl"). -include("../ejabberd.hrl").
-include("../jlib.hrl"). -include("../jlib.hrl").
@ -34,6 +35,8 @@
name, name,
stream_addr, stream_addr,
port, port,
http_port,
http_base_path,
acl acl
}). }).
@ -84,12 +87,14 @@ handle_info(_Info, State) ->
%%%------------------------ %%%------------------------
%% disco#info request %% disco#info request
process_iq(_, #iq{type = get, xmlns = ?NS_DISCO_INFO, lang = Lang} = IQ, #state{name=Name}) -> process_iq(_, #iq{type = get, xmlns = ?NS_DISCO_INFO} = IQ, #state{name=Name, http_port=HTTP_Port}) ->
io:format("~p~n", [IQ]),
IQ#iq{type = result, sub_el = IQ#iq{type = result, sub_el =
[{xmlelement, "query", [{"xmlns", ?NS_DISCO_INFO}], iq_disco_info(Lang, Name)}]}; [{xmlelement, "query", [{"xmlns", ?NS_DISCO_INFO}], iq_disco_info(Name, HTTP_Port)}]};
%% disco#items request %% disco#items request
process_iq(_, #iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ, _) -> process_iq(_, #iq{type = get, xmlns = ?NS_DISCO_ITEMS} = IQ, _) ->
io:format("~p~n", [IQ]),
IQ#iq{type = result, sub_el = IQ#iq{type = result, sub_el =
[{xmlelement, "query", [{"xmlns", ?NS_DISCO_ITEMS}], []}]}; [{xmlelement, "query", [{"xmlns", ?NS_DISCO_ITEMS}], []}]};
@ -101,6 +106,7 @@ process_iq(_, #iq{type = get, xmlns = ?NS_VCARD, lang = Lang} = IQ, _) ->
%% bytestreams info request %% bytestreams info request
process_iq(JID, #iq{type = get, sub_el = SubEl, xmlns = ?NS_BYTESTREAMS} = IQ, process_iq(JID, #iq{type = get, sub_el = SubEl, xmlns = ?NS_BYTESTREAMS} = IQ,
#state{acl = ACL, stream_addr = StreamAddr, serverhost = ServerHost}) -> #state{acl = ACL, stream_addr = StreamAddr, serverhost = ServerHost}) ->
io:format("~p~n", [IQ]),
case acl:match_rule(ServerHost, ACL, JID) of case acl:match_rule(ServerHost, ACL, JID) of
allow -> allow ->
StreamHostEl = [{xmlelement, "streamhost", StreamAddr, []}], StreamHostEl = [{xmlelement, "streamhost", StreamAddr, []}],
@ -110,9 +116,39 @@ process_iq(JID, #iq{type = get, sub_el = SubEl, xmlns = ?NS_BYTESTREAMS} = IQ,
IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]} IQ#iq{type = error, sub_el = [SubEl, ?ERR_FORBIDDEN]}
end; end;
%% bytestream target fake connection (for later http connection)
process_iq(TargetJID, #iq{type = set, sub_el = SubEl, xmlns = ?NS_HTTP_BYTESTREAMS} = IQ,
#state{acl = _ACL, myhost = MyHost,
http_port=HTTP_Port, http_base_path=HTTP_Base_Path}) ->
% XXX TODO: acl
SID = xml:get_tag_attr_s("sid", SubEl),
case catch jlib:string_to_jid(xml:get_tag_attr_s("jid", SubEl)) of
InitiatorJID when is_record(InitiatorJID, jid), SID /= "",
length(SID) =< 128, TargetJID /= InitiatorJID ->
Target = jlib:jid_to_string(jlib:jid_tolower(TargetJID)),
Initiator = jlib:jid_to_string(jlib:jid_tolower(InitiatorJID)),
SHA1 = sha:sha(SID ++ Initiator ++ Target),
URL = "http://" ++ MyHost ++ ":"++HTTP_Port++ HTTP_Base_Path,
case catch mod_proxy65_sm:register_stream(SHA1, TargetJID, URL, MyHost, self()) of
{atomic, ok} ->
IQ#iq{type = result, sub_el =
[{xmlelement, "connected",
[{"xmlns", ?NS_HTTP_BYTESTREAMS}, {"jid", MyHost}], []}]};
_Reason ->
?ERROR_MSG("process IQ set ~p:~n~p~n", [?NS_HTTP_BYTESTREAMS, _Reason]),
IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
end;
_Reason ->
?ERROR_MSG("process IQ set ~p:~n~p~n", [?NS_HTTP_BYTESTREAMS, _Reason]),
IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
end;
%% bytestream activation request %% bytestream activation request
process_iq(InitiatorJID, #iq{type = set, sub_el = SubEl, xmlns = ?NS_BYTESTREAMS} = IQ, process_iq(InitiatorJID, #iq{type = set, sub_el = SubEl, xmlns = ?NS_BYTESTREAMS} = IQ,
#state{acl = ACL, serverhost = ServerHost}) -> #state{acl = ACL, serverhost = ServerHost, myhost = MyHost,
http_port=HTTP_Port, http_base_path=HTTP_Base_Path}) ->
case acl:match_rule(ServerHost, ACL, InitiatorJID) of case acl:match_rule(ServerHost, ACL, InitiatorJID) of
allow -> allow ->
ActivateEl = xml:get_path_s(SubEl, [{elem, "activate"}]), ActivateEl = xml:get_path_s(SubEl, [{elem, "activate"}]),
@ -123,9 +159,21 @@ process_iq(InitiatorJID, #iq{type = set, sub_el = SubEl, xmlns = ?NS_BYTESTREAMS
Target = jlib:jid_to_string(jlib:jid_tolower(TargetJID)), Target = jlib:jid_to_string(jlib:jid_tolower(TargetJID)),
Initiator = jlib:jid_to_string(jlib:jid_tolower(InitiatorJID)), Initiator = jlib:jid_to_string(jlib:jid_tolower(InitiatorJID)),
SHA1 = sha:sha(SID ++ Initiator ++ Target), SHA1 = sha:sha(SID ++ Initiator ++ Target),
case mod_proxy65_sm:activate_stream(SHA1, InitiatorJID, TargetJID, ServerHost) of {Module, Activated} =
case xml:get_path_s(SubEl, [{elem, "x"}]) of
{xmlelement, "x", [{"xmlns", ?NS_HTTP_BYTESTREAMS}], _} ->
{mod_proxy65_http,
[{xmlelement, "activated",
[{"xmlns", ?NS_HTTP_BYTESTREAMS},
{"url", "http://" ++ MyHost ++ ":"++HTTP_Port++
HTTP_Base_Path ++ ?DEFAULT_HTTP_UPLOAD_PATH}], []}]};
_ ->
{mod_proxy65_sm, []}
end,
case mod_proxy65_sm:activate_stream(SHA1, InitiatorJID, TargetJID,
ServerHost, Module) of
ok -> ok ->
IQ#iq{type = result, sub_el = []}; IQ#iq{type = result, sub_el = Activated};
false -> false ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]}; IQ#iq{type = error, sub_el = [SubEl, ?ERR_ITEM_NOT_FOUND]};
limit -> limit ->
@ -135,7 +183,8 @@ process_iq(InitiatorJID, #iq{type = set, sub_el = SubEl, xmlns = ?NS_BYTESTREAMS
_ -> _ ->
IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]} IQ#iq{type = error, sub_el = [SubEl, ?ERR_INTERNAL_SERVER_ERROR]}
end; end;
_ -> _Reason ->
?ERROR_MSG("process IQ set ~p:~n~p~n", [?NS_BYTESTREAMS, _Reason]),
IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]} IQ#iq{type = error, sub_el = [SubEl, ?ERR_BAD_REQUEST]}
end; end;
deny -> deny ->
@ -155,15 +204,21 @@ process_iq(_, _, _) ->
%%%------------------------- %%%-------------------------
-define(FEATURE(Feat), {xmlelement,"feature",[{"var", Feat}],[]}). -define(FEATURE(Feat), {xmlelement,"feature",[{"var", Feat}],[]}).
iq_disco_info(Lang, Name) -> iq_disco_info(Name, HTTP_Port) ->
HTTP_Bytestreams = case HTTP_Port of
"0" ->
[];
_ ->
[?FEATURE(?NS_HTTP_BYTESTREAMS)]
end,
[{xmlelement, "identity", [{xmlelement, "identity",
[{"category", "proxy"}, [{"category", "proxy"},
{"type", "bytestreams"}, {"type", "bytestreams"},
{"name", translate:translate(Lang, Name)}], []}, {"name", Name}], []},
?FEATURE(?NS_DISCO_INFO), ?FEATURE(?NS_DISCO_INFO),
?FEATURE(?NS_DISCO_ITEMS), ?FEATURE(?NS_DISCO_ITEMS),
?FEATURE(?NS_VCARD), ?FEATURE(?NS_VCARD),
?FEATURE(?NS_BYTESTREAMS)]. ?FEATURE(?NS_BYTESTREAMS)] ++ HTTP_Bytestreams.
iq_vcard(Lang) -> iq_vcard(Lang) ->
[{xmlelement, "FN", [], [{xmlelement, "FN", [],
@ -177,6 +232,8 @@ iq_vcard(Lang) ->
parse_options(ServerHost, Opts) -> parse_options(ServerHost, Opts) ->
MyHost = gen_mod:get_opt(host, Opts, "proxy." ++ ServerHost), MyHost = gen_mod:get_opt(host, Opts, "proxy." ++ ServerHost),
Port = gen_mod:get_opt(port, Opts, 7777), Port = gen_mod:get_opt(port, Opts, 7777),
HTTP_Port = integer_to_list(gen_mod:get_opt(http_port, Opts, 0)),
HTTP_Base_Path = gen_mod:get_opt(http_base_path, Opts, ?DEFAULT_HTTP_BASE_PATH),
ACL = gen_mod:get_opt(access, Opts, all), ACL = gen_mod:get_opt(access, Opts, all),
Name = gen_mod:get_opt(name, Opts, "SOCKS5 Bytestreams"), Name = gen_mod:get_opt(name, Opts, "SOCKS5 Bytestreams"),
IP = case gen_mod:get_opt(ip, Opts, none) of IP = case gen_mod:get_opt(ip, Opts, none) of
@ -189,7 +246,9 @@ parse_options(ServerHost, Opts) ->
serverhost = ServerHost, serverhost = ServerHost,
name = Name, name = Name,
port = Port, port = Port,
stream_addr = StreamAddr, http_port = HTTP_Port,
http_base_path = HTTP_Base_Path,
stream_addr = StreamAddr,
acl = ACL}}. acl = ACL}}.
%% Return the IP of the proxy host, or if not found, the ip of the xmpp domain %% Return the IP of the proxy host, or if not found, the ip of the xmpp domain
@ -201,4 +260,4 @@ get_proxy_or_domainip(ServerHost, MyHost) ->
{ok, Addr} -> Addr; {ok, Addr} -> Addr;
{error, _} -> {127,0,0,1} {error, _} -> {127,0,0,1}
end end
end. end.

View File

@ -24,18 +24,14 @@
-export([ -export([
start_link/2, start_link/2,
register_stream/1, register_stream/1,
register_stream/5,
unregister_stream/1, unregister_stream/1,
activate_stream/4 activate_stream/5
]). ]).
-include("mod_proxy65.hrl").
-record(state, {max_connections}). -record(state, {max_connections}).
-record(bytestream, {
sha1, %% SHA1 key
target, %% Target Pid
initiator, %% Initiator Pid
active = false, %% Activity flag
jid_i %% Initiator's JID
}).
-define(PROCNAME, ejabberd_mod_proxy65_sm). -define(PROCNAME, ejabberd_mod_proxy65_sm).
@ -55,6 +51,7 @@ start_link(Host, Opts) ->
init([Opts]) -> init([Opts]) ->
mnesia:create_table(bytestream, [{ram_copies, [node()]}, mnesia:create_table(bytestream, [{ram_copies, [node()]},
{attributes, record_info(fields, bytestream)}]), {attributes, record_info(fields, bytestream)}]),
mnesia:add_table_index(bytestream, file),
mnesia:add_table_copy(bytestream, node(), ram_copies), mnesia:add_table_copy(bytestream, node(), ram_copies),
MaxConnections = gen_mod:get_opt(max_connections, Opts, infinity), MaxConnections = gen_mod:get_opt(max_connections, Opts, infinity),
{ok, #state{max_connections=MaxConnections}}. {ok, #state{max_connections=MaxConnections}}.
@ -67,7 +64,7 @@ handle_call({activate, SHA1, IJid}, _From, State) ->
F = fun() -> F = fun() ->
case mnesia:read(bytestream, SHA1, write) of case mnesia:read(bytestream, SHA1, write) of
[#bytestream{target = TPid, initiator = IPid} = ByteStream] [#bytestream{target = TPid, initiator = IPid} = ByteStream]
when is_pid(TPid), is_pid(IPid) -> when is_pid(TPid), is_pid(IPid) ->
ActiveFlag = ByteStream#bytestream.active, ActiveFlag = ByteStream#bytestream.active,
if if
ActiveFlag == false -> ActiveFlag == false ->
@ -109,13 +106,20 @@ handle_call(_Request, _From, State) ->
%%% transaction abort %%% transaction abort
%%% SHA1 = string() %%% SHA1 = string()
%%%--------------------------------------------------- %%%---------------------------------------------------
register_stream(SHA1) when is_list(SHA1) -> register_stream(SHA1) ->
register_stream(SHA1, undefined, undefined, undefined, undefined).
register_stream(SHA1, JID, URL, MyHost, InitiatorPID) when is_list(SHA1) ->
% PIDs are not used for http, we set it for compatibilty with plain socks5
StreamPid = self(), StreamPid = self(),
F = fun() -> F = fun() ->
case mnesia:read(bytestream, SHA1, write) of case mnesia:read(bytestream, SHA1, write) of
[] -> [] ->
mnesia:write(#bytestream{sha1 = SHA1, mnesia:write(#bytestream{sha1 = SHA1,
target = StreamPid}); target = StreamPid,
initiator = InitiatorPID,
jid_t = JID,
file = URL,
myhost = MyHost});
[#bytestream{target = Pid, [#bytestream{target = Pid,
initiator = undefined} = ByteStream] initiator = undefined} = ByteStream]
when is_pid(Pid), Pid /= StreamPid -> when is_pid(Pid), Pid /= StreamPid ->
@ -145,14 +149,14 @@ unregister_stream(SHA1) when is_list(SHA1) ->
%%% IJid = TJid = jid() %%% IJid = TJid = jid()
%%% Host = string() %%% Host = string()
%%%-------------------------------------------------------- %%%--------------------------------------------------------
activate_stream(SHA1, IJid, TJid, Host) when is_list(SHA1) -> activate_stream(SHA1, IJid, TJid, Host, Module) when is_list(SHA1) ->
Proc = gen_mod:get_module_proc(Host, ?PROCNAME), Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
case catch gen_server:call(Proc, {activate, SHA1, IJid}) of case catch gen_server:call(Proc, {activate, SHA1, IJid}) of
{atomic, {ok, IPid, TPid}} -> {atomic, {ok, IPid, TPid}} ->
mod_proxy65_stream:activate({IPid, IJid}, {TPid, TJid}); Module:activate({IPid, IJid}, {TPid, TJid});
{atomic, {limit, IPid, TPid}} -> {atomic, {limit, IPid, TPid}} ->
mod_proxy65_stream:stop(IPid), Module:stop(IPid),
mod_proxy65_stream:stop(TPid), Module:stop(TPid),
limit; limit;
{atomic, conflict} -> {atomic, conflict} ->
conflict; conflict;

View File

@ -163,7 +163,7 @@ wait_for_request(Packet, #state{socket=Socket} = StateData) ->
{stop, normal, StateData} {stop, normal, StateData}
end; end;
#s5_request{cmd=udp} -> #s5_request{cmd=udp} ->
Err = mod_proxy65_lib:make_error_reply(Request, ?ERR_COMMAND_NOT_SUPPORTED), Err = mod_proxy65_lib:make_error_reply(Request, ?SOCKS5_ERR_COMMAND_NOT_SUPPORTED),
gen_tcp:send(Socket, Err), gen_tcp:send(Socket, Err),
{stop, normal, StateData}; {stop, normal, StateData};
_ -> _ ->

View File

@ -16,7 +16,10 @@
become_controller/1, become_controller/1,
socket_type/0, socket_type/0,
receive_headers/1, receive_headers/1,
url_encode/1]). url_encode/1,
test/0, get_line/1, data/0,
parse_data/2
]).
-include("ejabberd.hrl"). -include("ejabberd.hrl").
-include("jlib.hrl"). -include("jlib.hrl").
@ -30,6 +33,7 @@
request_auth, request_auth,
request_keepalive, request_keepalive,
request_content_length, request_content_length,
request_content_type,
request_lang = "en", request_lang = "en",
%% XXX bard: request handlers are configured in %% XXX bard: request handlers are configured in
%% ejabberd.cfg under the HTTP service. For example, %% ejabberd.cfg under the HTTP service. For example,
@ -194,6 +198,8 @@ process_header(State, Data) ->
_ -> _ ->
State State
end; end;
{ok, {http_header, _, 'Content-Type', _, ContentType}} ->
State#state{request_content_type = ContentType};
{ok, {http_header, _, 'Accept-Language', _, Langs}} -> {ok, {http_header, _, 'Accept-Language', _, Langs}} ->
State#state{request_lang = parse_lang(Langs)}; State#state{request_lang = parse_lang(Langs)};
{ok, {http_header, _, _, _, _}} -> {ok, {http_header, _, _, _, _}} ->
@ -302,6 +308,7 @@ process_request(#state{request_method = 'POST',
request_auth = Auth, request_auth = Auth,
request_content_length = Len, request_content_length = Len,
request_lang = Lang, request_lang = Lang,
request_content_type = ContentType,
sockmod = SockMod, sockmod = SockMod,
socket = Socket, socket = Socket,
request_handlers = RequestHandlers} = State) request_handlers = RequestHandlers} = State)
@ -317,7 +324,7 @@ process_request(#state{request_method = 'POST',
case (catch url_decode_q_split(Path)) of case (catch url_decode_q_split(Path)) of
{'EXIT', _} -> {'EXIT', _} ->
process_request(false); process_request(false);
{NPath, Query} -> {NPath, _Query} ->
LPath = string:tokens(NPath, "/"), LPath = string:tokens(NPath, "/"),
LQuery = case (catch parse_urlencoded(Data)) of LQuery = case (catch parse_urlencoded(Data)) of
{'EXIT', _Reason} -> {'EXIT', _Reason} ->
@ -329,6 +336,7 @@ process_request(#state{request_method = 'POST',
path = LPath, path = LPath,
q = LQuery, q = LQuery,
auth = Auth, auth = Auth,
content_type = ContentType,
data = Data, data = Data,
lang = Lang}, lang = Lang},
case process(RequestHandlers, Request) of case process(RequestHandlers, Request) of
@ -355,7 +363,7 @@ process_request(State) ->
recv_data(State, Len) -> recv_data(State, Len) ->
recv_data(State, Len, []). recv_data(State, Len, []).
recv_data(State, 0, Acc) -> recv_data(_State, 0, Acc) ->
binary_to_list(list_to_binary(Acc)); binary_to_list(list_to_binary(Acc));
recv_data(State, Len, Acc) -> recv_data(State, Len, Acc) ->
case State#state.trail of case State#state.trail of
@ -615,7 +623,7 @@ code_to_phrase(504) -> "Gateway Timeout";
code_to_phrase(505) -> "HTTP Version Not Supported". code_to_phrase(505) -> "HTTP Version Not Supported".
parse_auth(Orig = "Basic " ++ Auth64) -> parse_auth(_Orig = "Basic " ++ Auth64) ->
case decode_base64(Auth64) of case decode_base64(Auth64) of
{error, _Err} -> {error, _Err} ->
undefined; undefined;
@ -972,3 +980,102 @@ get_line("\r\n" ++ Tail, Cur) ->
end; end;
get_line([H|T], Cur) -> get_line([H|T], Cur) ->
get_line(T, [H|Cur]). get_line(T, [H|Cur]).
% return {Value, [{Arg, ArgValue}]}
% example, for
% String = "form-data; name=\"FILE\"; filename=\"TEST\""
% return
% {"form-data", [{"name", "FILE"}, {"filename", "TEST"}]}
% XXX TODO: don't work if an arg value contains a semicolon ;
parse_header(String) ->
[Value | Args] = string:tokens(String, ";"),
catch {string:strip(Value),
lists:map(fun(Arg) ->
[ArgName, ArgValue] = string:tokens(string:strip(Arg), "="),
{string:strip(ArgName), string:strip(string:strip(ArgValue), both, $")}
end, Args)}.
% return [#http_data]
parse_data(Data, ContentType) ->
case parse_header(ContentType) of
{"multipart/form-data", Args} ->
case lists:keysearch("boundary", 1, Args) of
{value, {"boundary", Boundary}} ->
parse_multipart_data(Data, "--"++Boundary);
false ->
{error, "no boundary for multipart/form-data Content-Type"}
end;
{'EXIT', _} ->
{error, "malformed Content-Type"};
_ ->
[#http_data{content_type=ContentType, data=Data}]
end.
parse_multipart_data(Data, Boundary)->
case catch parse_multipart_data([], Data, Boundary) of
{'EXIT', _Reason} ->
{error, "malformed multipart/form-data body"};
List ->
List
end.
parse_multipart_data(Acc, Tail, Boundary) ->
BoundaryEnd = Boundary++"--\r\n",
case get_line(Tail) of
{incomplete, BoundaryEnd} ->
lists:reverse(Acc);
{line, Boundary, Tail2} ->
parse_multipart_headers([#http_data{data=[]} | Acc], Tail2, Boundary);
{line, Line, Tail2} ->
[#http_data{data=Data} = Cur | T] = Acc,
parse_multipart_data([Cur#http_data{data=Data++Line++"\r\n"} | T], Tail2, Boundary);
{lastline, Line, Tail2} ->
[#http_data{data=Data} = Cur | T] = Acc,
parse_multipart_data([Cur#http_data{data=Data++Line++"\r\n\r\n"} | T], Tail2, Boundary)
end.
parse_multipart_headers([#http_data{args=Args} = Cur | T], Tail, Boundary) ->
case get_line(Tail) of
{LineType, Line, Tail2} when Line /= Boundary ->
NewCur = case Line of
"Content-Type:"++Value ->
{Header, NewArgs} = parse_header(Value),
Cur#http_data{content_type=Header,
args=Args++NewArgs};
"Content-Disposition:"++Value ->
{Header, NewArgs} = parse_header(Value),
Cur#http_data{content_disposition=Header,
args=Args++NewArgs};
_ ->
Cur
end,
case LineType of
line ->
parse_multipart_headers([NewCur | T], Tail2, Boundary);
lastline ->
parse_multipart_data([NewCur | T], Tail2, Boundary)
end
end.
data() ->
S="-----------------------------7148830871206398517200280906
Content-Type: text/plain; charset=ISO-8859-2
Content-Disposition: form-data; name=\"SIDHASH\"
SIDHASH
-----------------------------7148830871206398517200280906
Content-Disposition: form-data; name=\"FILE\"; filename=\"TEST\"
Content-Type: application/octet-stream
TEST
-----------------------------7148830871206398517200280906--
",
lists:flatten(lists:map( fun($\n) ->
"\r\n";
(C) ->
C
end, S)).
test() ->
parse_data(data(), "multipart/form-data; boundary=---------------------------7148830871206398517200280906").

View File

@ -12,6 +12,13 @@
us, us,
auth, auth,
lang = "", lang = "",
content_type,
data = "", data = "",
ip ip
}). }).
-record(http_data, {content_type,
content_disposition,
args=[],
data
}).

View File

@ -1,7 +1,7 @@
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
%%% File : ejabberd_web.erl %%% File : ejabberd_web.erl
%%% Author : Alexey Shchepin <alexey@sevcom.net> %%% Author : Alexey Shchepin <alexey@sevcom.net>
%%% Purpose : %%% Purpose :
%%% Created : 28 Feb 2004 by Alexey Shchepin <alexey@sevcom.net> %%% Created : 28 Feb 2004 by Alexey Shchepin <alexey@sevcom.net>
%%% Id : $Id$ %%% Id : $Id$
%%%---------------------------------------------------------------------- %%%----------------------------------------------------------------------
@ -53,7 +53,11 @@ make_xhtml(Els) ->
{"name", Name}, {"name", Name},
{"value", Value}])). {"value", Value}])).
error(bad_request) ->
{400, [], make_xhtml([?XC("h1", "400 Bad Request")])};
error(not_allowed) ->
{401, [], make_xhtml([?XC("h1", "401 Unauthorized")])};
error(not_found) -> error(not_found) ->
{404, [], make_xhtml([?XC("h1", "404 Not Found")])}; {404, [], make_xhtml([?XC("h1", "404 Not Found")])};
error(not_allowed) -> error(internal) ->
{401, [], make_xhtml([?XC("h1", "401 Unauthorized")])}. {500, [], make_xhtml([?XC("h1", "500 Internal Error")])}.