typedef gnutls_dh_params_t gnutls_dh_params;
#endif
-enum issl_status { ISSL_NONE, ISSL_HANDSHAKING_READ, ISSL_HANDSHAKING_WRITE, ISSL_HANDSHAKEN, ISSL_CLOSING, ISSL_CLOSED };
+enum issl_status { ISSL_NONE, ISSL_HANDSHAKING, ISSL_HANDSHAKEN };
#if INSPIRCD_GNUTLS_HAS_VERSION(2, 12, 0)
#define GNUTLS_NEW_CERT_CALLBACK_API
typedef gnutls_retr_st cert_cb_last_param_type;
#endif
+#if INSPIRCD_GNUTLS_HAS_VERSION(3, 3, 5)
+#define INSPIRCD_GNUTLS_HAS_RECV_PACKET
+#endif
+
class RandGen : public HandlerBase2<void, char*, size_t>
{
public:
}
};
+ class DataReader
+ {
+ int retval;
+#ifdef INSPIRCD_GNUTLS_HAS_RECV_PACKET
+ gnutls_packet_t packet;
+
+ public:
+ DataReader(gnutls_session_t sess)
+ {
+ // Using the packet API avoids the final copy of the data which GnuTLS does if we supply
+ // our own buffer. Instead, we get the buffer containing the data from GnuTLS and copy it
+ // to the recvq directly from there in appendto().
+ retval = gnutls_record_recv_packet(sess, &packet);
+ }
+
+ void appendto(std::string& recvq)
+ {
+ // Copy data from GnuTLS buffers to recvq
+ gnutls_datum_t datum;
+ gnutls_packet_get(packet, &datum, NULL);
+ recvq.append(reinterpret_cast<const char*>(datum.data), datum.size);
+
+ gnutls_packet_deinit(packet);
+ }
+#else
+ char* const buffer;
+
+ public:
+ DataReader(gnutls_session_t sess)
+ : buffer(ServerInstance->GetReadBuffer())
+ {
+ // Read data from GnuTLS buffers into ReadBuffer
+ retval = gnutls_record_recv(sess, buffer, ServerInstance->Config->NetBufferSize);
+ }
+
+ void appendto(std::string& recvq)
+ {
+ // Copy data from ReadBuffer to recvq
+ recvq.append(buffer, retval);
+ }
+#endif
+
+ int ret() const { return retval; }
+ };
+
class Profile : public refcountbase
{
/** Name of this profile
status = ISSL_NONE;
}
- bool Handshake(StreamSocket* user)
+ // Returns 1 if handshake succeeded, 0 if it is still in progress, -1 if it failed
+ int Handshake(StreamSocket* user)
{
int ret = gnutls_handshake(this->sess);
if(ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
{
// Handshake needs resuming later, read() or write() would have blocked.
+ this->status = ISSL_HANDSHAKING;
if (gnutls_record_get_direction(this->sess) == 0)
{
// gnutls_handshake() wants to read() again.
- this->status = ISSL_HANDSHAKING_READ;
SocketEngine::ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE);
}
else
{
// gnutls_handshake() wants to write() again.
- this->status = ISSL_HANDSHAKING_WRITE;
SocketEngine::ChangeEventMask(user, FD_WANT_NO_READ | FD_WANT_SINGLE_WRITE);
}
+
+ return 0;
}
else
{
user->SetError("Handshake Failed - " + std::string(gnutls_strerror(ret)));
CloseSession();
- this->status = ISSL_CLOSING;
+ return -1;
}
-
- return false;
}
else
{
// Finish writing, if any left
SocketEngine::ChangeEventMask(user, FD_WANT_POLL_READ | FD_WANT_NO_WRITE | FD_ADD_TRIAL_WRITE);
- return true;
+ return 1;
}
}
gnutls_x509_crt_deinit(cert);
}
+ // Returns 1 if application I/O should proceed, 0 if it must wait for the underlying protocol to progress, -1 on fatal error
+ int PrepareIO(StreamSocket* sock)
+ {
+ if (status == ISSL_HANDSHAKEN)
+ return 1;
+ else if (status == ISSL_HANDSHAKING)
+ {
+ // The handshake isn't finished, try to finish it
+ return Handshake(sock);
+ }
+
+ CloseSession();
+ sock->SetError("No SSL session");
+ return -1;
+ }
+
static const char* UnknownIfNULL(const char* str)
{
return str ? str : "UNKNOWN";
int OnStreamSocketRead(StreamSocket* user, std::string& recvq) CXX11_OVERRIDE
{
- if (!this->sess)
- {
- CloseSession();
- user->SetError("No SSL session");
- return -1;
- }
-
- if (this->status == ISSL_HANDSHAKING_READ || this->status == ISSL_HANDSHAKING_WRITE)
- {
- // The handshake isn't finished, try to finish it.
-
- if (!Handshake(user))
- {
- if (this->status != ISSL_CLOSING)
- return 0;
- return -1;
- }
- }
+ // Finish handshake if needed
+ int prepret = PrepareIO(user);
+ if (prepret <= 0)
+ return prepret;
// If we resumed the handshake then this->status will be ISSL_HANDSHAKEN.
-
- if (this->status == ISSL_HANDSHAKEN)
{
- char* buffer = ServerInstance->GetReadBuffer();
- size_t bufsiz = ServerInstance->Config->NetBufferSize;
- int ret = gnutls_record_recv(this->sess, buffer, bufsiz);
+ GnuTLS::DataReader reader(sess);
+ int ret = reader.ret();
if (ret > 0)
{
- recvq.append(buffer, ret);
+ reader.appendto(recvq);
return 1;
}
else if (ret == GNUTLS_E_AGAIN || ret == GNUTLS_E_INTERRUPTED)
return -1;
}
}
- else if (this->status == ISSL_CLOSING)
- return -1;
-
- return 0;
}
int OnStreamSocketWrite(StreamSocket* user, std::string& sendq) CXX11_OVERRIDE
{
- if (!this->sess)
- {
- CloseSession();
- user->SetError("No SSL session");
- return -1;
- }
-
- if (this->status == ISSL_HANDSHAKING_WRITE || this->status == ISSL_HANDSHAKING_READ)
- {
- // The handshake isn't finished, try to finish it.
- Handshake(user);
- if (this->status != ISSL_CLOSING)
- return 0;
- return -1;
- }
+ // Finish handshake if needed
+ int prepret = PrepareIO(user);
+ if (prepret <= 0)
+ return prepret;
+ // Session is ready for transferring application data
int ret = 0;
- if (this->status == ISSL_HANDSHAKEN)
{
ret = gnutls_record_send(this->sess, sendq.data(), sendq.length());
return -1;
}
}
-
- return 0;
}
void TellCiphersAndFingerprint(LocalUser* user)