I have a working Ejabberd server (version 2.1.9) and my client application running just fine, but I wish to modify the way the application's XMPP client connects to Ejabberd in order to reduce the number of requests/responses between them, because its for a mobile environment and I wish to reduce the initial connection time.
I've looked up the XMPP protocol specification (RFC 6120) and some protocol extensions (XEPs), namely XEP-0305 Quickstart, but the protocol itself doesn't specify single request sign in and the Quickstart extension although aims to reduce the number of requests isn't enough for the time reduction I'm looking for.
After searching and not finding any solution I've started to modify both client and server and wish to accomplish the following for now as a proof of concept:
//Client Request
<?xml version='1.0'?>
<stream:stream ... user='user' pass='pass'>
//Server Response
<?xml version='1.0'?>
<stream:stream ... success='1'>
I've managed to modify my client accordingly and the Ejabberd server, and it seems they connect successfully, but any request the client makes after establishing the session doesn't get a response by the server. I've used Wireshark to check the TCP connection client and server side: client side its open and the request is sent, and on the server side is also open and the request is received, but when I try to send the response it is not sent.
I've modified ONLY the file ejabberd_c2s.erl and the changes are the following:
//init function
...
%% changed the first state of the fsm to point to quickstart
%% {ok, wait_for_stream, #state{socket = Socket1,
{ok, wait_for_quickstart, #state{socket = Socket1,
...
//wait_for_quickstart function
...
case resource_conflict_action(U, StateData#state.server, R) of
closenew ->
send_header(StateData, Server, "1.0", DefaultLang, "0"),
send_trailer(StateData),
{stop, normal, StateData};
{accept_resource, R2} ->
JID = jlib:make_jid(U, StateData#state.server, R2),
allow = acl:match_rule(Server,c2s,JID),
case ejabberd_auth:check_password(U, Server, P) of
true ->
send_header(StateData, Server, "1.0", DefaultLang, "1"),
change_shaper(StateData, JID),
{Fs, Ts} = ejabberd_hooks:run_fold(
roster_get_subscription_lists,
StateData#state.server,
{[], []},
[U, StateData#state.server]),
LJID = jlib:jid_tolower(jlib:jid_remove_resource(JID)),
Fs1 = [LJID | Fs],
Ts1 = [LJID | Ts],
PrivList =
ejabberd_hooks:run_fold(
privacy_get_user_list,
StateData#state.server,
#userlist{},
[U, StateData#state.server]),
SID = {now(), self()},
Conn = get_conn_type(StateData),
Info = [{ip, StateData#state.ip},
{conn, Conn},
{auth_module, StateData#state.auth_module}],
ejabberd_sm:open_session(SID, U, StateData#state.server, R, Info),
NewStateData =
StateData#state{
user = U,
resource = R2,
jid = JID,
sid = SID,
conn = Conn,
auth_module = ejabberd_auth_internal,
authenticated = true,
pres_f = ?SETS:from_list(Fs1),
pres_t = ?SETS:from_list(Ts1),
privacy_list = PrivList},
fsm_next_state_pack(session_established,
NewStateData);
_ ->
%%auth fail
end
end.
Just to clarify: the initial client authentication request and server response are being transmitted just fine, subsequent requests are also being transmitted but there is no response to them.
I'm I overlooking something?
Thanks in advance
#Nuno-Freitas Indeed that was what was failing, thanks for your helpful insight.
I added the code:
R1 = xml:get_attr_s("r",Attrs),
R = case jlib:resourceprep(R1) of
error -> error;
"" ->
lists:concat([randoms:get_string() | tuple_to_list(now())]);
Resource -> Resource
end,
That made the server respond to my requests, but there was other thing amiss: the <presence/> tag was breaking on server because the #state.lang was not defined, so I had to define it in the wait_for_quickstart function and now I have a single sign in XMPP client server working proof of concept.
Related
I am trying to create websocket server and client in my iOS app, which i successfully managed to do with the help of sample implementation here. (https://github.com/apple/swift-nio/tree/master/Sources/NIOWebSocketServer) - so current working situation is, i run the websocket server when app launches and then I load the client in a webview which can connect to it.
Now my problem is I want my server to secured websocket server (Basically connect to the websocket server from a HTTPS html page)
I am new to network programming and Swift-nio documentation is lacking to say the least. As far as I understand I could use (https://github.com/apple/swift-nio-transport-services)
I found this thread which is exactly what I need - https://github.com/apple/swift-nio-transport-services/issues/39 - I could disable the TLS authentication as I dont care in my usecase as long as I could get the websocket connected.
So my question is how to I extend my client (https://github.com/apple/swift-nio/tree/master/Sources/NIOWebSocketClient) and server (https://github.com/apple/swift-nio/tree/master/Sources/NIOWebSocketServer) to use swift-nio-transport-service.
I could add the NIOSSLContext and stuff but I think I need to add the EventLoopGroup and new bootstrap methods. I know the answers is right there.... but I just cannot seem to pinpoint it.
Any pointer would be appreciated.
Thanks.
To translate a simple NIO Server to a NIOTransportServices one, you need to make the following changes:
Add a dependency on NIOTransportServices to your server.
Change MultiThreadedEventLoopGroup to NIOTSEventLoopGroup.
Change ClientBootstrap to NIOTSConnectionBootstrap.
Change ServerBootstrap to NIOTSListenerBootstrap.
Build and run your code.
Some ChannelOptions don’t work in NIOTransportServices, but most do: the easiest way to confirm that things are behaving properly is to quickly test the common flow.
This doesn’t add any extra functionality to your application, but it does give you the same functionality using the iOS APIs.
To add TLS to either NIOTSConnectionBootstrap or NIOTSListenerBootstrap, you use the .tlsOptions function. For example:
NIOTSListenerBootstrap(group: group)
.tlsOptions(myTLSOptions())
Configuring a NWProtocolTLS.Options is a somewhat tricky thing to do. You need to obtain a SecIdentity, which requires interacting with the keychain. Quinn has discussed this somewhat here.
Once you have a SecIdentity, you can use it like so:
func myTLSOptions() -> NWProtocolTLS.Options {
let options = NWProtocolTLS.Options()
let yourSecIdentity = // you have to implement something here
sec_protocol_options_set_local_identity(options.securityProtocolOptions, sec_identity_create(yourSecIdentity)
return options
}
Once you have that code written, everything should go smoothly!
As an extension, if you wanted to secure a NIO server on Linux, you can do so using swift-nio-ssl. This has separate configuration as the keychain APIs are not available, and so you do a lot more loading of keys and certificates from files.
I needed a secure websocket without using SecIdentity or NIOTransportServices, so based on #Lukasa's hint about swift-nio-ssl I cobbled together an example that appears to work correctly.
I dunno if it's correct, but I'm putting it here in case someone else can benefit. Error-handling and aborting when the try's fail is left out for brevity.
let configuration = TLSConfiguration.forServer(certificateChain: try! NIOSSLCertificate.fromPEMFile("/path/to/your/tlsCert.pem").map { .certificate($0) }, privateKey: .file("/path/to/your/tlsKey.pem"))
let sslContext = try! NIOSSLContext(configuration: configuration)
let upgradePipelineHandler: (Channel, HTTPRequestHead) -> EventLoopFuture<Void> = { channel, req in
WebSocket.server(on: channel) { ws in
ws.send("You have connected to WebSocket")
ws.onText { ws, string in
print("Received text: \(string)")
}
ws.onBinary { ws, buffer in
// We don't accept any Binary data
}
ws.onClose.whenSuccess { value in
print("onClose")
}
}
}
self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 2)
let port: Int = 5759
let promise = self.eventLoopGroup!.next().makePromise(of: String.self)
_ = try? ServerBootstrap(group: self.eventLoopGroup!)
// Specify backlog and enable SO_REUSEADDR for the server itself
.serverChannelOption(ChannelOptions.backlog, value: 256)
.serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
.childChannelInitializer { channel in
let handler = NIOSSLServerHandler(context: sslContext)
_ = channel.pipeline.addHandler(handler)
let webSocket = NIOWebSocketServerUpgrader(
shouldUpgrade: { channel, req in
return channel.eventLoop.makeSucceededFuture([:])
},
upgradePipelineHandler: upgradePipelineHandler
)
return channel.pipeline.configureHTTPServerPipeline(
withServerUpgrade: (
upgraders: [webSocket],
completionHandler: { ctx in
// complete
})
)
}.bind(host: "0.0.0.0", port: port).wait()
_ = try! promise.futureResult.wait()
try! server.close(mode: .all).wait()
I currently using rabbit_client to send queue message from our ejabberd. This message is triggered using ejabberd hook. Can successfully compiled and installed without any issue.
I keep on getting the same error when the try to create the connection. Appreciated if someone could give some clue on the issue.
Following is the code used to connect and publish the message.
{ok, Connection} =
amqp_connection:start(#amqp_params_network{host = "192.168.xx.xx", username = "username", password= "password"}),
{ok, Channel} = amqp_connection:open_channel(Connection),
amqp_channel:call(Channel, #'queue.declare'{queue = <<"hello">>}),
amqp_channel:cast(Channel,
#'basic.publish'{
exchange = <<"">>,
routing_key = <<"hello">>},
#amqp_msg{payload = <<"Hello World!">>}),
io:format(" [x] Sent 'Hello World!'~n"),
ok = amqp_channel:close(Channel),
ok = amqp_connection:close(Connection),
ok.
Error
{undef,[{amqp_connection,start,[{amqp_params_network,"username","password",<<"/">>,"192.168.xx.xx",undefined,0,0,10,infinity,none,[#Fun,#Fun],[],[]}],[]},{mod_mymod,send_internal_notice,4,[{file,"src/mod_mymod.erl"},{line,67}]},{ejabberd_hooks,safe_apply,3,[{file,"src/ejabberd_hooks.erl"},{line,382}]},{ejabberd_hooks,run1,3,[{file,"src/ejabberd_hooks.erl"},{line,329}]},{ejabberd_c2s,presence_update,3,[{file,"src/ejabberd_c2s.erl"},{line,2068}]},{ejabberd_c2s,session_established2,2,[{file,"src/ejabberd_c2s.erl"},{line,1249}]},{p1_fsm,handle_msg,10,[{file,"src/p1_fsm.erl"},{line,582}]},{proc_lib,init_p_do_apply,3,[{file,"proc_lib.erl"},{line,247}]}]}
{undef,[{amqp_connection,start,... means that the amqp_connection:start/1 function was not defined (undef). This usually means that the amqp_connection module isn't in the Erlang code path.
I'm a beginner on Erlang-Ejabberd development and i want to create a module on top of Ejabberd 15.07. So i got some code as example in many posts and some tutorials to start developing my own module. Now i came out with this code to get started.
-module(mod_test)
-behaviour(gen_mod).
-ifndef(LAGER).
-define(LAGER, 1).
-endif.
-define(NS_SEEN, <<"jabber:iq:seen">>).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("jlib.hrl").
-export([start/2, stop/1, process_sm_iq/3]).
start(Host, Opt) ->
?INFO_MSG(" mod_test starting ", []),
IQDisc = gen_mod:get_opt(iqdisc, Opt, fun gen_iq_handler:check_type/1, one_queue),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host, ?NS_SEEN, ?MODULE, process_sm_iq, IQDisc),
ok.
stop(Host) ->
?INFO_MSG(" mod_test2 stopping ", []),
gen_iq_handler:remove_iq_handler(ejabberd_sm, Host, ?NS_SEEN),
ok.
process_sm_iq(_From, _To, #iq{type = get, xmlns = ?NS_SEEN} = IQ) ->
?INFO_MSG(" Processing IQ Get query:~n ~p ", [IQ]),
IQ#iq{type = result, sub_el = [{xmlelement, <<"value">>, [], [{xmlcdata, <<"Hello World of Testing. ">>}]}]}.
So here, using Strophe.js i send a custom IQ stanza with this function.
var iq = $iq( {type: 'get', id : 'id123', from : 'renato#localhost'}).c('query', {xmlns : 'jabber:iq:seen'});
connection.sendIQ(iq, onResult);
And it comes out with a Stanza like this.
<iq type="get" id="id123" from="renato#localhost" xmlns="jabber:client">
<query xmlns="jabber:iq:seen" >
</iq>
Then i get disconnected and get no result from the server as expected. This is what i got in the log file.
2016-05-11 17:47:28.965 [info]<0.486.0>#ejabberd_listener:accept:299 (#Port<0.3939>) Accepted connection 127.0.0.1:49120 -> 127.0.0.1:5280
2016-05-11 17:47:28.966 [info]<0.507.0>#ejabberd_http:init:157 started: {gen_tcp,#Port<0.3939>}
2016-05-11 17:47:29.511 [info]<0.509.0>#ejabberd_c2s:wait_for_sasl_response:932 ({socket_state,ejabberd_http_bind,{http_bind,<0.508.0>, {{127,0,0,1},49120}},ejabberd_http_bind}) Accepted authentication for renato by undefined from 127.0.0.1
2016-05-11 17:47:30.150 [info]<0.509.0>#ejabberd_c2s:wait_for_session:1120 ({socket_state,ejabberd_http_bind,{http_bind,<0.508.0>, {{127,0,0,1},49120}},ejabberd_http_bind}) Opened session for renato#localhost/32685205291462981649937971
2016-05-11 17:47:31.451 [info]<0.486.0>#ejabberd_listener:accept:299 (#Port<0.3948>) Accepted connection 127.0.0.1:49122 -> 127.0.0.1:5280
2016-05-11 17:47:31.452 [info]<0.511.0>#ejabberd_http:init:157 started: {gen_tcp,#Port<0.3948>}
2016-05-11 17:47:31.453 [info]<0.390.0>#mod_test2:process_sm_iq:45 Processing IQ Get query:
{iq,<<"id123">>,get,<<"jabber:iq:seen">>,<<>>,{xmlel,<<"query">>, [{<<"xmlns">>,<<"jabber:iq:seen">>},{<<"querytype">>,<<"seen">>}],[]}}
2016-05-11 17:49:01.556 [info]<0.508.0>#ejabberd_http_bind:handle_info:522 Session timeout. Closing the HTTP bind session: <<"1199a026fb06de7e7c728425587b09a0b9c81433">>
2016-05-11 17:49:01.556 [info]<0.509.0>#ejabberd_c2s:terminate:1842 ({socket_state,ejabberd_http_bind,{http_bind,<0.508.0>, {{127,0,0,1},49120}},ejabberd_http_bind}) Close session for renato#localhost/32685205291462981649937971
I'm running ejabberd 15.07. I installed the run package and compiled the module using these commands.
erlc -DNO_EXT_LIB -I lib/ejabberd-15.07/include -pz lib/ejabberd-15.07/lib mod_test.erl
After that, i copied the file to ebin/ directory then i started the ejabberd server and everything else worked fine. Someone can help me out and explain me what i have to, and show me if i have to change the code, configuration file or maybe use a newer version. I'm using this version for months. Thanks...
I am using the embedded YAWS web-server with yaws cookie session.
I first authenticate the user with user-name & password to allow him the entry to the web pages.
My problem is if the user directly opens the internal web page instead of login page he can view it even without the authentication. How to restrict the user that he must have the cookie to view any internal web page.
In chapter 7 of the Yaws PDF documentation there's an example that does exactly what you're asking about. It uses arg rewriting to redirect unauthenticated requests to a login page.
First we configure an arg rewriter module named myapp in the server portion of yaws.conf:
arg_rewrite_mod = myapp
The myapp:arg_rewrite/1 function checks the incoming request via the #arg{} record to look for a specific cookie, and if not found and the request isn't trying to retrieve one of the three resources returned from the login_pages/0 function, it calls do_rewrite/1 to rewrite the request to deliver a login.yaws page instead:
arg_rewrite(Arg) ->
OurCookieName = "myapp_sid"
case check_cookie(Arg, OurCookieName) of
{error, _} ->
do_rewrite(Arg);
{ok, _Session} ->
%% return Arg untouched
Arg
end.
%% these pages must be shippable without a good cookie
login_pages() ->
["/banner.gif", "/login.yaws", "/post_login.yaws"].
do_rewrite(Arg) ->
Req = Arg#arg.req,
{abs_path, Path} = Req#http_request.path,
case lists:member(Path, login_pages()) of
true ->
Arg;
false ->
Arg#arg{req = Req#http_request{path = {abs_path, "/login.yaws"}},
state = Path}
end.
Please see the Yaws PDF documentation for further details.
Quoting from the WebSharper 2.5 alpah docs the remoting component assumes that:
RPC-callable methods are safe to call from the web by an unauthenticated client.
Is there anyway to secure remote calls so they can only be called from an authenticated client?
One of the samples in the WebSharper website is a chat application that seems to do just that by providing a Login method that returns an authentication token, which is then required to call the other functions:
[<Rpc>]
let Login (user: string) : Option<Auth.Token> =
let s = State.Get()
if s.Users.ContainsKey user then
None
else
// (snip)
user |> Auth.Generate |> Some
[<Rpc>]
let Poll (auth: Auth.Token) (time: int) =
// (snip)
The full chat sample can be found here: http://www.websharper.com/samples/Chat
Just been playing with this myself. Turns out if you're using Forms Authentication you can read the current HTTPContext from inside RPC methods so you can do something like this:
[<Rpc>]
let protectedMethod () =
match IntelliFactory.WebSharper.Sitelets.UserSession.GetLoggedInUser() with
| Some(username) ->
// User is authenticated... do stuff
()
| None -> failwith "Authentication failed"