Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

integrate lxmppc into escalus #3

Merged
merged 36 commits into from
Sep 3, 2012
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
cb0c208
Initial commit
Nov 25, 2011
843d987
First test
Nov 25, 2011
7d9ca95
Added Travis support for maximum hipstership
Nov 25, 2011
a735de2
Basics working
Jan 5, 2012
1f158e4
Changed lxmppc_stanza:stream_start's API
Jan 11, 2012
7b333b5
Basic lxmppc:start/1 implementation
Jan 11, 2012
fc77e86
.gitignore
Jan 16, 2012
41a1805
API cleanup
Jan 16, 2012
79d6f29
Close socket on failed lxmppc:start
Feb 3, 2012
6de09d2
Updated copyright
Feb 8, 2012
227221c
Added DIGEST-MD5 authentication mechanism
erszcz Feb 10, 2012
9668f16
Export useful stuff from lxmppc_auth
Feb 16, 2012
87ac07d
send </stream:stream> on exit
Feb 21, 2012
6e64c0a
Removed ugly test
goj Feb 24, 2012
c0fdf05
Revamped lxmppc_socket_tcp module
goj Feb 24, 2012
4e21cc9
Don't crash on stopping already stopped clients
goj Feb 24, 2012
e2d0fbb
lxmppc_socket_tcp - ignore unknown messages
goj Feb 24, 2012
51564a9
Fix bug in lxmppc:connect/1
Mar 26, 2012
31781c4
Get rid of abstraction breaking in lxmppc.erl
Mar 26, 2012
27fea26
add anonymous authentication
michalwski Jun 15, 2012
0b6dbc7
Merge pull request #2 from michalwski/master
goj Jun 18, 2012
d1bfd79
add support for tls connection
michalwski Jun 21, 2012
5b5932d
Stream compression almost done
erszcz Jun 21, 2012
c46da4e
zlib finally works as it should
michalwski Jun 27, 2012
9660339
check if server supports starttls or compression
michalwski Jun 27, 2012
eca8263
Stream compression - final touches
erszcz Jun 28, 2012
5496556
Merge pull request #3 from lavrin/stream-compression
goj Jun 29, 2012
ed3faa6
Use esl/exml instead of goj/exml as dependency
erszcz Aug 24, 2012
86f775d
first steps to use bosh to communicate with jabber
michalwski Jul 23, 2012
95a66ee
next step, remember session id and use it in next calls
michalwski Aug 10, 2012
eb591a9
finish bosh layer communication
michalwski Aug 13, 2012
a7de513
improve body wraper
michalwski Aug 14, 2012
4624db8
Merge pull request #1 from michalwski/bosh
goj Aug 29, 2012
9fe26cd
Merge the lxmppc project into Escalus
goj Aug 30, 2012
f7305f8
Remove empty .gitmodules file
erszcz Aug 30, 2012
d4ce991
Clean up the API after exml record fixup
goj Aug 30, 2012
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ tags
deps
.eunit
rebar
test/*.beam
Empty file removed .gitmodules
Empty file.
9 changes: 9 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
language: erlang
branches:
only:
- master
notifications:
email: [email protected]
env:
- R14B03
- R14B02
13 changes: 13 additions & 0 deletions include/escalus.hrl
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,16 @@
jid :: binary(),
conn :: any()
}).

-record(jid, {
user :: binary(),
server :: binary(),
resource = <<"">> :: binary()}).

-record(transport, {
module :: atom(),
socket :: term(),
ssl :: boolean(),
compress :: {zlib, {Zin::zlib:zstream(), Zout::zlib:zstream()}}
| false,
rcv_pid :: pid()}).
Binary file added rebar
Binary file not shown.
4 changes: 3 additions & 1 deletion rebar.config
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@
{require_otp_vsn, "R1[45]"}.

{deps, [
{lxmppc, ".*", {git, "git://github.com/esl/lxmppc.git", {branch, "master"}}}
{exml, ".*", {git, "git://github.com/esl/exml.git", {branch, "master"}}},
{base16, ".*", {git, "git://github.com/goj/base16.git", {branch, "master"}}},
{lhttpc, ".*", {git, "git://github.com/esl/lhttpc.git", {branch, "master"}}}
]}.
2 changes: 1 addition & 1 deletion run_ct
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
#!/bin/bash
ct_run -config test/test.config -logdir logs -dir test -erl_args -pa $PWD/ebin $PWD/deps/lxmppc/ebin $PWD/deps/exml/ebin
ct_run -config test/test.config -logdir logs -dir test -erl_args -pa $PWD/ebin $PWD/deps/exml/ebin
72 changes: 72 additions & 0 deletions src/csvkv.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
%%%===================================================================
%%% @copyright (C) 2012, Erlang Solutions Ltd.
%%% @doc Module for parsing DIGEST-MD5 - style k1=v1,k2="v2" content
%%% @end
%%%===================================================================

-module(csvkv).
-export([parse/1, format/1]).

%%--------------------------------------------------------------------
%% Public API
%%--------------------------------------------------------------------

-spec parse(binary()) -> [{binary(), binary()}].
parse(Text) ->
do_parse(rev_lex(Text, []), []).

-spec format([{binary(), binary()}]) -> binary().
format(Items) ->
IOList = join([format_item(I) || I <- Items], $,),
list_to_binary(IOList).

%%--------------------------------------------------------------------
%% Implementation
%%--------------------------------------------------------------------

%% note: lexemes are in reverse order
do_parse([{v, V}, {k, K} | Rest], Acc) ->
do_parse(Rest, [{K, V} | Acc]);
do_parse([], Acc) ->
Acc.

%% note: returns lexemes in reverse order
rev_lex(<<>>, Acc) ->
Acc;
rev_lex(Text, Acc) ->
rev_lex_key(Text, Acc).

rev_lex_key(Text, Acc) ->
[Key, Rest] = binary:split(Text, <<"=">>),
rev_lex_value(Rest, [{k, Key} | Acc]).

rev_lex_value(<<"\"", Text/binary>>, Acc) ->
rev_lex_value_quoted(Text, <<>>, Acc);
rev_lex_value(Text, Acc) ->
rev_lex_value_bare(Text, Acc).

rev_lex_value_quoted(<<"\\\"",T/binary>>, Val, Acc) ->
rev_lex_value_quoted(T, <<Val/binary,"\"">>, Acc);
rev_lex_value_quoted(<<"\",",T/binary>>, Val, Acc) ->
rev_lex(T, [{v, Val} | Acc]);
rev_lex_value_quoted(<<"\"",T/binary>>, Val, Acc) ->
rev_lex(T, [{v, Val} | Acc]);
rev_lex_value_quoted(<<H,T/binary>>, Val, Acc) ->
rev_lex_value_quoted(T, <<Val/binary,H>>, Acc).

rev_lex_value_bare(Text, Acc) ->
case binary:split(Text, <<",">>) of
[Val, Rest] ->
rev_lex(Rest, [{v, Val} | Acc]);
[Val] ->
rev_lex(<<>>, [{v, Val} | Acc])
end.

join([], _) ->
[];
join(Items, Sep) ->
tl(lists:append([[Sep, I] || I <- Items])).

format_item({Key, Val}) ->
EscVal = re:replace(Val, "\"", "\\\"", [global]),
[Key, $=, $", EscVal, $"].
1 change: 0 additions & 1 deletion src/escalus.erl
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ suite() ->

init_per_suite(Config) ->
application:start(exml),
application:start(lxmppc),
Config.

end_per_suite(_Config) ->
Expand Down
127 changes: 127 additions & 0 deletions src/escalus_auth.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
%%%===================================================================
%%% @copyright (C) 2012, Erlang Solutions Ltd.
%%% @doc Module supporting various authentication mechanisms
%%% @end
%%%===================================================================
-module(escalus_auth).

%% Public APi
-export([auth_plain/2,
auth_digest_md5/2,
auth_sasl_anon/2]).

%% Useful helpers for writing own mechanisms
-export([get_challenge/2,
wait_for_success/2]).

-include_lib("exml/include/exml.hrl").

%%--------------------------------------------------------------------
%% Public API
%%--------------------------------------------------------------------

auth_plain(Conn, Props) ->
Username = get_property(username, Props),
Password = get_property(password, Props),
Payload = <<0:8,Username/binary,0:8,Password/binary>>,
Stanza = escalus_stanza:auth_stanza(<<"PLAIN">>, base64_cdata(Payload)),
ok = escalus_connection:send(Conn, Stanza),
wait_for_success(Username, Conn).

auth_digest_md5(Conn, Props) ->
ok = escalus_connection:send(Conn, escalus_stanza:auth_stanza(<<"DIGEST-MD5">>, [])),
ChallengeData = get_challenge(Conn, challenge1),
Response = md5_digest_response(ChallengeData, Props),
ResponseStanza1 = escalus_stanza:auth_response_stanza([Response]),
ok = escalus_connection:send(Conn, ResponseStanza1),
[{<<"rspauth">>, _}] = get_challenge(Conn, challenge2), %% TODO: validate
ResponseStanza2 = escalus_stanza:auth_response_stanza([]),
ok = escalus_connection:send(Conn, ResponseStanza2),
wait_for_success(get_property(username, Props), Conn).

auth_sasl_anon(Conn, Props) ->
Stanza = escalus_stanza:auth_stanza(<<"ANONYMOUS">>, []),
ok = escalus_connection:send(Conn, Stanza),
wait_for_success(get_property(username, Props), Conn).


%%--------------------------------------------------------------------
%% Helpers - implementation
%%--------------------------------------------------------------------

md5_digest_response(ChallengeData, Props) ->
%% Digest calculated via description at
%% http://web.archive.org/web/20050224191820/http://cataclysm.cx/wip/digest-hex_md5-crash.html
Username = get_property(username, Props),
Password = get_property(password, Props),
Server = get_property(server, Props),
Resource = get_property(resource, Props),
Nonce = get_property(<<"nonce">>, ChallengeData),
CNonce = base16:encode(crypto:rand_bytes(16)),
Realm = proplists:get_value(<<"realm">>, ChallengeData, <<>>),
QOP = get_property(<<"qop">>, ChallengeData),
NC = <<"00000001">>,
ServType = <<"xmpp">>,
DigestUri = <<"xmpp/", Server/binary>>,
FullJid = <<Username/binary, "@", Server/binary, "/", Resource/binary>>,

Y = crypto:md5([Username, $:, Realm, $:, Password]),
HA1 = hex_md5([Y, $:, Nonce, $:, CNonce, $:, FullJid]),
HA2 = hex_md5([<<"AUTHENTICATE:">>, DigestUri]),

%% Digest is the Z from the description above
Digest = hex_md5([HA1, $:, Nonce, $:, NC, $:, CNonce, $:, QOP, $:, HA2]),

base64_cdata(csvkv:format([
{<<"username">>, Username},
{<<"nonce">>, Nonce},
{<<"nc">>, NC},
{<<"cnonce">>, CNonce},
{<<"qop">>, QOP},
{<<"serv-type">>, ServType},
{<<"host">>, Server},
{<<"digest-uri">>, DigestUri},
{<<"response">>, Digest},
{<<"charset">>, <<"utf-8">>},
{<<"authzid">>, FullJid}
])).

hex_md5(Data) ->
base16:encode(crypto:md5(Data)).

%%--------------------------------------------------------------------
%% Helpers - actions
%%--------------------------------------------------------------------

get_challenge(Conn, Descr) ->
Challenge = escalus_connection:get_stanza(Conn, Descr),
case Challenge of
#xmlelement{name = <<"challenge">>, children=[CData]} ->
csvkv:parse(base64:decode(exml:unescape_cdata(CData)));
_ ->
throw({expected_challenge, got, Challenge})
end.

wait_for_success(Username, Conn) ->
AuthReply = escalus_connection:get_stanza(Conn, auth_reply),
case AuthReply#xmlelement.name of
<<"success">> ->
ok;
<<"failure">> ->
throw({auth_failed, Username, AuthReply})
end.

get_property(PropName, Proplist) ->
case lists:keyfind(PropName, 1, Proplist) of
{PropName, Value} ->
Value;
false ->
throw({missing_property, PropName})
end.

%%--------------------------------------------------------------------
%% Helpers
%%--------------------------------------------------------------------

base64_cdata(Payload) ->
#xmlcdata{content=base64:encode(Payload)}.
Loading