Getting Started with POP3 Using Boost Asio - c++

To help myself learn socket programming, I made a very simple SMTP client (see code below), using some code from the the Wt SMTP Library
As an exercise, I thought I would take a shot at implementing a POP3 client. Using the Wikipedia as a reference, it looked as if you must simply connect to the server on port 110 and send commands (just like SMTP connects on port 25 and sends commands).
However, I have so far been unable to even connect, and basically just timeout in the get_response() every time. I tried connecting to pop.gmail.com with no luck.
I don't believe that you need to send any user/password/other information when the connection is first established? I expected to receive something along the lines of +OK POP3 Server Ready once I connect, and I can then send the USER and PASS commands.
My question is: Am I missing something during the connection phase? Am I trying to connect to a bad host (gmail)? If so, is there a better host than pop.gmail.com with which I can test? Or am I going about this entirely wrong?
#include <string>
#include <sstream>
#include <iostream>
#include <iterator>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
int main()
{
boost::asio::io_service io_service;
tcp::socket socket(io_service);
tcp::resolver resolver(io_service);
tcp::resolver::query query("localhost", "25"); // Change this for POP3
tcp::resolver::iterator endpt_iter = resolver.resolve(query);
boost::system::error_code erc = boost::asio::error::host_not_found;
while(erc && endpt_iter != end)
{
socket.close();
socket.connect(*endpt_iter++, erc);
}
if(erc) { socket.close(); return }
std::cout << get_response(socket) << std::endl; // 220 <my-host-name> ESMTP Postfix
send(socket, "EHLO localhost\r\n");
std::cout << get_response(socket) << std::endl; // 250-<my-host-name>
return 0;
}
std::string get_response(tcp::socket& socket)
{
boost::asio::streambuf response;
for(;;)
{
std::string msg;
boost::asio::read_until(socket, response, "\r\n");
std::istream in(&response);
std::getline(in, msg);
return msg;
}
}
void send(tcp::socket& socket, std::string msg)
{
boost::asio::write(socket, boost::asio::buffer(msg));
}

Gmail requires SSL/TLS encryption on its POP3 and SMTP connections.
For POP3, you have to connect to port 995 and initiate an SSL/TLS handshake immediately upon connecting. Then you can read the server's initial +OK greeting from, and send commands to, the encrypted connection.
For SMTP, you have a couple of choices:
connect to port 465. Initiate an SSL/TLS handshake immediately upon connecting. Read the +OK greeting from, and send all commands to, the encrypted connection.
connect to port 587. Read the +OK greeting from, and send EHLO and STARTTLS commands to, the unencrypted connection. Initiate the SSL/TLS handshake. Send a new EHLO command, and other commands, to the encrypted connection.

Related

Why boost::beast::http::async_write sends one http request in multiple TLS segments?

I am working on one simple async Https client using boost::beast lib. Boost version is 1.69.0.
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> sslSocket_;
boost::asio::ip::tcp::resolver resolver_;
boost::optional<boost::beast::http::request<boost::beast::http::string_body>> request_;
boost::optional<boost::beast::http::response_parser<boost::beast::http::string_body>> responseParser_;
boost::beast::flat_buffer buffer_;
After Tcp connection and tls handshake, everything works fine. However, when we build one http request and send it with boost::beast::http::async_write:
void SimpleAsyncHttpsClient::buildHttpRequest(const std::string& uri, const std::string& payload)
{
request_.emplace();
request_->set(boost::beast::http::field::host, host_);
request_->set(boost::beast::http::field::accept, "*/*");
request_->set(boost::beast::http::field::content_type, "application/json");
request_->set(boost::beast::http::field::date, "Tue, 01 Jun 2021 07:27:04 GMT");
request_->set(boost::beast::http::field::connection, "close");
request_->target(uri);
request_->method(boost::beast::http::verb::post);
request_->body() = payload;
request_->prepare_payload();
}
void SimpleAsyncHttpsClient::sendHttp()
{
boost::beast::http::async_write(sslSocket_,
*request_,
std::bind(
&SimpleAsyncHttpClient::waitForResponse,
shared_from_this(),
std::placeholders::_1));
}
Strangely the such piece of code sends my http request in multiple segments,
wireshark screenshot1
wireshark screenshot2
The client firstly send "POST", then send the path and then the header lines in each segment.
I've also tried one sync https client and it is the same behavior. However, if we use boost::asio::async_write API we do not have this problem.
Question: How could I send the http request in one packet with boost::beast APIs? The other beast lib(parser, etc) are very friendly to developers.

