erlang: receive response from ssl - ssl

I added a new module (-behavior(gen_mod)) inside ejabberd and I was able to open a ssl connection to a server and send a message directly using the ssl connection(ssl:send() function). However, I am unable to receive response after this.
I tried using the receive option in erlang to catch messages, and still no luck.
I tried changing the module to be a stateful module by using the gen_server behavior and I still don't observe any handle_info calls with any message.
I could not get any response using ssl:recv/2 either.
Am I missing something? How do I asynchronously receive the response from the ssl socket in erlang?
Any pointers would be really appreciated. Thank you!
code: Adding only relevant parts of the code.
sendPacketToServer() ->
case ssl:connect(Gateway, Port, Options, ?SSL_TIMEOUT) of
{ok, Socket} ->
ssl:controlling_process(Socket, self()),
Packet = .....,
Result = ssl:send(Socket, Packet),
receiveMessage(),
ssl:close(Socket),
?INFO_MSG("~n~n~n~n Successfully sent payload to the server, result: ~p for the user: ~p", [Result, Username]);
{error, Reason} = Err ->
?ERROR_MSG("Unable to connect to the server: ~s for the user: ~p", [ssl:format_error(Reason), Username]),
Err
end
...
....
receiveMessage() ->
receive ->
{ssl, Msg, Data} -> % incoming msg from SSL, send it to process
......
{ssl_closed, Msg} -> % incoming msg from SSL, send it to process
.....
{ssl_error, Msg} -> % incoming msg from SSL, send it to process
.....
{ssl_passive, Msg} -> % incoming msg from SSL, send it to process
....
end.
Added the following code for gen_server: (when doing the following, i do not close the socket immediately, but it still does not work).
start(Host, Opts) ->
gen_mod:start_child(?MODULE, Host, Opts).
stop(Host) ->
gen_mod:stop_child(?MODULE, Host).
init([ServerHost|_]) ->
Opts = gen_mod:get_module_opts(ServerHost, ?MODULE),
start(ServerHost, Opts),
{ok, #state{host = ServerHost}}.
handle_call(Request, From, State) ->
?WARNING_MSG("Unexpected call from ~p: ~p", [From, Request]),
{noreply, State}.
handle_cast(Msg, State) ->
?WARNING_MSG("Unexpected cast: ~p", [Msg]),
{noreply, State}.
handle_info(Info, State) ->
?WARNING_MSG("Unexpected info: ~p", [Info]),
{noreply, State}.
terminate(_Reason, State) ->
ok.
code_change(_OldVsn, State, _Extra) -> {ok, State}.

I was able to overcome the problem by using a gen_server module, since it has explicit callbacks for any messages.

Related

BizTalk receiving from RabbitMQ

I'm new to RabbitMQ but I have now installed onto a Windows server and have a couple of demo console apps (C#) that happily write to a read from a queue.
The following code works to pull messages from a queue called "RabbitPoCQueue_2" on the local server:
string queueName = "RabbitPoCQueue_2";
var factory = new ConnectionFactory();
bool keepGoing = true;
factory.HostName = "127.0.0.1";
try
{
using (var connection = factory.CreateConnection())
using (var channel = connection.CreateModel())
{
const bool durable = false;
channel.QueueDeclare(queueName, durable, false, false, null);
System.Console.WriteLine(" [*] Waiting for messages.");
while (keepGoing)
{
var consumer = new EventingBasicConsumer(channel);
consumer.Received += (model, ea) =>
{
var body = ea.Body;
var message = Encoding.UTF8.GetString(body);
System.Console.WriteLine(" [x] Received {0}", message);
};
channel.BasicConsume(queue: queueName,
autoAck: true,
consumer: consumer);
channel.BasicGet(queue: queueName, autoAck: true);
System.Console.WriteLine("Press Y to continue or any other key to exit");
keepGoing = System.Console.ReadKey().Key == ConsoleKey.Y;
}
}
}
I now need to configure a BizTalk (2016 FP3 CU5) receive location to do the same. I have ensured I've stopped the console receiver and that I have messages sat on the queue for BizTalk to collect.
I followed the article https://social.technet.microsoft.com/wiki/contents/articles/7401.biztalk-server-and-rabbitmq.aspx
Problem is, when I start the receive location, I get no errors but nothing is received.
The config for the WCF receive location can be seen below:
and here:
and here's a pic from the RabbitMQ management console showing messages sat on the queue:
When I look in the RabbitMQ log file, I see 2 rows on starting the receive location. I see 3 rows when starting the .Net console app (using RabbitMQ API), as shown below - first 2 rows are from BizTalk, last 3 from the console app:
2019-08-28 16:17:45.693 [info] <0.13361.2> connection <0.13361.2> ([::1]:16807 -> [::1]:5672): user 'guest' authenticated and granted access to vhost '/' ** Start of Receive location
2019-08-28 16:19:57.958 [info] <0.13452.2> accepting AMQP connection <0.13452.2> (127.0.0.1:17173 -> 127.0.0.1:5672)
2019-08-28 16:19:58.026 [info] <0.13452.2> connection <0.13452.2> (127.0.0.1:17173 -> 127.0.0.1:5672): user 'guest' authenticated and granted access to vhost '/' ** Receive from command line
2019-08-28 18:56:26.267 [info] <0.13452.2> closing AMQP connection <0.13452.2> (127.0.0.1:17173 -> 127.0.0.1:5672, vhost: '/', user: 'guest')
2019-08-28 18:56:39.815 [info] <0.17923.2> accepting AMQP connection <0.17923.2> (127.0.0.1:41103 -> 127.0.0.1:5672)
Can anyone spot where I went wrong?

How can disable SSL peer verification in http client (httpc) in erlang

I sent a restfull api request by http client, but i get below error:
{error,{failed_connect,[{to_address,{"https://example.com",443}},
{inet,[inet],{tls_alert,"record overflow"}}]}}
i found that SSL peer verification made this problem. how can i disable it?
my code:
test() ->
inets:start(),
ssl:start(),
RequestBody = "",
Request = {"https://example.com", [{"X-API-CODE",""}, {"Accept","application/json"}, {"access-token",""}], "application/json", RequestBody},
{ok, {_, _, ResponseBody}} = httpc:request(post, Request, [], []),
io:format("~st", [ResponseBody]).
Although disabling verification is not a good idea, but it's possible by using {ssl, [{verify, verify_none}]} in the options.
Example:
httpc:request(get, {"https://revoked.badssl.com/", []}, [{ssl, [{verify, verify_none}]}], []).

Elixir error handling pipe options

The following code is an invalid changeset which errors, however it took me a long time to find the cause because the error message was not originally being matched and logged.
I added a case statement to the end of the pipe, is this the best way to pickup errors in pipes?
User.changeset(%User{}, %{username: "username_is_test", password: "test", password_confirmation: "test", email: "test#test.com"})
|> Repo.insert
|> case do
{:ok, result} -> IO.puts("result")
{:error, error} -> IO.inspect error
end
Pipelines and error tuples don't work very well together. You can handle an error at the end of a pipeline with a case as you have, but it only works at the last stage.
For operations returning error tuples, I prefer to use with/else syntax:
with changeset <- User.changeset(%User{}, %{username: "username_is_test", password: "test", password_confirmation: "test", email: "test#test.com"})
{:ok, result} <- Repo.insert(changeset) do
IO.puts("result")
else
{:error, error} -> IO.inspect error
end
You can add as many failable operations as required in the with block, and handle all the error cases with pattern matching in the else block.
https://hexdocs.pm/elixir/Kernel.SpecialForms.html#with/1
You have a couple options. If you don't want to explicitly handle the error conditions, you should use Repo.insert! instead. At least this will raise an exception if the changeset is not valid.
Otherwise, you should be using a case handle handling the {:error, changeset} by checking the changeset.action in your template.
In more complicated pipelines that can error part way through, I've started using the with special form.
with result when not is_nil(result) <- fun1,
{:ok, result} <- fun2(result),
{:ok, result} <- fun3(result) do
success_handling(result)
else
nil -> # handle first failure
{:error, error} -> # handle other errors
_ -> # catch all failure
end

How to specify custom host for connection in RabbitMQ?

How can I specify my custom host in send.exs file if my app which has receive.exs is hosted somewhere? I have one elixir app with send.exs and another app with receive.exs which is Phoenix app and is hosted.
send.exs
{:ok, connection} = AMQP.Connection.open
{:ok, channel} = AMQP.Channel.open(connection)
AMQP.Queue.declare(channel, "hello")
AMQP.Basic.publish(channel, "", "hello", msg)
IO.puts " [x] Sent everything"
AMQP.Connection.close(connection)
receive.exs
...
{:ok, connection} = AMQP.Connection.open
{:ok, channel} = AMQP.Channel.open(connection)
AMQP.Queue.declare(channel, "hello")
AMQP.Basic.consume(channel, "hello", nil, no_ack: true)
IO.puts " [*] Waiting for messages. To exit press CTRL+C, CTRL+C"
...
There are a few different forms of Connection.open.
The first, which you're using, takes no arguments, and uses some default settings (localhost, "guest" username, "guest" password, etc.).
The second takes a Keyword list of options, including host, port, virtual_host, and others. You may use Connection.open(host: your_host). Any settings which you don't provide will use the default setting.
The third form takes a well-formed RabbitMQ URI as a String, which is formed using a combination of host, port, username, password, and virtual host. Note that some of these values are optional in the URI.

Get client certificate without validation using Erlang

I'm relatively new to Erlang and want to write a small server that uses the client certificate to identify the client. The client should be able to use any public/private key pair without it being part of a certificate chain. I looked at the SSL examples from the OTP source code and used make_certs.erl to create the key pairs. I can connect using the created client certificate, but not using a self signed certificate.
How can I get the client certificate without validating it?
My sample code is currently:
-module(simple_server).
-export([start/0]).
keep_alive() ->
receive
Any -> io:format("Listening socket: ~p~n",[Any])
end.
start() ->
ssl:start(),
spawn(fun() ->
start_parallel_server(3333),
keep_alive()
end).
start_parallel_server(Port) ->
case ssl:listen(Port, [
binary,
{packet, 0},
{reuseaddr, true},
{active, true},
{certfile,"../etc/server/cert.pem"},
{keyfile,"../etc/server/key.pem"},
{cacertfile,"../etc/server/cacerts.pem"},
{verify,verify_peer},
{fail_if_no_peer_cert,true}
]) of
{ok,Listen} ->
spawn(fun() -> par_connect(Listen) end);
{error,Reason} ->
io:format("error ~p~n",[Reason])
end.
par_connect(Listen) ->
case ssl:transport_accept(Listen) of
{ok,Socket} ->
spawn(fun() -> par_connect(Listen) end),
ssl:ssl_accept(Socket),
print_cert(Socket),
get_request(Socket,[]);
{error,Reason} ->
io:format("Listening stopped (~p)~n",[Reason])
end.
print_cert(Socket) ->
case ssl:peercert(Socket) of
{ok,Cert} ->
io:format("Certificate: ~p~n",[Cert]);
{error,Reason} ->
io:format("Certificate error ~p~n",[Reason])
end.
get_request(Socket,L) ->
receive
{ssl, Socket, Bin} ->
io:format("Server received: ~p~n",[Bin]),
get_request(Socket,L);
{ssl_closed, Socket} ->
io:format("Socket did disconnect~n");
Reason ->
io:format("Client error: ~p~n",[Reason])
end.
You need to supply a custom path verification function to ssl:listen that allows self-signed certificates.
The verify_fun option (see the docs) lets you specify a function that is called when a certification verification error is encountered.
We can take the default implementation (given in the docs) and make sure the selfsigned_peer case returns success:
{verify_fun, {fun(_, {bad_cert, selfsigned_peer}, UserState) ->
{valid, UserState}; %% Allow self-signed certificates
(_,{bad_cert, _} = Reason, _) ->
{fail, Reason};
(_,{extension, _}, UserState) ->
{unknown, UserState};
(_, valid, UserState) ->
{valid, UserState};
(_, valid_peer, UserState) ->
{valid, UserState}
end, []}}