From a0e27ffe656c3cde8e9e405630a95a0e6e2934e6 Mon Sep 17 00:00:00 2001 From: Ben Langfeld Date: Wed, 13 Aug 2014 12:21:04 -0300 Subject: [PATCH 1/7] Add mod_post_log --- mod_post_log/mod_post_log.erl | 143 ++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 mod_post_log/mod_post_log.erl diff --git a/mod_post_log/mod_post_log.erl b/mod_post_log/mod_post_log.erl new file mode 100644 index 0000000..c28def2 --- /dev/null +++ b/mod_post_log/mod_post_log.erl @@ -0,0 +1,143 @@ +%%%---------------------------------------------------------------------- +%%% File : mod_post_log.erl +%%% Author : Tim Stewart +%%% Purpose : POST user messages to server via HTTP +%%% Created : 02 Aug 2014 by Tim Stewart +%%% +%%% Based on mod_service_log.erl +%%%---------------------------------------------------------------------- + +-module(mod_post_log). +-author('tim@stoo.org'). + +-behaviour(gen_mod). + +-export([start/2, + stop/1, + log_user_send/3, + post_result/1]). + +-include("ejabberd.hrl"). +-include("jlib.hrl"). + +start(Host, _Opts) -> + ok = case inets:start() of + {error, {already_started, inets}} -> + ok; + ok -> + ok + end, + ejabberd_hooks:add(user_send_packet, Host, + ?MODULE, log_user_send, 50), + ok. + +stop(Host) -> + ejabberd_hooks:delete(user_send_packet, Host, + ?MODULE, log_user_send, 50), + ok. + +log_user_send(From, To, Packet) -> + ok = log_packet(From, To, Packet, From#jid.lserver). + +log_packet(From, To, {xmlelement, "message", _Attrs, _Els} = Packet, Host) -> + ok = log_message(From, To, Packet, Host); + +log_packet(_From, _To, {xmlelement, _Name, _Attrs, _Els}, _Host) -> + ok. + +log_message(From, To, {xmlelement, _Name, Attrs, _Els} = Packet, Host) -> + Type = lists:keyfind("type", 1, Attrs), + log_message_filter(Type, From, To, Packet, Host). + +log_message_filter({"type", Type}, From, To, Packet, Host) + when Type =:= "chat"; + Type =:= "groupchat" -> + log_chat(From, To, Packet, Host); +log_message_filter(_Other, _From, _To, _Packet, _Host) -> + ok. + +log_chat(From, To, {xmlelement, _Name, _Attrs, Els} = Packet, Host) -> + case get_body(Els) of + no_body -> + ok; + {ok, _Body} -> + log_chat_with_body(From, To, Packet, Host) + end. + +log_chat_with_body(_From, _To, Packet, _Host) -> + post_xml(xml:element_to_binary(Packet)). + +post_xml(Xml) -> + Ts = to_iso_8601_date(os:timestamp()), + + Body = Xml, + + Url = get_opt(url), + TsHeader = get_opt(ts_header, "X-Message-Timestamp"), + Headers = [ {TsHeader, Ts} | get_opt(headers, []) ], + io:format("Headers: ~p\n", [Headers]), + ContentType = get_opt(content_type, "text/xml"), + HttpOptions = get_opt(http_options, []), + ReqOptions = get_opt(req_options, []), + + {ok, _ReqId} = httpc:request(post, + {Url, Headers, ContentType, Body}, + HttpOptions, + [ {sync, false}, + {receiver, {?MODULE, post_result, []}} + | ReqOptions ]), + ok. + +post_result({_ReqId, {error, Reason}}) -> + report_error(Reason); +post_result({_ReqId, Result}) -> + {StatusLine, Headers, Body} = Result, + {_HttpVersion, StatusCode, ReasonPhrase} = StatusLine, + if StatusCode < 200; + StatusCode > 299 -> + ok = report_error([ {status_code, StatusCode}, + {reason_phrase, ReasonPhrase}, + {headers, Headers}, + {body, Body} ]), + ok; + true -> + ok + end. + +get_body(Els) -> + XmlElements = [ El || El <- Els, element(1, El) =:= xmlelement ], + case lists:keyfind("body", 2, XmlElements) of + false -> + no_body; + {xmlelement, "body", _, InnerEls} -> + case lists:keyfind(xmlcdata, 1, InnerEls) of + false -> + no_body; + {xmlcdata, Body} -> + {ok, Body} + end + end. + +get_opt(Opt) -> + get_opt(Opt, undefined). + +get_opt(Opt, Default) -> + gen_mod:get_module_opt(global, ?MODULE, Opt, Default). + +report_error(ReportArgs) -> + ok = error_logger:error_report([ mod_post_log_cannot_post | ReportArgs ]). + +%% Erlang now()-style timestamps are in UTC by definition, and we are +%% assuming ISO 8601 dates should be printed in UTC as well, so no +%% conversion necessary +%% +%% Example: +%% {1385,388790,334905} +%% -becomes- +%% 2013-11-25 14:13:10.334905Z +-spec to_iso_8601_date(erlang:timestamp()) -> string(). +to_iso_8601_date(Timestamp) when is_tuple(Timestamp) -> + {{Y, Mo, D}, {H, M, S}} = calendar:now_to_universal_time(Timestamp), + {_, _, US} = Timestamp, + lists:flatten(io_lib:format("~4.10.0B-~2.10.0B-~2.10.0B ~2.10.0B:~2.10.0B:~2.10.0B.~6.10.0BZ", + [Y, Mo, D, H, M, S, US])). From 04df0724f33eac0da7314d3ed811a6ca83e56a52 Mon Sep 17 00:00:00 2001 From: Ben Langfeld Date: Wed, 13 Aug 2014 12:54:37 -0300 Subject: [PATCH 2/7] Proper build setup --- mod_post_log/build.sh | 2 ++ mod_post_log/ebin/.keepme | 0 mod_post_log/{ => src}/mod_post_log.erl | 0 3 files changed, 2 insertions(+) create mode 100644 mod_post_log/build.sh create mode 100644 mod_post_log/ebin/.keepme rename mod_post_log/{ => src}/mod_post_log.erl (100%) diff --git a/mod_post_log/build.sh b/mod_post_log/build.sh new file mode 100644 index 0000000..f0a0cfa --- /dev/null +++ b/mod_post_log/build.sh @@ -0,0 +1,2 @@ +#!/bin/sh +erl -pa ../ejabberd-dev/ebin -pz ebin -make diff --git a/mod_post_log/ebin/.keepme b/mod_post_log/ebin/.keepme new file mode 100644 index 0000000..e69de29 diff --git a/mod_post_log/mod_post_log.erl b/mod_post_log/src/mod_post_log.erl similarity index 100% rename from mod_post_log/mod_post_log.erl rename to mod_post_log/src/mod_post_log.erl From d974cb618d3607958bba28422581be1bdb08a668 Mon Sep 17 00:00:00 2001 From: Ben Langfeld Date: Wed, 13 Aug 2014 13:14:41 -0300 Subject: [PATCH 3/7] Correct permissions --- mod_post_log/build.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 mod_post_log/build.sh diff --git a/mod_post_log/build.sh b/mod_post_log/build.sh old mode 100644 new mode 100755 From c28cb3b4c26545395904bbc337e86d8a4b7cb753 Mon Sep 17 00:00:00 2001 From: Ben Langfeld Date: Wed, 13 Aug 2014 13:35:32 -0300 Subject: [PATCH 4/7] Emakefile is required to build mods --- mod_post_log/Emakefile | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 mod_post_log/Emakefile diff --git a/mod_post_log/Emakefile b/mod_post_log/Emakefile new file mode 100644 index 0000000..60bb572 --- /dev/null +++ b/mod_post_log/Emakefile @@ -0,0 +1,2 @@ +{'../ejabberd-dev/src/gen_mod', [{outdir, "../ejabberd-dev/ebin"},{i,"../ejabberd-dev/include"}]}. +{'src/mod_post_log', [{outdir, "ebin"},{i,"../ejabberd-dev/include"}]}. From d744d772887b113fe3eda3144ee87a8ac1e64db4 Mon Sep 17 00:00:00 2001 From: Tim Stewart Date: Tue, 21 Oct 2014 12:42:39 -0400 Subject: [PATCH 5/7] Remove unnecessary `Host' parameter --- mod_post_log/src/mod_post_log.erl | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/mod_post_log/src/mod_post_log.erl b/mod_post_log/src/mod_post_log.erl index c28def2..3f79ca9 100644 --- a/mod_post_log/src/mod_post_log.erl +++ b/mod_post_log/src/mod_post_log.erl @@ -37,34 +37,34 @@ stop(Host) -> ok. log_user_send(From, To, Packet) -> - ok = log_packet(From, To, Packet, From#jid.lserver). + ok = log_packet(From, To, Packet). -log_packet(From, To, {xmlelement, "message", _Attrs, _Els} = Packet, Host) -> - ok = log_message(From, To, Packet, Host); +log_packet(From, To, {xmlelement, "message", _Attrs, _Els} = Packet) -> + ok = log_message(From, To, Packet); -log_packet(_From, _To, {xmlelement, _Name, _Attrs, _Els}, _Host) -> +log_packet(_From, _To, {xmlelement, _Name, _Attrs, _Els}) -> ok. -log_message(From, To, {xmlelement, _Name, Attrs, _Els} = Packet, Host) -> +log_message(From, To, {xmlelement, _Name, Attrs, _Els} = Packet) -> Type = lists:keyfind("type", 1, Attrs), - log_message_filter(Type, From, To, Packet, Host). + log_message_filter(Type, From, To, Packet). -log_message_filter({"type", Type}, From, To, Packet, Host) +log_message_filter({"type", Type}, From, To, Packet) when Type =:= "chat"; Type =:= "groupchat" -> - log_chat(From, To, Packet, Host); -log_message_filter(_Other, _From, _To, _Packet, _Host) -> + log_chat(From, To, Packet); +log_message_filter(_Other, _From, _To, _Packet) -> ok. -log_chat(From, To, {xmlelement, _Name, _Attrs, Els} = Packet, Host) -> +log_chat(From, To, {xmlelement, _Name, _Attrs, Els} = Packet) -> case get_body(Els) of no_body -> ok; {ok, _Body} -> - log_chat_with_body(From, To, Packet, Host) + log_chat_with_body(From, To, Packet) end. -log_chat_with_body(_From, _To, Packet, _Host) -> +log_chat_with_body(_From, _To, Packet) -> post_xml(xml:element_to_binary(Packet)). post_xml(Xml) -> From d265730f31fb6be648154c031085a0f8f3de8772 Mon Sep 17 00:00:00 2001 From: Tim Stewart Date: Tue, 21 Oct 2014 12:44:30 -0400 Subject: [PATCH 6/7] Repair error reporting in {error, Reason} case --- mod_post_log/src/mod_post_log.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod_post_log/src/mod_post_log.erl b/mod_post_log/src/mod_post_log.erl index 3f79ca9..cae8ac8 100644 --- a/mod_post_log/src/mod_post_log.erl +++ b/mod_post_log/src/mod_post_log.erl @@ -89,7 +89,7 @@ post_xml(Xml) -> ok. post_result({_ReqId, {error, Reason}}) -> - report_error(Reason); + report_error([ {error, Reason } ]); post_result({_ReqId, Result}) -> {StatusLine, Headers, Body} = Result, {_HttpVersion, StatusCode, ReasonPhrase} = StatusLine, From 0df57edeef446b639a4afd576f8971c768af2e88 Mon Sep 17 00:00:00 2001 From: Tim Stewart Date: Tue, 21 Oct 2014 12:45:21 -0400 Subject: [PATCH 7/7] Include to/from info in POSTed headers --- mod_post_log/src/mod_post_log.erl | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/mod_post_log/src/mod_post_log.erl b/mod_post_log/src/mod_post_log.erl index cae8ac8..cfafffb 100644 --- a/mod_post_log/src/mod_post_log.erl +++ b/mod_post_log/src/mod_post_log.erl @@ -64,18 +64,22 @@ log_chat(From, To, {xmlelement, _Name, _Attrs, Els} = Packet) -> log_chat_with_body(From, To, Packet) end. -log_chat_with_body(_From, _To, Packet) -> - post_xml(xml:element_to_binary(Packet)). +log_chat_with_body(From, To, Packet) -> + post_xml(From, To, xml:element_to_binary(Packet)). -post_xml(Xml) -> +post_xml(From, To, Xml) -> Ts = to_iso_8601_date(os:timestamp()), Body = Xml, Url = get_opt(url), TsHeader = get_opt(ts_header, "X-Message-Timestamp"), - Headers = [ {TsHeader, Ts} | get_opt(headers, []) ], - io:format("Headers: ~p\n", [Headers]), + FromHeader = get_opt(from_header, "X-Message-From"), + ToHeader = get_opt(to_header, "X-Message-To"), + Headers = [ {TsHeader, Ts}, + {FromHeader, format_jid(From)}, + {ToHeader, format_jid(To)} + | get_opt(headers, []) ], ContentType = get_opt(content_type, "text/xml"), HttpOptions = get_opt(http_options, []), ReqOptions = get_opt(req_options, []), @@ -127,6 +131,16 @@ get_opt(Opt, Default) -> report_error(ReportArgs) -> ok = error_logger:error_report([ mod_post_log_cannot_post | ReportArgs ]). +format_jid(#jid{luser = User, lserver = Server, lresource = Resource}) + when Resource =:= undefined; + Resource =:= ""; + Resource =:= <<"">> -> + %% The guard above feels defensive, but I don't yet know the full + %% set of ways that ejabberd will represent an empty resource + io_lib:format("~s@~s", [User, Server]); +format_jid(#jid{luser = User, lserver = Server, lresource = Resource}) -> + io_lib:format("~s@~s/~s", [User, Server, Resource]). + %% Erlang now()-style timestamps are in UTC by definition, and we are %% assuming ISO 8601 dates should be printed in UTC as well, so no %% conversion necessary