Clarification needed about a SSL client using Boost asio

I have a C++ application that uses Boost_asio to do TCP/IP connection who read a .php document in a Web server that in turn uses the php script to update certain statistics.
The whole thing work as planned, but recently the server changed to use SSL (Google mandatory) and naturally, the previous connection stopped to work.
After a lot of theoretical reading about SSL, I'm still in the dark about certain practical details.
Using the example in the Boost asio documentation and a file “cacert.pem”, downloaded form somewhere following indications in this site, I'm able to run correctly the example using:
<host> = “www.google.com” and <port> = “https”.
Using the example “as is”, the Google server response is:
Verifying /OU=GlobalSign Root CA - R2/O=GlobalSign/CN=GlobalSign
Verifying /C=US/O=Google Trust Services/CN=Google Internet Authority G3
Verifying /C=US/ST=California/L=Mountain View/O=Google LLC/CN=www.google.com
Enter message: Reply:
But when using
<host> = “www.zator.com” and <port> = “https”
my domain, hosted in 1&1 IONOS, the reply is:
Handshake failed: tlsv1 alert internal error
At this point I have several questions:
What in the hell mean the sentence: ctx.load_verify_file("cacert.pem"); ?
The content of that file, can be the culprit of the fail when connecting with my domain?
Is that sentence essential to complete the connection?
In the case of google server (absence of error), is it supposed that after the sentence io_context.run(); the connection has been correctly established?
Assuming I make public the client's member socket_ (private in the example), can I continue with some as (I can't test that in google :-)
std::string request("GET /mystatistics.php HTTP/1.1\r\n\r\n");
boost::asio::write(c.socket_, boost::asio::buffer(request));
boost::system::error_code ec;
std::string response;
do { // read response
char buf[1024];
size_t bytes_transferred = c.socket_.read_some(boost::asio::buffer(buf), ec);
if (!ec) response.append(buf, buf + bytes_transferred);
} while (!ec);
std::cout << "Response received: '" << response << "'\n";
Thanks in advance.
I've found some useful information here. A good, albeit partial info, and a good start point to further search

Connect SSL server via HTTP proxy using boost

I should send text messages to messaging platform like Slacks using incoming webhook. But my network as developing is different from public one, so I decided to use our company's proxy server which can connect to public one. Briefly,
Client - HTTP PROXY - SERVER (messaging platform)
1. Firstly, establish C-P connection using normal TCP socket.
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> m_socket;
boost::asio::ip::tcp::resolver resolver(m_io);
boost::asio::ip::tcp::resolver::query query("www.proxy.dev", "8080");
boost::asio::ip::tcp::resolver::iterator iterator = resolver.resolve(query);
boost::asio::async_connect(m_socket.lowest_layer(), iterator,
boost::bind(&ClientSocket::HandleHandShake, this,
boost::asio::placeholders::error, shared_from_this()));
After connecting, send connection string to proxy.
m_stringStream << "CONNECT";
m_stringStream << " " << m_domain << ":443";
m_stringStream << " HTTP/1.1\r\n";
m_stringStream << "HOST: " << m_domain << ":443\r\n";
m_stringStream << "Proxy-Connection: keep-alive\r\n";
m_stringStream << "Connection: keep-alive\r\n";
2. Receive status code from proxy.
200 OK
3. Handshake
m_socket.set_verify_mode(boost::asio::ssl::verify_none);
m_socket.set_verify_callback([](auto&& preverified, auto&& ctx)
{
char subject_name[256];
X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256);
return preverified;
});
m_socket.async_handshake(boost::asio::ssl::stream_base::client,
boost::bind(&ClientSocket::HandleHandShake, this,
boost::asio::placeholders::error, shared_from_this()));
4. Write HTTP POST, Read status code.
At 3 step, the error code informs me "short read" so i can't go next step.
I have searched many questions, but everything is not worked for me.
If I skip the handshake process, can receive "uninitialized" error.
If I also skip 'CONNECT' with proxy and send 'POST' directly to server, can receive "403 badrequest" error.
It's OK to connect server and send text message not through proxy server.
Thank you for reading.

