1074 lines
27 KiB
C++
1074 lines
27 KiB
C++
// Copyright (c) 2010-2023, Lawrence Livermore National Security, LLC. Produced
|
|
// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
|
|
// LICENSE and NOTICE for details. LLNL-CODE-806117.
|
|
//
|
|
// This file is part of the MFEM library. For more information and source code
|
|
// availability visit https://mfem.org.
|
|
//
|
|
// MFEM is free software; you can redistribute it and/or modify it under the
|
|
// terms of the BSD-3 license. We welcome feedback and contributions, see file
|
|
// CONTRIBUTING.md for details.
|
|
|
|
#ifdef _WIN32
|
|
// Turn off CRT deprecation warnings for strerror (VS 2013)
|
|
#define _CRT_SECURE_NO_WARNINGS
|
|
#endif
|
|
|
|
#include "socketstream.hpp"
|
|
|
|
#include <cstring> // memset, memcpy, strerror
|
|
#include <cerrno> // errno
|
|
#ifndef _WIN32
|
|
#include <netdb.h> // gethostbyname
|
|
#include <arpa/inet.h> // htons
|
|
#include <sys/types.h> // socket, setsockopt, connect, recv, send
|
|
#include <sys/socket.h> // socket, setsockopt, connect, recv, send
|
|
#include <unistd.h> // close
|
|
#include <netinet/in.h> // sockaddr_in
|
|
#define closesocket (::close)
|
|
#else
|
|
#include <winsock.h>
|
|
#ifdef _MSC_VER
|
|
typedef int ssize_t;
|
|
// Link with ws2_32.lib
|
|
#pragma comment(lib, "ws2_32.lib")
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef MFEM_USE_GNUTLS
|
|
#include <cstdlib> // getenv
|
|
#ifndef MFEM_USE_GNUTLS_X509
|
|
#include <gnutls/openpgp.h>
|
|
#endif
|
|
// Enable debug messages from GnuTLS_* classes
|
|
// #define MFEM_USE_GNUTLS_DEBUG
|
|
#endif
|
|
|
|
namespace mfem
|
|
{
|
|
|
|
// Helper class for handling Winsock initialization calls.
|
|
class WinsockWrapper
|
|
{
|
|
public:
|
|
WinsockWrapper()
|
|
{
|
|
#ifdef _WIN32
|
|
WSADATA wsaData;
|
|
int err_flag = WSAStartup(MAKEWORD(2,2), &wsaData);
|
|
if (err_flag != 0)
|
|
{
|
|
mfem::out << "Error occurred during initialization of WinSock."
|
|
<< std::endl;
|
|
return;
|
|
}
|
|
#endif
|
|
initialized = true;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
~WinsockWrapper() { WSACleanup(); }
|
|
#endif
|
|
|
|
WinsockWrapper(const WinsockWrapper&) = delete;
|
|
WinsockWrapper& operator=(const WinsockWrapper&) = delete;
|
|
|
|
bool Initialized() { return initialized; }
|
|
private:
|
|
bool initialized = false;
|
|
};
|
|
|
|
// If available, Winsock is initialized when this object is constructed.
|
|
static WinsockWrapper wsInit_;
|
|
|
|
int socketbuf::attach(int sd)
|
|
{
|
|
int old_sd = socket_descriptor;
|
|
pubsync();
|
|
socket_descriptor = sd;
|
|
setg(NULL, NULL, NULL);
|
|
setp(obuf, obuf + buflen);
|
|
return old_sd;
|
|
}
|
|
|
|
int socketbuf::open(const char hostname[], int port)
|
|
{
|
|
struct sockaddr_in sa;
|
|
struct hostent *hp;
|
|
|
|
if (!wsInit_.Initialized())
|
|
{
|
|
mfem_error("Attempting to open socket, but Winsock not initialized.");
|
|
}
|
|
|
|
close();
|
|
setg(NULL, NULL, NULL);
|
|
setp(obuf, obuf + buflen);
|
|
|
|
hp = gethostbyname(hostname);
|
|
if (hp == NULL)
|
|
{
|
|
socket_descriptor = -3;
|
|
return -1;
|
|
}
|
|
memset(&sa, 0, sizeof(sa));
|
|
memcpy((char *)&sa.sin_addr, hp->h_addr, hp->h_length);
|
|
sa.sin_family = hp->h_addrtype;
|
|
sa.sin_port = htons(port);
|
|
socket_descriptor = socket(hp->h_addrtype, SOCK_STREAM, 0);
|
|
if (socket_descriptor < 0)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
#if defined __APPLE__
|
|
// OS X does not support the MSG_NOSIGNAL option of send().
|
|
// Instead we can use the SO_NOSIGPIPE socket option.
|
|
int on = 1;
|
|
if (setsockopt(socket_descriptor, SOL_SOCKET, SO_NOSIGPIPE,
|
|
(char *)(&on), sizeof(on)) < 0)
|
|
{
|
|
closesocket(socket_descriptor);
|
|
socket_descriptor = -2;
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
if (connect(socket_descriptor,
|
|
(const struct sockaddr *)&sa, sizeof(sa)) < 0)
|
|
{
|
|
closesocket(socket_descriptor);
|
|
socket_descriptor = -2;
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int socketbuf::close()
|
|
{
|
|
if (is_open())
|
|
{
|
|
pubsync();
|
|
int err_flag = closesocket(socket_descriptor);
|
|
socket_descriptor = -1;
|
|
return err_flag;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int socketbuf::sync()
|
|
{
|
|
ssize_t bw, n = pptr() - pbase();
|
|
// mfem::out << "[socketbuf::sync n=" << n << ']' << std::endl;
|
|
while (n > 0)
|
|
{
|
|
#ifdef MSG_NOSIGNAL
|
|
bw = send(socket_descriptor, pptr() - n, n, MSG_NOSIGNAL);
|
|
#else
|
|
bw = send(socket_descriptor, pptr() - n, n, 0);
|
|
#endif
|
|
if (bw < 0)
|
|
{
|
|
#ifdef MFEM_DEBUG
|
|
mfem::out << "Error in send(): " << strerror(errno) << std::endl;
|
|
#endif
|
|
setp(pptr() - n, obuf + buflen);
|
|
pbump(n);
|
|
return -1;
|
|
}
|
|
n -= bw;
|
|
}
|
|
setp(obuf, obuf + buflen);
|
|
return 0;
|
|
}
|
|
|
|
socketbuf::int_type socketbuf::underflow()
|
|
{
|
|
// assuming (gptr() < egptr()) is false
|
|
ssize_t br = recv(socket_descriptor, ibuf, buflen, 0);
|
|
// mfem::out << "[socketbuf::underflow br=" << br << ']'
|
|
// << std::endl;
|
|
if (br <= 0)
|
|
{
|
|
#ifdef MFEM_DEBUG
|
|
if (br < 0)
|
|
{
|
|
mfem::out << "Error in recv(): " << strerror(errno) << std::endl;
|
|
}
|
|
#endif
|
|
setg(NULL, NULL, NULL);
|
|
return traits_type::eof();
|
|
}
|
|
setg(ibuf, ibuf, ibuf + br);
|
|
return traits_type::to_int_type(*ibuf);
|
|
}
|
|
|
|
socketbuf::int_type socketbuf::overflow(int_type c)
|
|
{
|
|
if (sync() < 0)
|
|
{
|
|
return traits_type::eof();
|
|
}
|
|
if (traits_type::eq_int_type(c, traits_type::eof()))
|
|
{
|
|
return traits_type::not_eof(c);
|
|
}
|
|
*pptr() = traits_type::to_char_type(c);
|
|
pbump(1);
|
|
return c;
|
|
}
|
|
|
|
std::streamsize socketbuf::xsgetn(char_type *s__, std::streamsize n__)
|
|
{
|
|
// mfem::out << "[socketbuf::xsgetn n__=" << n__ << ']'
|
|
// << std::endl;
|
|
const std::streamsize bn = egptr() - gptr();
|
|
if (n__ <= bn)
|
|
{
|
|
traits_type::copy(s__, gptr(), n__);
|
|
gbump(n__);
|
|
return n__;
|
|
}
|
|
traits_type::copy(s__, gptr(), bn);
|
|
setg(NULL, NULL, NULL);
|
|
std::streamsize remain = n__ - bn;
|
|
char_type *end = s__ + n__;
|
|
ssize_t br;
|
|
while (remain > 0)
|
|
{
|
|
br = recv(socket_descriptor, end - remain, remain, 0);
|
|
if (br <= 0)
|
|
{
|
|
#ifdef MFEM_DEBUG
|
|
if (br < 0)
|
|
{
|
|
mfem::out << "Error in recv(): " << strerror(errno) << std::endl;
|
|
}
|
|
#endif
|
|
return (n__ - remain);
|
|
}
|
|
remain -= br;
|
|
}
|
|
return n__;
|
|
}
|
|
|
|
std::streamsize socketbuf::xsputn(const char_type *s__, std::streamsize n__)
|
|
{
|
|
// mfem::out << "[socketbuf::xsputn n__=" << n__ << ']'
|
|
// << std::endl;
|
|
if (pptr() + n__ <= epptr())
|
|
{
|
|
traits_type::copy(pptr(), s__, n__);
|
|
pbump(n__);
|
|
return n__;
|
|
}
|
|
if (sync() < 0)
|
|
{
|
|
return 0;
|
|
}
|
|
ssize_t bw;
|
|
std::streamsize remain = n__;
|
|
const char_type *end = s__ + n__;
|
|
while (remain > buflen)
|
|
{
|
|
#ifdef MSG_NOSIGNAL
|
|
bw = send(socket_descriptor, end - remain, remain, MSG_NOSIGNAL);
|
|
#else
|
|
bw = send(socket_descriptor, end - remain, remain, 0);
|
|
#endif
|
|
if (bw < 0)
|
|
{
|
|
#ifdef MFEM_DEBUG
|
|
mfem::out << "Error in send(): " << strerror(errno) << std::endl;
|
|
#endif
|
|
return (n__ - remain);
|
|
}
|
|
remain -= bw;
|
|
}
|
|
if (remain > 0)
|
|
{
|
|
traits_type::copy(pptr(), end - remain, remain);
|
|
pbump(remain);
|
|
}
|
|
return n__;
|
|
}
|
|
|
|
|
|
socketserver::socketserver(int port, int backlog)
|
|
{
|
|
listen_socket = socket(PF_INET, SOCK_STREAM, 0); // tcp socket
|
|
if (listen_socket < 0)
|
|
{
|
|
return;
|
|
}
|
|
int on = 1;
|
|
if (setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR,
|
|
(char *)(&on), sizeof(on)) < 0)
|
|
{
|
|
closesocket(listen_socket);
|
|
listen_socket = -2;
|
|
return;
|
|
}
|
|
struct sockaddr_in sa;
|
|
memset(&sa, 0, sizeof(sa));
|
|
sa.sin_family = AF_INET;
|
|
sa.sin_port = htons(port);
|
|
sa.sin_addr.s_addr = INADDR_ANY;
|
|
if (bind(listen_socket, (const struct sockaddr *)&sa, sizeof(sa)))
|
|
{
|
|
closesocket(listen_socket);
|
|
listen_socket = -3;
|
|
return;
|
|
}
|
|
|
|
if (listen(listen_socket, backlog) < 0)
|
|
{
|
|
closesocket(listen_socket);
|
|
listen_socket = -4;
|
|
return;
|
|
}
|
|
}
|
|
|
|
int socketserver::close()
|
|
{
|
|
if (!good())
|
|
{
|
|
return 0;
|
|
}
|
|
int err_flag = closesocket(listen_socket);
|
|
listen_socket = -1;
|
|
return err_flag;
|
|
}
|
|
|
|
int socketserver::accept()
|
|
{
|
|
return good() ? ::accept(listen_socket, NULL, NULL) : -1;
|
|
}
|
|
|
|
int socketserver::accept(socketstream &sockstr)
|
|
{
|
|
if (!good())
|
|
{
|
|
return -1;
|
|
}
|
|
int socketd = ::accept(listen_socket, NULL, NULL);
|
|
if (socketd >= 0)
|
|
{
|
|
sockstr.rdbuf()->close();
|
|
sockstr.rdbuf()->attach(socketd);
|
|
return sockstr.rdbuf()->getsocketdescriptor();
|
|
}
|
|
return socketd;
|
|
}
|
|
|
|
#ifdef MFEM_USE_GNUTLS
|
|
|
|
static void mfem_gnutls_log_func(int level, const char *str)
|
|
{
|
|
mfem::out << "GnuTLS <" << level << "> " << str << std::flush;
|
|
}
|
|
|
|
GnuTLS_global_state::GnuTLS_global_state()
|
|
{
|
|
status.set_result(gnutls_global_init());
|
|
status.print_on_error("gnutls_global_init");
|
|
glob_init_ok = status.good();
|
|
|
|
if (status.good())
|
|
{
|
|
gnutls_global_set_log_function(mfem_gnutls_log_func);
|
|
}
|
|
|
|
dh_params = NULL;
|
|
}
|
|
|
|
GnuTLS_global_state::~GnuTLS_global_state()
|
|
{
|
|
gnutls_dh_params_deinit(dh_params);
|
|
if (glob_init_ok) { gnutls_global_deinit(); }
|
|
}
|
|
|
|
void GnuTLS_global_state::generate_dh_params()
|
|
{
|
|
if (status.good())
|
|
{
|
|
status.set_result(gnutls_dh_params_init(&dh_params));
|
|
status.print_on_error("gnutls_dh_params_init");
|
|
if (!status.good()) { dh_params = NULL; }
|
|
else
|
|
{
|
|
#if GNUTLS_VERSION_NUMBER >= 0x021200
|
|
unsigned bits =
|
|
gnutls_sec_param_to_pk_bits(
|
|
GNUTLS_PK_DH, GNUTLS_SEC_PARAM_LEGACY);
|
|
#else
|
|
unsigned bits = 1024;
|
|
#endif
|
|
mfem::out << "Generating DH params (" << bits << " bits) ..."
|
|
<< std::flush;
|
|
status.set_result(gnutls_dh_params_generate2(dh_params, bits));
|
|
mfem::out << " done." << std::endl;
|
|
status.print_on_error("gnutls_dh_params_generate2");
|
|
if (!status.good())
|
|
{
|
|
gnutls_dh_params_deinit(dh_params);
|
|
dh_params = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int mfem_gnutls_verify_callback(gnutls_session_t session)
|
|
{
|
|
unsigned int status;
|
|
#if GNUTLS_VERSION_NUMBER >= 0x030104
|
|
const char *hostname = (const char *) gnutls_session_get_ptr(session);
|
|
int ret = gnutls_certificate_verify_peers3(session, hostname, &status);
|
|
if (ret < 0)
|
|
{
|
|
mfem::out << "Error in gnutls_certificate_verify_peers3:"
|
|
<< gnutls_strerror(ret) << std::endl;
|
|
return GNUTLS_E_CERTIFICATE_ERROR;
|
|
}
|
|
|
|
#ifdef MFEM_DEBUG
|
|
gnutls_datum_t status_str;
|
|
gnutls_certificate_type_t type = gnutls_certificate_type_get(session);
|
|
ret = gnutls_certificate_verification_status_print(
|
|
status, type, &status_str, 0);
|
|
if (ret < 0)
|
|
{
|
|
mfem::out << "Error in gnutls_certificate_verification_status_print:"
|
|
<< gnutls_strerror(ret) << std::endl;
|
|
return GNUTLS_E_CERTIFICATE_ERROR;
|
|
}
|
|
mfem::out << status_str.data << std::endl;
|
|
gnutls_free(status_str.data);
|
|
#endif
|
|
#else // --> GNUTLS_VERSION_NUMBER < 0x030104
|
|
int ret = gnutls_certificate_verify_peers2(session, &status);
|
|
if (ret < 0)
|
|
{
|
|
mfem::out << "Error in gnutls_certificate_verify_peers2:"
|
|
<< gnutls_strerror(ret) << std::endl;
|
|
return GNUTLS_E_CERTIFICATE_ERROR;
|
|
}
|
|
#ifdef MFEM_DEBUG
|
|
mfem::out << (status ?
|
|
"The certificate is NOT trusted." :
|
|
"The certificate is trusted.") << std::endl;
|
|
#endif
|
|
#endif
|
|
|
|
return status ? GNUTLS_E_CERTIFICATE_ERROR : 0;
|
|
}
|
|
|
|
GnuTLS_session_params::GnuTLS_session_params(
|
|
GnuTLS_global_state &state, const char *pubkey_file,
|
|
const char *privkey_file, const char *trustedkeys_file, unsigned int flags)
|
|
: state(state)
|
|
{
|
|
status.set_result(state.status.get_result());
|
|
my_flags = status.good() ? flags : 0;
|
|
|
|
// allocate my_cred
|
|
if (status.good())
|
|
{
|
|
status.set_result(
|
|
gnutls_certificate_allocate_credentials(&my_cred));
|
|
status.print_on_error("gnutls_certificate_allocate_credentials");
|
|
}
|
|
if (!status.good()) { my_cred = NULL; }
|
|
else
|
|
{
|
|
#ifndef MFEM_USE_GNUTLS_X509
|
|
status.set_result(
|
|
gnutls_certificate_set_openpgp_key_file(
|
|
my_cred, pubkey_file, privkey_file, GNUTLS_OPENPGP_FMT_RAW));
|
|
status.print_on_error("gnutls_certificate_set_openpgp_key_file");
|
|
#else
|
|
status.set_result(
|
|
gnutls_certificate_set_x509_key_file(
|
|
my_cred, pubkey_file, privkey_file, GNUTLS_X509_FMT_PEM));
|
|
// e.g. pubkey_file, privkey_file == "cert.pem", "key.pem"
|
|
status.print_on_error("gnutls_certificate_set_x509_key_file");
|
|
#endif
|
|
}
|
|
|
|
if (status.good())
|
|
{
|
|
/*
|
|
gnutls_certificate_set_pin_function(
|
|
my_cred,
|
|
(gnutls_pin_callback_t) fn,
|
|
(void *) userdata);
|
|
*/
|
|
}
|
|
|
|
if (status.good())
|
|
{
|
|
#ifndef MFEM_USE_GNUTLS_X509
|
|
status.set_result(
|
|
gnutls_certificate_set_openpgp_keyring_file(
|
|
my_cred, trustedkeys_file, GNUTLS_OPENPGP_FMT_RAW));
|
|
status.print_on_error("gnutls_certificate_set_openpgp_keyring_file");
|
|
#else
|
|
int num_certs =
|
|
gnutls_certificate_set_x509_trust_file(
|
|
my_cred, trustedkeys_file, GNUTLS_X509_FMT_PEM);
|
|
// e.g. trustedkeys_file == "trusted-certs.pem"
|
|
#ifdef MFEM_USE_GNUTLS_DEBUG
|
|
mfem::out << "[GnuTLS_session_params::GnuTLS_session_params] "
|
|
"number of trusted certificates = " << num_certs << std::endl;
|
|
#endif
|
|
status.set_result(num_certs > 0 ?
|
|
GNUTLS_E_SUCCESS : GNUTLS_E_CERTIFICATE_ERROR);
|
|
status.print_on_error("gnutls_certificate_set_x509_trust_file");
|
|
#endif
|
|
}
|
|
|
|
#if GNUTLS_VERSION_NUMBER >= 0x021000
|
|
if (status.good())
|
|
{
|
|
gnutls_certificate_set_verify_function(
|
|
my_cred, mfem_gnutls_verify_callback);
|
|
}
|
|
#endif
|
|
|
|
if (status.good() && (flags & GNUTLS_SERVER))
|
|
{
|
|
gnutls_dh_params_t dh_params = state.get_dh_params();
|
|
status.set_result(state.status.get_result());
|
|
if (status.good())
|
|
{
|
|
gnutls_certificate_set_dh_params(my_cred, dh_params);
|
|
}
|
|
}
|
|
}
|
|
|
|
void GnuTLS_socketbuf::handshake()
|
|
{
|
|
#ifdef MFEM_USE_GNUTLS_DEBUG
|
|
mfem::out << "[GnuTLS_socketbuf::handshake]" << std::endl;
|
|
#endif
|
|
|
|
// Called at the end of start_session.
|
|
int err_flag;
|
|
do
|
|
{
|
|
err_flag = gnutls_handshake(session);
|
|
status.set_result(err_flag);
|
|
if (status.good())
|
|
{
|
|
#if 0
|
|
mfem::out << "handshake successful, TLS version is "
|
|
<< gnutls_protocol_get_name(
|
|
gnutls_protocol_get_version(session)) << std::endl;
|
|
#endif
|
|
return;
|
|
}
|
|
}
|
|
while (err_flag == GNUTLS_E_INTERRUPTED || err_flag == GNUTLS_E_AGAIN);
|
|
#ifdef MFEM_DEBUG
|
|
status.print_on_error("gnutls_handshake");
|
|
#endif
|
|
}
|
|
|
|
#if (defined(MSG_NOSIGNAL) && !defined(_WIN32) && !defined(__APPLE__))
|
|
#define MFEM_USE_GNUTLS_PUSH_FUNCTION
|
|
|
|
static ssize_t mfem_gnutls_push_function(
|
|
gnutls_transport_ptr_t fd_ptr, const void *data, size_t datasize)
|
|
{
|
|
return send((int)(long)fd_ptr, data, datasize, MSG_NOSIGNAL);
|
|
}
|
|
#endif
|
|
|
|
void GnuTLS_socketbuf::start_session()
|
|
{
|
|
#ifdef MFEM_USE_GNUTLS_DEBUG
|
|
mfem::out << "[GnuTLS_socketbuf::start_session]" << std::endl;
|
|
#endif
|
|
|
|
// check for valid 'socket_descriptor' and inactive session
|
|
if (!is_open() || session_started) { return; }
|
|
|
|
status.set_result(params.status.get_result());
|
|
if (status.good())
|
|
{
|
|
#if GNUTLS_VERSION_NUMBER >= 0x030102
|
|
status.set_result(gnutls_init(&session, params.get_flags()));
|
|
#else
|
|
status.set_result(
|
|
gnutls_init(&session, (gnutls_connection_end_t) params.get_flags()));
|
|
#endif
|
|
status.print_on_error("gnutls_init");
|
|
}
|
|
|
|
session_started = status.good();
|
|
if (status.good())
|
|
{
|
|
const char *priorities;
|
|
// what is the right version here?
|
|
if (gnutls_check_version("2.12.0") != NULL)
|
|
{
|
|
// This works for version 2.12.23 (0x020c17) and above
|
|
#ifndef MFEM_USE_GNUTLS_X509
|
|
priorities = "NONE:+VERS-TLS1.2:+CIPHER-ALL:+MAC-ALL:+SIGN-ALL:"
|
|
"+COMP-ALL:+KX-ALL:+CTYPE-OPENPGP:+CURVE-ALL";
|
|
#else
|
|
priorities = "NONE:+VERS-TLS1.2:+CIPHER-ALL:+MAC-ALL:+SIGN-ALL:"
|
|
"+COMP-ALL:+KX-ALL:+CTYPE-X509:+CURVE-ALL";
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
// This works for version 2.8.5 (0x020805) and below
|
|
#ifndef MFEM_USE_GNUTLS_X509
|
|
priorities = "NORMAL:-CTYPE-X.509";
|
|
#else
|
|
priorities = "NORMAL:";
|
|
#endif
|
|
}
|
|
const char *err_ptr;
|
|
status.set_result(
|
|
gnutls_priority_set_direct(session, priorities, &err_ptr));
|
|
status.print_on_error("gnutls_priority_set_direct");
|
|
if (!status.good())
|
|
{
|
|
mfem::out << "Error ptr = \"" << err_ptr << '"' << std::endl;
|
|
}
|
|
}
|
|
|
|
if (status.good())
|
|
{
|
|
// set session credentials
|
|
status.set_result(
|
|
gnutls_credentials_set(
|
|
session, GNUTLS_CRD_CERTIFICATE, my_cred));
|
|
status.print_on_error("gnutls_credentials_set");
|
|
}
|
|
|
|
if (status.good())
|
|
{
|
|
const char *hostname = NULL; // no hostname verification
|
|
gnutls_session_set_ptr(session, (void*)hostname);
|
|
if (params.get_flags() & GNUTLS_SERVER)
|
|
{
|
|
// require clients to send certificate:
|
|
gnutls_certificate_server_set_request(session, GNUTLS_CERT_REQUIRE);
|
|
}
|
|
#if GNUTLS_VERSION_NUMBER >= 0x030100
|
|
gnutls_handshake_set_timeout(
|
|
session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT);
|
|
#endif
|
|
}
|
|
|
|
if (status.good())
|
|
{
|
|
#if GNUTLS_VERSION_NUMBER >= 0x030109
|
|
gnutls_transport_set_int(session, socket_descriptor);
|
|
#else
|
|
gnutls_transport_set_ptr(session,
|
|
(gnutls_transport_ptr_t) socket_descriptor);
|
|
#endif
|
|
|
|
handshake();
|
|
}
|
|
|
|
#if GNUTLS_VERSION_NUMBER < 0x021000
|
|
if (status.good())
|
|
{
|
|
status.set_result(mfem_gnutls_verify_callback(session));
|
|
if (!status.good())
|
|
{
|
|
int err_flag;
|
|
do
|
|
{
|
|
// Close the connection without waiting for close reply, i.e. we
|
|
// use GNUTLS_SHUT_WR.
|
|
err_flag = gnutls_bye(session, GNUTLS_SHUT_WR);
|
|
}
|
|
while (err_flag == GNUTLS_E_AGAIN || err_flag == GNUTLS_E_INTERRUPTED);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef MFEM_USE_GNUTLS_PUSH_FUNCTION
|
|
if (status.good())
|
|
{
|
|
gnutls_transport_set_push_function(session, mfem_gnutls_push_function);
|
|
}
|
|
#endif
|
|
|
|
if (!status.good())
|
|
{
|
|
if (session_started) { gnutls_deinit(session); }
|
|
session_started = false;
|
|
socketbuf::close();
|
|
}
|
|
}
|
|
|
|
void GnuTLS_socketbuf::end_session()
|
|
{
|
|
#ifdef MFEM_USE_GNUTLS_DEBUG
|
|
mfem::out << "[GnuTLS_socketbuf::end_session]" << std::endl;
|
|
#endif
|
|
|
|
// check for valid 'socket_descriptor'
|
|
if (!session_started) { return; }
|
|
|
|
if (is_open() && status.good())
|
|
{
|
|
pubsync();
|
|
#ifdef MFEM_USE_GNUTLS_DEBUG
|
|
mfem::out << "[GnuTLS_socketbuf::end_session: gnutls_bye]" << std::endl;
|
|
#endif
|
|
int err_flag;
|
|
do
|
|
{
|
|
// err_flag = gnutls_bye(session, GNUTLS_SHUT_RDWR);
|
|
err_flag = gnutls_bye(session,
|
|
GNUTLS_SHUT_WR); // does not wait for reply
|
|
status.set_result(err_flag);
|
|
}
|
|
while (err_flag == GNUTLS_E_AGAIN || err_flag == GNUTLS_E_INTERRUPTED);
|
|
status.print_on_error("gnutls_bye");
|
|
}
|
|
|
|
gnutls_deinit(session);
|
|
session_started = false;
|
|
}
|
|
|
|
int GnuTLS_socketbuf::attach(int sd)
|
|
{
|
|
#ifdef MFEM_USE_GNUTLS_DEBUG
|
|
mfem::out << "[GnuTLS_socketbuf::attach]" << std::endl;
|
|
#endif
|
|
|
|
end_session();
|
|
|
|
int old_sd = socketbuf::attach(sd);
|
|
|
|
start_session();
|
|
|
|
return old_sd;
|
|
}
|
|
|
|
int GnuTLS_socketbuf::open(const char hostname[], int port)
|
|
{
|
|
#ifdef MFEM_USE_GNUTLS_DEBUG
|
|
mfem::out << "[GnuTLS_socketbuf::open]" << std::endl;
|
|
#endif
|
|
|
|
int err_flag = socketbuf::open(hostname, port); // calls close()
|
|
if (err_flag) { return err_flag; }
|
|
|
|
start_session();
|
|
|
|
return status.good() ? 0 : -100;
|
|
}
|
|
|
|
int GnuTLS_socketbuf::close()
|
|
{
|
|
#ifdef MFEM_USE_GNUTLS_DEBUG
|
|
mfem::out << "[GnuTLS_socketbuf::close]" << std::endl;
|
|
#endif
|
|
|
|
end_session();
|
|
|
|
int err_flag = socketbuf::close();
|
|
|
|
return status.good() ? err_flag : -100;
|
|
}
|
|
|
|
int GnuTLS_socketbuf::sync()
|
|
{
|
|
ssize_t bw, n = pptr() - pbase();
|
|
#ifdef MFEM_USE_GNUTLS_DEBUG
|
|
mfem::out << "[GnuTLS_socketbuf::sync n=" << n << ']' << std::endl;
|
|
#endif
|
|
if (!session_started || !status.good()) { return -1; }
|
|
while (n > 0)
|
|
{
|
|
bw = gnutls_record_send(session, pptr() - n, n);
|
|
if (bw == GNUTLS_E_INTERRUPTED || bw == GNUTLS_E_AGAIN) { continue; }
|
|
if (bw < 0)
|
|
{
|
|
status.set_result((int)bw);
|
|
#ifdef MFEM_DEBUG
|
|
status.print_on_error("gnutls_record_send");
|
|
#endif
|
|
setp(pptr() - n, obuf + buflen);
|
|
pbump(n);
|
|
return -1;
|
|
}
|
|
n -= bw;
|
|
}
|
|
setp(obuf, obuf + buflen);
|
|
return 0;
|
|
}
|
|
|
|
GnuTLS_socketbuf::int_type GnuTLS_socketbuf::underflow()
|
|
{
|
|
#ifdef MFEM_USE_GNUTLS_DEBUG
|
|
mfem::out << "[GnuTLS_socketbuf::underflow ...]" << std::endl;
|
|
#endif
|
|
if (!session_started || !status.good()) { return traits_type::eof(); }
|
|
|
|
ssize_t br;
|
|
do
|
|
{
|
|
br = gnutls_record_recv(session, ibuf, buflen);
|
|
if (br == GNUTLS_E_REHANDSHAKE)
|
|
{
|
|
continue; // TODO: replace with re-handshake
|
|
}
|
|
}
|
|
while (br == GNUTLS_E_INTERRUPTED || br == GNUTLS_E_AGAIN);
|
|
#ifdef MFEM_USE_GNUTLS_DEBUG
|
|
mfem::out << "[GnuTLS_socketbuf::underflow br=" << br << ']' << std::endl;
|
|
#endif
|
|
|
|
if (br <= 0)
|
|
{
|
|
if (br < 0)
|
|
{
|
|
status.set_result((int)br);
|
|
#ifdef MFEM_DEBUG
|
|
status.print_on_error("gnutls_record_recv");
|
|
#endif
|
|
}
|
|
setg(NULL, NULL, NULL);
|
|
return traits_type::eof();
|
|
}
|
|
setg(ibuf, ibuf, ibuf + br);
|
|
return traits_type::to_int_type(*ibuf);
|
|
}
|
|
|
|
std::streamsize GnuTLS_socketbuf::xsgetn(char_type *s__, std::streamsize n__)
|
|
{
|
|
#ifdef MFEM_USE_GNUTLS_DEBUG
|
|
mfem::out << "[GnuTLS_socketbuf::xsgetn n__=" << n__ << ']' << std::endl;
|
|
#endif
|
|
if (!session_started || !status.good()) { return 0; }
|
|
|
|
const std::streamsize bn = egptr() - gptr();
|
|
if (n__ <= bn)
|
|
{
|
|
traits_type::copy(s__, gptr(), n__);
|
|
gbump(n__);
|
|
return n__;
|
|
}
|
|
traits_type::copy(s__, gptr(), bn);
|
|
setg(NULL, NULL, NULL);
|
|
std::streamsize remain = n__ - bn;
|
|
char_type *end = s__ + n__;
|
|
ssize_t br;
|
|
while (remain > 0)
|
|
{
|
|
do
|
|
{
|
|
br = gnutls_record_recv(session, end - remain, remain);
|
|
if (br == GNUTLS_E_REHANDSHAKE)
|
|
{
|
|
continue; // TODO: replace with re-handshake
|
|
}
|
|
}
|
|
while (br == GNUTLS_E_INTERRUPTED || br == GNUTLS_E_AGAIN);
|
|
if (br <= 0)
|
|
{
|
|
if (br < 0)
|
|
{
|
|
status.set_result((int)br);
|
|
#ifdef MFEM_DEBUG
|
|
status.print_on_error("gnutls_record_recv");
|
|
#endif
|
|
}
|
|
return (n__ - remain);
|
|
}
|
|
remain -= br;
|
|
}
|
|
return n__;
|
|
}
|
|
|
|
std::streamsize GnuTLS_socketbuf::xsputn(const char_type *s__,
|
|
std::streamsize n__)
|
|
{
|
|
#ifdef MFEM_USE_GNUTLS_DEBUG
|
|
mfem::out << "[GnuTLS_socketbuf::xsputn n__=" << n__ << ']' << std::endl;
|
|
#endif
|
|
if (!session_started || !status.good()) { return 0; }
|
|
|
|
if (pptr() + n__ <= epptr())
|
|
{
|
|
traits_type::copy(pptr(), s__, n__);
|
|
pbump(n__);
|
|
return n__;
|
|
}
|
|
if (sync() < 0)
|
|
{
|
|
return 0;
|
|
}
|
|
ssize_t bw;
|
|
std::streamsize remain = n__;
|
|
const char_type *end = s__ + n__;
|
|
while (remain > buflen)
|
|
{
|
|
bw = gnutls_record_send(session, end - remain, remain);
|
|
if (bw == GNUTLS_E_INTERRUPTED || bw == GNUTLS_E_AGAIN) { continue; }
|
|
#ifdef MFEM_USE_GNUTLS_DEBUG
|
|
mfem::out << "[GnuTLS_socketbuf::xsputn bw=" << bw << ']' << std::endl;
|
|
#endif
|
|
if (bw < 0)
|
|
{
|
|
status.set_result((int)bw);
|
|
#ifdef MFEM_DEBUG
|
|
status.print_on_error("gnutls_record_send");
|
|
#endif
|
|
return (n__ - remain);
|
|
}
|
|
remain -= bw;
|
|
}
|
|
if (remain > 0)
|
|
{
|
|
traits_type::copy(pptr(), end - remain, remain);
|
|
pbump(remain);
|
|
}
|
|
return n__;
|
|
}
|
|
|
|
|
|
int socketstream::num_glvis_sockets = 0;
|
|
GnuTLS_global_state *socketstream::state = NULL;
|
|
GnuTLS_session_params *socketstream::params = NULL;
|
|
|
|
// static method
|
|
GnuTLS_session_params &socketstream::add_socket()
|
|
{
|
|
if (num_glvis_sockets == 0)
|
|
{
|
|
state = new GnuTLS_global_state;
|
|
// state->set_log_level(1000);
|
|
std::string home_dir(getenv("HOME"));
|
|
std::string client_dir = home_dir + "/.config/glvis/client/";
|
|
#ifndef MFEM_USE_GNUTLS_X509
|
|
std::string pubkey = client_dir + "pubring.gpg";
|
|
std::string privkey = client_dir + "secring.gpg";
|
|
std::string trustedkeys = client_dir + "trusted-servers.gpg";
|
|
#else
|
|
std::string pubkey = client_dir + "cert.pem";
|
|
std::string privkey = client_dir + "key.pem";
|
|
std::string trustedkeys = client_dir + "trusted-servers.pem";
|
|
#endif
|
|
params = new GnuTLS_session_params(
|
|
*state, pubkey.c_str(), privkey.c_str(), trustedkeys.c_str(),
|
|
GNUTLS_CLIENT);
|
|
if (!params->status.good())
|
|
{
|
|
mfem::out << " public key = " << pubkey << '\n'
|
|
<< " private key = " << privkey << '\n'
|
|
<< " trusted keys = " << trustedkeys << std::endl;
|
|
mfem::out << "Error setting GLVis client parameters.\n"
|
|
"Use the following GLVis script to create your GLVis keys:\n"
|
|
" bash glvis-keygen.sh [\"Your Name\"] [\"Your Email\"]"
|
|
<< std::endl;
|
|
}
|
|
}
|
|
num_glvis_sockets++;
|
|
return *params;
|
|
}
|
|
|
|
// static method
|
|
void socketstream::remove_socket()
|
|
{
|
|
if (num_glvis_sockets > 0)
|
|
{
|
|
num_glvis_sockets--;
|
|
if (num_glvis_sockets == 0)
|
|
{
|
|
delete params; params = NULL;
|
|
delete state; state = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
inline void socketstream::set_secure_socket(const GnuTLS_session_params &p)
|
|
{
|
|
buf__ = new GnuTLS_socketbuf(p);
|
|
std::iostream::rdbuf(buf__);
|
|
}
|
|
|
|
socketstream::socketstream(const GnuTLS_session_params &p)
|
|
: std::iostream(0), glvis_client(false)
|
|
{
|
|
set_secure_socket(p);
|
|
check_secure_socket();
|
|
}
|
|
|
|
#endif // MFEM_USE_GNUTLS
|
|
|
|
void socketstream::set_socket(bool secure)
|
|
{
|
|
glvis_client = secure;
|
|
if (secure)
|
|
{
|
|
#ifdef MFEM_USE_GNUTLS
|
|
set_secure_socket(add_socket());
|
|
#else
|
|
mfem_error("The secure option in class mfem::socketstream can only\n"
|
|
"be used when GnuTLS support is enabled.");
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
buf__ = new socketbuf;
|
|
std::iostream::rdbuf(buf__);
|
|
}
|
|
}
|
|
|
|
inline void socketstream::check_secure_socket()
|
|
{
|
|
#ifdef MFEM_USE_GNUTLS
|
|
if (((GnuTLS_socketbuf*)buf__)->gnutls_good()) { clear(); }
|
|
else { setstate(std::ios::failbit); }
|
|
#endif
|
|
}
|
|
|
|
socketstream::socketstream(bool secure) : std::iostream(0)
|
|
{
|
|
set_socket(secure);
|
|
if (secure) { check_secure_socket(); }
|
|
}
|
|
|
|
socketstream::socketstream(int s, bool secure) : std::iostream(0)
|
|
{
|
|
set_socket(secure);
|
|
buf__->attach(s);
|
|
if (secure) { check_secure_socket(); }
|
|
}
|
|
|
|
int socketstream::open(const char hostname[], int port)
|
|
{
|
|
int err_flag = buf__->open(hostname, port);
|
|
if (err_flag)
|
|
{
|
|
setstate(std::ios::failbit);
|
|
}
|
|
else
|
|
{
|
|
clear();
|
|
}
|
|
return err_flag;
|
|
}
|
|
|
|
socketstream::~socketstream()
|
|
{
|
|
delete buf__;
|
|
#ifdef MFEM_USE_GNUTLS
|
|
if (glvis_client) { remove_socket(); }
|
|
#endif
|
|
}
|
|
|
|
} // namespace mfem
|