How to connect to elasticsearch using boost asio?

I'm trying to write a C++ snippet to make a POST to an elasticsearch server. The server is running on ip 172.20.1.160, and I can PUT data using curl, like in the Elasticsearch tutorial:
curl -XPUT 'localhost:9200/twitter/tweet/1?op_type=create&pretty' -H 'Content-Type: application/json' -d'
{
"user" : "kimchy",
"post_date" : "2009-11-15T14:12:12",
"message" : "trying out Elasticsearch"
}
'
But when I try to do something similar with boost ASIO, my connection is apparently refused.
Here's minimal compileable code showing how I try to create the connection, and which produces the error:
/// Compiled with:
/// g++ --std=c++14 foo.cpp -lboost_system -lpthread
#include <boost/asio.hpp>
constexpr const char *carbon_port = "2003";
constexpr const char *ES_port = "9200";
constexpr const char *server_ip = "172.20.1.160";
int main()
{
boost::asio::io_service io_service;
boost::asio::ip::tcp::resolver resolver(io_service);
boost::asio::ip::tcp::resolver::query query(server_ip, ES_port);
auto endpoint = *resolver.resolve(query);
boost::asio::ip::tcp::socket socket (io_service);
socket.connect(endpoint);
}
For what it's worth, I also have a carbon-graphite server running on the same machine, and I can connect and send data there via the plain-text protocol, but I am unable to make a socket connection to elasticsearch, I get the exception:
terminate called after throwing an instance of 'boost::exception_detail::clone_impl<boost::exception_detail::error_info_injector<boost::system::system_error> >'
what(): connect: Connection refused
Can someone enlighten me to what I need to know to get this working?
by default elasticsearch listens to localhost only , if you want it to listen to all ports , in config/elasticsearch.yml add the line network.host: 0.0.0.0 and restart elasticsearch.

Calling QSslSocket::startServerEncryption, but nothing happens

I'm trying to implement an SSL server using the sample code from Qt documentation.
But after serverSocket->startServerEncryption(); is called, nothing happens - neither the encrypted() nor the sslErrors() signals are emitted (I've put breakpoints in the slots connected to them).
I test it by connecting an QSslSocket using connectToHostEncrypted to the port I'm listening on. The socket sends data, but my server does not respond (I'm using a TCP sniffer/proxy to see all the data being sent from client to server and from server to client).
This is my code for the server:
void SslServer::incomingConnection(int socketDescriptor)
{
qDebug() << "SslServer::incomingConnection()";
QSslSocket *serverSocket = new QSslSocket(this);
if (serverSocket->setSocketDescriptor(socketDescriptor)) {
connect(serverSocket, SIGNAL(encrypted()), this, SLOT(ready()));
connect(serverSocket, SIGNAL(sslErrors(QList<QSslError>)), this, SLOT(sslErrors(QList<QSslError>)));
serverSocket->startServerEncryption();
} else {
delete serverSocket;
}
}
And this is how I connect to it:
server = new SslServer(this);
server->listen(QHostAddress::Any, 3333);
QSslSocket *socket = new QSslSocket(this);
socket->connectToHostEncrypted("127.0.0.1", 3333);
According to the documentation:
Both the key and the local certificate are required if you are creating an SSL server socket.
And if you don't provide them, a "regular" error(QAbstractSocket::SocketError) signal is emitted by the socket. As you found out, the server doesn't send any data in that case.
SSH is not SSL. SSH client waits for initial data from server, while SSL client first sends data. So they both waiting for data from the other side.