mirror of
https://github.com/moparisthebest/socat
synced 2024-11-16 05:55:11 -05:00
1305 lines
42 KiB
C
1305 lines
42 KiB
C
/* source: xio-openssl.c */
|
|
/* Copyright Gerhard Rieger 2002-2008 */
|
|
/* Published under the GNU General Public License V.2, see file COPYING */
|
|
|
|
/* this file contains the implementation of the openssl addresses */
|
|
|
|
#include "xiosysincludes.h"
|
|
#if WITH_OPENSSL /* make this address configure dependend */
|
|
#include "xioopen.h"
|
|
|
|
#include "xio-fd.h"
|
|
#include "xio-socket.h" /* _xioopen_connect() */
|
|
#include "xio-listen.h"
|
|
#include "xio-ipapp.h"
|
|
#include "xio-openssl.h"
|
|
|
|
/* the openssl library requires a file descriptor for external communications.
|
|
so our best effort is to provide any possible kind of un*x file descriptor
|
|
(not only tcp, but also pipes, stdin, files...)
|
|
for tcp we want to provide support for socks and proxy.
|
|
read and write functions must use the openssl crypt versions.
|
|
but currently only plain tcp4 is implemented.
|
|
*/
|
|
|
|
/* Linux: "man 3 ssl" */
|
|
|
|
/* generate a simple openssl server for testing:
|
|
1) generate a private key
|
|
openssl genrsa -out server.key 1024
|
|
2) generate a self signed cert
|
|
openssl req -new -key server.key -x509 -days 3653 -out server.crt
|
|
enter fields...
|
|
3) generate the pem file
|
|
cat server.key server.crt >server.pem
|
|
openssl s_server (listens on 4433/tcp)
|
|
*/
|
|
|
|
/* static declaration of ssl's open function */
|
|
static int xioopen_openssl_connect(int argc, const char *argv[], struct opt *opts,
|
|
int xioflags, xiofile_t *fd, unsigned groups,
|
|
int dummy1, int dummy2, int dummy3);
|
|
|
|
/* static declaration of ssl's open function */
|
|
static int xioopen_openssl_listen(int argc, const char *argv[], struct opt *opts,
|
|
int xioflags, xiofile_t *fd, unsigned groups,
|
|
int dummy1, int dummy2, int dummy3);
|
|
static int openssl_SSL_ERROR_SSL(int level, const char *funcname);
|
|
static int openssl_handle_peer_certificate(struct single *xfd, bool opt_ver,
|
|
int level);
|
|
static int xioSSL_set_fd(struct single *xfd, int level);
|
|
static int xioSSL_connect(struct single *xfd, bool opt_ver, int level);
|
|
|
|
|
|
/* description record for inter-address ssl connect with 0 parameters */
|
|
static const struct xioaddr_inter_desc xiointer_openssl_connect0 = {
|
|
XIOADDR_INTER, /* this is an embedded address (inter module) */
|
|
"openssl-client", /* keyword for selecting this address type in xioopen calls
|
|
(canonical or main name) */
|
|
0, /* number of required parameters */
|
|
XIOBIT_ALL, /* data flow directions this address supports on API layer:
|
|
XIOBIT_RDONLY|XIOBIT_WRONLY|XIOBIT_RDWR */
|
|
GROUP_CHILD|GROUP_OPENSSL|GROUP_RETRY, /* bitwise OR of address groups this address belongs to.
|
|
You might have to specify a new group in xioopts.h */
|
|
XIOSHUT_OPENSSL,
|
|
XIOCLOSE_CLOSE,
|
|
xioopen_openssl_connect, /* a function pointer used to "open" these addresses.*/
|
|
0, /* an integer passed to xioopen_openssl; makes it possible to
|
|
use the same xioopen_openssl function for slightly different
|
|
address types. */
|
|
0, /* like previous argument */
|
|
0, /* like previous arguments, but pointer type.
|
|
No trailing comma or semicolon! */
|
|
XIOBIT_RDWR /* SSL is a bidirectional protocol */
|
|
HELP("") /* a text displayed from xio help function.
|
|
No trailing comma or semicolon!
|
|
only generates this text if WITH_HELP is != 0 */
|
|
} ;
|
|
|
|
/* description record for endpoint-address ssl connect with 2 parameters */
|
|
static const struct xioaddr_endpoint_desc xioendpoint_openssl_connect2 = {
|
|
XIOADDR_ENDPOINT, /* this is not an embedded address but a sys address */
|
|
"openssl-client", /* keyword for selecting this address type in xioopen calls
|
|
(canonical or main name) */
|
|
2, /* number of required parameters */
|
|
XIOBIT_ALL, /* data flow directions this address supports on API layer:
|
|
XIOBIT_RDONLY|XIOBIT_WRONLY|XIOBIT_RDWR */
|
|
GROUP_FD|GROUP_SOCKET|GROUP_SOCK_IP4|GROUP_SOCK_IP6|GROUP_IP_TCP|GROUP_CHILD|GROUP_OPENSSL|GROUP_RETRY, /* bitwise OR of address groups this address belongs to.
|
|
You might have to specify a new group in xioopts.h */
|
|
XIOSHUT_OPENSSL,
|
|
XIOCLOSE_CLOSE,
|
|
xioopen_openssl_connect, /* a function pointer used to "open" these addresses.*/
|
|
0, /* an integer passed to xioopen_openssl; makes it possible to
|
|
use the same xioopen_openssl function for slightly different
|
|
address types. */
|
|
0, /* like previous argument */
|
|
0 /* like previous arguments, but pointer type.
|
|
No trailing comma or semicolon! */
|
|
HELP(":<host>:<port>") /* a text displayed from xio help function.
|
|
No trailing comma or semicolon!
|
|
only generates this text if WITH_HELP is != 0 */
|
|
} ;
|
|
|
|
/* array containing ssl connect description records */
|
|
const union xioaddr_desc *xioaddrs_openssl_connect[] = {
|
|
(union xioaddr_desc *)&xiointer_openssl_connect0,
|
|
(union xioaddr_desc *)&xioendpoint_openssl_connect2,
|
|
NULL
|
|
};
|
|
|
|
|
|
#if WITH_LISTEN
|
|
/* description record for inter-address ssl listen */
|
|
static const struct xioaddr_inter_desc xiointer_openssl_listen0 = {
|
|
XIOADDR_INTER, /* this is an embedded address (inter module) */
|
|
"openssl-server", /* keyword for selecting this address type in xioopen calls
|
|
(canonical or main name) */
|
|
0, /* number of required parameters */
|
|
XIOBIT_ALL, /* data flow directions this address supports on API layer:
|
|
XIOBIT_RDONLY|XIOBIT_WRONLY|XIOBIT_RDWR */
|
|
GROUP_CHILD|GROUP_OPENSSL|GROUP_RETRY, /* bitwise OR of address groups this address belongs to.
|
|
You might have to specify a new group in xioopts.h */
|
|
XIOSHUT_OPENSSL,
|
|
XIOCLOSE_CLOSE,
|
|
xioopen_openssl_listen, /* a function pointer used to "open" these addresses.*/
|
|
0, /* an integer passed to xioopen_openssl_listen; makes it possible to
|
|
use the same xioopen_openssl_listen function for slightly different
|
|
address types. */
|
|
0, /* like previous argument */
|
|
0, /* like previous arguments, but pointer type.
|
|
No trailing comma or semicolon! */
|
|
XIOBIT_RDWR /* SSL is a bidirectional protocol */
|
|
HELP("") /* a text displayed from xio help function.
|
|
No trailing comma or semicolon!
|
|
only generates this text if WITH_HELP is != 0 */
|
|
} ;
|
|
|
|
/* description record for enpoint-address ssl listen */
|
|
static const struct xioaddr_endpoint_desc xioendpoint_openssl_listen1 = {
|
|
XIOADDR_ENDPOINT, /* this is not an embedded module but a sys module */
|
|
"openssl-server", /* keyword for selecting this address type in xioopen calls
|
|
(canonical or main name) */
|
|
1, /* number of required parameters */
|
|
XIOBIT_ALL, /* data flow directions this address supports on API layer:
|
|
XIOBIT_RDONLY|XIOBIT_WRONLY|XIOBIT_RDWR */
|
|
GROUP_FD|GROUP_SOCKET|GROUP_SOCK_IP4|GROUP_SOCK_IP6|GROUP_IP_TCP|GROUP_LISTEN|GROUP_CHILD|GROUP_RANGE|GROUP_OPENSSL|GROUP_RETRY, /* bitwise OR of address groups this address belongs to.
|
|
You might have to specify a new group in xioopts.h */
|
|
XIOSHUT_OPENSSL,
|
|
XIOCLOSE_CLOSE,
|
|
xioopen_openssl_listen, /* a function pointer used to "open" these addresses.*/
|
|
0, /* an integer passed to xioopen_openssl_listen; makes it possible to
|
|
use the same xioopen_openssl_listen function for slightly different
|
|
address types. */
|
|
0, /* like previous argument */
|
|
0 /* like previous arguments, but pointer type.
|
|
No trailing comma or semicolon! */
|
|
HELP(":<port>") /* a text displayed from xio help function.
|
|
No trailing comma or semicolon!
|
|
only generates this text if WITH_HELP is != 0 */
|
|
} ;
|
|
|
|
/* array containing ssl listen description records */
|
|
const union xioaddr_desc *xioaddrs_openssl_listen[] = {
|
|
(union xioaddr_desc *)&xiointer_openssl_listen0,
|
|
(union xioaddr_desc *)&xioendpoint_openssl_listen1,
|
|
NULL
|
|
};
|
|
#endif /* WITH_LISTEN */
|
|
|
|
/* both client and server */
|
|
const struct optdesc opt_openssl_cipherlist = { "openssl-cipherlist", "ciphers", OPT_OPENSSL_CIPHERLIST, GROUP_OPENSSL, PH_SPEC, TYPE_STRING, OFUNC_SPEC };
|
|
const struct optdesc opt_openssl_method = { "openssl-method", "method", OPT_OPENSSL_METHOD, GROUP_OPENSSL, PH_SPEC, TYPE_STRING, OFUNC_SPEC };
|
|
const struct optdesc opt_openssl_verify = { "openssl-verify", "verify", OPT_OPENSSL_VERIFY, GROUP_OPENSSL, PH_SPEC, TYPE_BOOL, OFUNC_SPEC };
|
|
const struct optdesc opt_openssl_certificate = { "openssl-certificate", "cert", OPT_OPENSSL_CERTIFICATE, GROUP_OPENSSL, PH_SPEC, TYPE_FILENAME, OFUNC_SPEC };
|
|
const struct optdesc opt_openssl_key = { "openssl-key", "key", OPT_OPENSSL_KEY, GROUP_OPENSSL, PH_SPEC, TYPE_FILENAME, OFUNC_SPEC };
|
|
const struct optdesc opt_openssl_dhparam = { "openssl-dhparam", "dh", OPT_OPENSSL_DHPARAM, GROUP_OPENSSL, PH_SPEC, TYPE_FILENAME, OFUNC_SPEC };
|
|
const struct optdesc opt_openssl_cafile = { "openssl-cafile", "cafile", OPT_OPENSSL_CAFILE, GROUP_OPENSSL, PH_SPEC, TYPE_FILENAME, OFUNC_SPEC };
|
|
const struct optdesc opt_openssl_capath = { "openssl-capath", "capath", OPT_OPENSSL_CAPATH, GROUP_OPENSSL, PH_SPEC, TYPE_FILENAME, OFUNC_SPEC };
|
|
const struct optdesc opt_openssl_egd = { "openssl-egd", "egd", OPT_OPENSSL_EGD, GROUP_OPENSSL, PH_SPEC, TYPE_FILENAME, OFUNC_SPEC };
|
|
const struct optdesc opt_openssl_pseudo = { "openssl-pseudo", "pseudo", OPT_OPENSSL_PSEUDO, GROUP_OPENSSL, PH_SPEC, TYPE_BOOL, OFUNC_SPEC };
|
|
#if WITH_FIPS
|
|
const struct optdesc opt_openssl_fips = { "openssl-fips", "fips", OPT_OPENSSL_FIPS, GROUP_OPENSSL, PH_SPEC, TYPE_BOOL, OFUNC_SPEC };
|
|
#endif
|
|
|
|
|
|
/* If FIPS is compiled in, we need to track if the user asked for FIPS mode.
|
|
* On forks, the FIPS mode must be reset by a disable, then enable since
|
|
* FIPS tracks the process ID that initializes things.
|
|
* If FIPS is not compiled in, no tracking variable is needed
|
|
* and we make the reset code compile out. This keeps the
|
|
* rest of the code below free of FIPS related #ifs
|
|
*/
|
|
#if WITH_FIPS
|
|
static bool xio_openssl_fips = false;
|
|
int xio_reset_fips_mode(void) {
|
|
if (xio_openssl_fips) {
|
|
if(!sycFIPS_mode_set(0) || !sycFIPS_mode_set(1)) {
|
|
ERR_load_crypto_strings();
|
|
ERR_print_errors(BIO_new_fp(stderr,BIO_NOCLOSE));
|
|
Error("Failed to reset OpenSSL FIPS mode");
|
|
xio_openssl_fips = false;
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
#else
|
|
#define xio_reset_fips_mode() 0
|
|
#endif
|
|
|
|
/* the open function for OpenSSL client */
|
|
static int
|
|
xioopen_openssl_connect(int argc,
|
|
const char *argv[], /* the arguments in the address string */
|
|
struct opt *opts,
|
|
int xioflags, /* is the open meant for reading (0),
|
|
writing (1), or both (2) ? */
|
|
xiofile_t *xxfd, /* a xio file descriptor structure,
|
|
already allocated */
|
|
unsigned groups, /* the matching address groups... */
|
|
int dummy1, /* first transparent integer value from
|
|
addr_openssl */
|
|
int dummy2, /* second transparent integer value from
|
|
addr_openssl */
|
|
int dummy3) /* transparent pointer value from
|
|
addr_openssl */
|
|
{
|
|
struct single *xfd = &xxfd->stream;
|
|
int rw = (xioflags&XIO_ACCMODE);
|
|
struct opt *opts0 = NULL;
|
|
const char *hostname, *portname = NULL;
|
|
int pf = PF_UNSPEC;
|
|
int ipproto = IPPROTO_TCP;
|
|
int socktype = SOCK_STREAM;
|
|
bool dofork = false;
|
|
union sockaddr_union us_sa, *us = &us_sa;
|
|
union sockaddr_union them_sa, *them = &them_sa;
|
|
socklen_t uslen = sizeof(us_sa);
|
|
socklen_t themlen = sizeof(them_sa);
|
|
bool needbind = false;
|
|
bool lowport = false;
|
|
int level;
|
|
/*0 SSL_CTX* ctx;*/
|
|
bool opt_ver = true; /* verify peer certificate */
|
|
char *opt_cert = NULL; /* file name of client certificate */
|
|
int result;
|
|
|
|
if (!(xioflags & XIO_MAYCONVERT)) {
|
|
Error("address with data processing not allowed here");
|
|
return STAT_NORETRY;
|
|
}
|
|
xfd->flags |= XIO_DOESCONVERT;
|
|
|
|
xfd->howtoshut = XIOSHUT_OPENSSL;
|
|
xfd->howtoclose = XIOCLOSE_CLOSE;
|
|
|
|
/* we support two forms of openssl-connect */
|
|
if (argc == 3) {
|
|
hostname = argv[1];
|
|
portname = argv[2];
|
|
|
|
/* a "terminal" form where we build a tcp connection to given host and
|
|
port */
|
|
|
|
applyopts_single(xfd, opts, PH_INIT);
|
|
applyopts(-1, opts, PH_INIT);
|
|
|
|
retropt_bool(opts, OPT_FORK, &dofork);
|
|
|
|
retropt_string(opts, OPT_OPENSSL_CERTIFICATE, &opt_cert);
|
|
|
|
result =
|
|
_xioopen_openssl_prepare(opts, xfd, false, &opt_ver, opt_cert,
|
|
&xfd->para.openssl.ctx);
|
|
if (result != STAT_OK) return STAT_NORETRY;
|
|
|
|
result =
|
|
_xioopen_ipapp_prepare(opts, &opts0, hostname, portname, &pf, ipproto,
|
|
xfd->para.socket.ip.res_opts[1],
|
|
xfd->para.socket.ip.res_opts[0],
|
|
them, &themlen, us, &uslen,
|
|
&needbind, &lowport, socktype);
|
|
if (result != STAT_OK) return STAT_NORETRY;
|
|
} else if (argc = 1) {
|
|
|
|
/* or a "non terminal" address without required parameters */
|
|
if (xfd->wfd < 0) {
|
|
Error("openssl-connect without hostname and port must be an embedded address");
|
|
return STAT_NORETRY;
|
|
}
|
|
|
|
if (applyopts_single(xfd, opts, PH_INIT) < 0) return -1;
|
|
applyopts(-1, opts, PH_INIT);
|
|
|
|
retropt_bool(opts, OPT_FORK, &dofork);
|
|
retropt_string(opts, OPT_OPENSSL_CERTIFICATE, &opt_cert);
|
|
|
|
result =
|
|
_xioopen_openssl_prepare(opts, xfd, false, &opt_ver, opt_cert,
|
|
&xfd->para.openssl.ctx);
|
|
if (result != STAT_OK) return STAT_NORETRY;
|
|
|
|
retropt_bool(opts, OPT_FORK, &dofork);
|
|
|
|
} else {
|
|
Error1("%s: 0 or 2 parameters required", argv[0]);
|
|
return STAT_NORETRY;
|
|
}
|
|
|
|
if (xioopts.logopt == 'm') {
|
|
Info("starting connect loop, switching to syslog");
|
|
diag_set('y', xioopts.syslogfac); xioopts.logopt = 'y';
|
|
} else {
|
|
Info("starting connect loop");
|
|
}
|
|
|
|
do { /* loop over failed connect and SSL handshake attempts */
|
|
|
|
#if WITH_RETRY
|
|
if (xfd->forever || xfd->retry) {
|
|
level = E_INFO;
|
|
} else
|
|
#endif /* WITH_RETRY */
|
|
level = E_ERROR;
|
|
|
|
/*!!! this belongs only to "old" openssl-connect form */
|
|
if (portname) {
|
|
/* this cannot fork because we retrieved fork option above */
|
|
result =
|
|
_xioopen_connect(xfd,
|
|
needbind?(struct sockaddr *)us:NULL, sizeof(*us),
|
|
(struct sockaddr *)them, themlen,
|
|
opts, pf, socktype, ipproto, lowport, level);
|
|
switch (result) {
|
|
case STAT_OK: break;
|
|
#if WITH_RETRY
|
|
case STAT_RETRYLATER:
|
|
case STAT_RETRYNOW:
|
|
if (xfd->forever || xfd->retry) {
|
|
dropopts(opts, PH_ALL); opts = copyopts(opts0, GROUP_ALL);
|
|
if (result == STAT_RETRYLATER) {
|
|
Nanosleep(&xfd->intervall, NULL);
|
|
}
|
|
--xfd->retry;
|
|
continue;
|
|
}
|
|
return STAT_NORETRY;
|
|
#endif /* WITH_RETRY */
|
|
default:
|
|
return result;
|
|
}
|
|
xfd->wfd = xfd->rfd;
|
|
}
|
|
|
|
/*! isn't this too early? */
|
|
if ((result = _xio_openlate(xfd, opts)) < 0) {
|
|
return result;
|
|
}
|
|
|
|
result =
|
|
_xioopen_openssl_connect(xfd, opt_ver, xfd->para.openssl.ctx, level);
|
|
switch (result) {
|
|
case STAT_OK: break;
|
|
#if WITH_RETRY
|
|
case STAT_RETRYLATER:
|
|
case STAT_RETRYNOW:
|
|
if (xfd->forever || xfd->retry) {
|
|
Close(xfd->rfd);
|
|
Close(xfd->wfd);
|
|
dropopts(opts, PH_ALL); opts = copyopts(opts0, GROUP_ALL);
|
|
if (result == STAT_RETRYLATER) {
|
|
Nanosleep(&xfd->intervall, NULL);
|
|
}
|
|
--xfd->retry;
|
|
continue;
|
|
}
|
|
#endif /* WITH_RETRY */
|
|
default: return STAT_NORETRY;
|
|
}
|
|
|
|
if (dofork) {
|
|
xiosetchilddied(); /* set SIGCHLD handler */
|
|
}
|
|
|
|
#if WITH_RETRY
|
|
if (dofork) {
|
|
pid_t pid;
|
|
int level = E_ERROR;
|
|
if (xfd->forever || xfd->retry) {
|
|
level = E_WARN;
|
|
}
|
|
while ((pid = xio_fork(false, level)) < 0) {
|
|
if (xfd->forever || --xfd->retry) {
|
|
Nanosleep(&xfd->intervall, NULL); continue;
|
|
}
|
|
return STAT_RETRYLATER;
|
|
}
|
|
|
|
if (pid == 0) { /* child process */
|
|
xfd->forever = false; xfd->retry = 0;
|
|
break;
|
|
}
|
|
|
|
/* parent process */
|
|
Notice1("forked off child process "F_pid, pid);
|
|
Close(xfd->rfd);
|
|
Close(xfd->wfd);
|
|
sycSSL_free(xfd->para.openssl.ssl);
|
|
xfd->para.openssl.ssl = NULL;
|
|
/* with and without retry */
|
|
Nanosleep(&xfd->intervall, NULL);
|
|
dropopts(opts, PH_ALL); opts = copyopts(opts0, GROUP_ALL);
|
|
continue; /* with next socket() bind() connect() */
|
|
}
|
|
#endif /* WITH_RETRY */
|
|
break;
|
|
} while (true); /* drop out on success */
|
|
|
|
Notice1("SSL connection using %s", SSL_get_cipher(xfd->para.openssl.ssl));
|
|
|
|
/* fill in the fd structure */
|
|
return STAT_OK;
|
|
}
|
|
|
|
|
|
/* this function is typically called within the OpenSSL client fork/retry loop.
|
|
xfd must be of type DATA_OPENSSL, and its fd must be set with a valid file
|
|
descriptor. this function then performs all SSL related step to make a valid
|
|
SSL connection from an FD and a CTX. */
|
|
int _xioopen_openssl_connect(struct single *xfd,
|
|
bool opt_ver,
|
|
SSL_CTX *ctx,
|
|
int level) {
|
|
SSL *ssl;
|
|
unsigned long err;
|
|
int result;
|
|
|
|
/* create a SSL object */
|
|
if ((ssl = sycSSL_new(ctx)) == NULL) {
|
|
if (ERR_peek_error() == 0) Msg(level, "SSL_new() failed");
|
|
while (err = ERR_get_error()) {
|
|
Msg1(level, "SSL_new(): %s", ERR_error_string(err, NULL));
|
|
}
|
|
/*Error("SSL_new()");*/
|
|
return STAT_RETRYLATER;
|
|
}
|
|
xfd->para.openssl.ssl = ssl;
|
|
|
|
result = xioSSL_set_fd(xfd, level);
|
|
if (result != STAT_OK) {
|
|
sycSSL_free(xfd->para.openssl.ssl);
|
|
xfd->para.openssl.ssl = NULL;
|
|
return result;
|
|
}
|
|
|
|
result = xioSSL_connect(xfd, opt_ver, level);
|
|
if (result != STAT_OK) {
|
|
sycSSL_free(xfd->para.openssl.ssl);
|
|
xfd->para.openssl.ssl = NULL;
|
|
return result;
|
|
}
|
|
|
|
result = openssl_handle_peer_certificate(xfd, opt_ver, level);
|
|
if (result != STAT_OK) {
|
|
sycSSL_free(xfd->para.openssl.ssl);
|
|
xfd->para.openssl.ssl = NULL;
|
|
return result;
|
|
}
|
|
|
|
return STAT_OK;
|
|
}
|
|
|
|
|
|
#if WITH_LISTEN
|
|
|
|
static int
|
|
xioopen_openssl_listen(int argc,
|
|
const char *argv[], /* the arguments in the address string */
|
|
struct opt *opts,
|
|
int xioflags, /* is the open meant for reading (0),
|
|
writing (1), or both (2) ? */
|
|
xiofile_t *xxfd, /* a xio file descriptor structure,
|
|
already allocated */
|
|
unsigned groups, /* the matching address groups... */
|
|
int dummy1, /* first transparent integer value from
|
|
addr_openssl */
|
|
int dummy2, /* second transparent integer value from
|
|
addr_openssl */
|
|
int dummy3) /* transparent pointer value from
|
|
addr_openssl */
|
|
{
|
|
struct single *xfd = &xxfd->stream;
|
|
const char *portname = NULL;
|
|
struct opt *opts0 = NULL;
|
|
union sockaddr_union us_sa, *us = &us_sa;
|
|
socklen_t uslen = sizeof(us_sa);
|
|
int pf;
|
|
int socktype = SOCK_STREAM;
|
|
int ipproto = IPPROTO_TCP;
|
|
/*! lowport? */
|
|
int level;
|
|
/*0 SSL_CTX* ctx;*/
|
|
bool opt_ver = true; /* verify peer certificate - changed with 1.6.0 */
|
|
char *opt_cert = NULL; /* file name of server certificate */
|
|
int result;
|
|
|
|
if (!(xioflags & XIO_MAYCONVERT)) {
|
|
Error("address with data processing not allowed here");
|
|
return STAT_NORETRY;
|
|
}
|
|
xfd->flags |= XIO_DOESCONVERT;
|
|
|
|
#if WITH_IP4 && WITH_IP6
|
|
pf = xioopts.default_ip=='6'?PF_INET6:PF_INET;
|
|
#elif WITH_IP6
|
|
pf = PF_INET6;
|
|
#else
|
|
pf = PF_INET;
|
|
#endif
|
|
|
|
if (argc == 2) {
|
|
|
|
portname = argv[1];
|
|
if (applyopts_single(xfd, opts, PH_INIT) < 0) return -1;
|
|
applyopts(-1, opts, PH_INIT);
|
|
|
|
retropt_string(opts, OPT_OPENSSL_CERTIFICATE, &opt_cert);
|
|
if (opt_cert == NULL) {
|
|
Warn("no certificate given; consider option \"cert\"");
|
|
}
|
|
|
|
result =
|
|
_xioopen_openssl_prepare(opts, xfd, true, &opt_ver, opt_cert,
|
|
&xfd->para.openssl.ctx);
|
|
if (result != STAT_OK) return STAT_NORETRY;
|
|
|
|
if (_xioopen_ipapp_listen_prepare(opts, &opts0, portname, &pf, ipproto,
|
|
xfd->para.socket.ip.res_opts[1],
|
|
xfd->para.socket.ip.res_opts[0],
|
|
us, &uslen, socktype)
|
|
!= STAT_OK) {
|
|
return STAT_NORETRY;
|
|
}
|
|
|
|
} else if (argc == 1) {
|
|
if (xfd->rfd < 0) {
|
|
Error("openssl-listen without port must be an embedded address");
|
|
return STAT_NORETRY;
|
|
}
|
|
|
|
if (applyopts_single(xfd, opts, PH_INIT) < 0) return -1;
|
|
applyopts(-1, opts, PH_INIT);
|
|
|
|
retropt_string(opts, OPT_OPENSSL_CERTIFICATE, &opt_cert);
|
|
if (opt_cert == NULL) {
|
|
Warn("no certificate given; consider option \"cert\"");
|
|
}
|
|
|
|
applyopts(-1, opts, PH_EARLY);
|
|
|
|
result =
|
|
_xioopen_openssl_prepare(opts, xfd, true, &opt_ver, opt_cert,
|
|
&xfd->para.openssl.ctx);
|
|
if (result != STAT_OK) return STAT_NORETRY;
|
|
|
|
} else {
|
|
Error1("%s: 1 parameter required", argv[0]);
|
|
return -1;
|
|
}
|
|
|
|
xfd->dtype = XIODATA_OPENSSL;
|
|
|
|
while (true) { /* loop over failed attempts */
|
|
|
|
#if WITH_RETRY
|
|
if (xfd->forever || xfd->retry) {
|
|
level = E_INFO;
|
|
} else
|
|
#endif /* WITH_RETRY */
|
|
level = E_ERROR;
|
|
|
|
if (portname) {
|
|
/* tcp listen; this can fork() for us; it only returns on error or on
|
|
successful establishment of tcp connection */
|
|
result = _xioopen_listen(xfd, xioflags,
|
|
(struct sockaddr *)us, uslen,
|
|
opts, pf, socktype, IPPROTO_TCP,
|
|
#if WITH_RETRY
|
|
(xfd->retry||xfd->forever)?E_INFO:E_ERROR
|
|
#else
|
|
E_ERROR
|
|
#endif /* WITH_RETRY */
|
|
);
|
|
/*! not sure if we should try again on retry/forever */
|
|
switch (result) {
|
|
case STAT_OK: break;
|
|
#if WITH_RETRY
|
|
case STAT_RETRYLATER:
|
|
case STAT_RETRYNOW:
|
|
if (xfd->forever || xfd->retry) {
|
|
dropopts(opts, PH_ALL); opts = copyopts(opts0, GROUP_ALL);
|
|
if (result == STAT_RETRYLATER) {
|
|
Nanosleep(&xfd->intervall, NULL);
|
|
}
|
|
dropopts(opts, PH_ALL); opts = copyopts(opts0, GROUP_ALL);
|
|
--xfd->retry;
|
|
continue;
|
|
}
|
|
return STAT_NORETRY;
|
|
#endif /* WITH_RETRY */
|
|
default:
|
|
return result;
|
|
}
|
|
xfd->wfd = xfd->rfd;
|
|
}
|
|
result =
|
|
_xioopen_openssl_listen(xfd, opt_ver, xfd->para.openssl.ctx, level);
|
|
switch (result) {
|
|
case STAT_OK: break;
|
|
#if WITH_RETRY
|
|
case STAT_RETRYLATER:
|
|
case STAT_RETRYNOW:
|
|
if (xfd->forever || xfd->retry) {
|
|
dropopts(opts, PH_ALL); opts = copyopts(opts0, GROUP_ALL);
|
|
if (result == STAT_RETRYLATER) {
|
|
Nanosleep(&xfd->intervall, NULL);
|
|
}
|
|
dropopts(opts, PH_ALL); opts = copyopts(opts0, GROUP_ALL);
|
|
--xfd->retry;
|
|
continue;
|
|
}
|
|
return STAT_NORETRY;
|
|
#endif /* WITH_RETRY */
|
|
default:
|
|
return result;
|
|
}
|
|
|
|
Notice1("SSL connection using %s",
|
|
SSL_get_cipher(xfd->para.openssl.ssl));
|
|
break;
|
|
|
|
} /* drop out on success */
|
|
|
|
/* fill in the fd structure */
|
|
|
|
return STAT_OK;
|
|
}
|
|
|
|
|
|
int _xioopen_openssl_listen(struct single *xfd,
|
|
bool opt_ver,
|
|
SSL_CTX *ctx,
|
|
int level) {
|
|
char error_string[120];
|
|
unsigned long err;
|
|
int errint, ret;
|
|
|
|
/* create an SSL object */
|
|
if ((xfd->para.openssl.ssl = sycSSL_new(ctx)) == NULL) {
|
|
if (ERR_peek_error() == 0) Msg(level, "SSL_new() failed");
|
|
while (err = ERR_get_error()) {
|
|
Msg1(level, "SSL_new(): %s", ERR_error_string(err, NULL));
|
|
}
|
|
/*Error("SSL_new()");*/
|
|
return STAT_NORETRY;
|
|
}
|
|
|
|
/* assign the network connection to the SSL object */
|
|
ret = xioSSL_set_fd(xfd, level);
|
|
if (ret != STAT_OK) {
|
|
sycSSL_free(xfd->para.openssl.ssl);
|
|
xfd->para.openssl.ssl = NULL;
|
|
return ret;
|
|
}
|
|
|
|
#if WITH_DEBUG
|
|
{
|
|
int i = 0;
|
|
const char *ciphers = NULL;
|
|
Debug("available ciphers:");
|
|
do {
|
|
ciphers = SSL_get_cipher_list(xfd->para.openssl.ssl, i);
|
|
if (ciphers == NULL) break;
|
|
Debug2("CIPHERS pri=%d: %s", i, ciphers);
|
|
++i;
|
|
} while (1);
|
|
}
|
|
#endif /* WITH_DEBUG */
|
|
|
|
/* connect via SSL by performing handshake */
|
|
if ((ret = sycSSL_accept(xfd->para.openssl.ssl)) <= 0) {
|
|
/*if (ERR_peek_error() == 0) Msg(level, "SSL_accept() failed");*/
|
|
errint = SSL_get_error(xfd->para.openssl.ssl, ret);
|
|
switch (errint) {
|
|
case SSL_ERROR_NONE:
|
|
Msg(level, "ok"); break;
|
|
case SSL_ERROR_ZERO_RETURN:
|
|
Msg(level, "connection closed (wrong version number?)"); break;
|
|
case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE:
|
|
case SSL_ERROR_WANT_CONNECT:
|
|
case SSL_ERROR_WANT_X509_LOOKUP:
|
|
Msg(level, "nonblocking operation did not complete"); break; /*!*/
|
|
case SSL_ERROR_SYSCALL:
|
|
if (ERR_peek_error() == 0) {
|
|
if (ret == 0) {
|
|
Msg(level, "SSL_accept(): socket closed by peer");
|
|
} else if (ret == -1) {
|
|
Msg1(level, "SSL_accept(): %s", strerror(errno));
|
|
}
|
|
} else {
|
|
Msg(level, "I/O error"); /*!*/
|
|
while (err = ERR_get_error()) {
|
|
ERR_error_string_n(err, error_string, sizeof(error_string));
|
|
Msg4(level, "SSL_accept(): %s / %s / %s / %s", error_string,
|
|
ERR_lib_error_string(err), ERR_func_error_string(err),
|
|
ERR_reason_error_string(err));
|
|
}
|
|
/* Msg1(level, "SSL_connect(): %s", ERR_error_string(e, buf));*/
|
|
}
|
|
break;
|
|
case SSL_ERROR_SSL:
|
|
/*ERR_print_errors_fp(stderr);*/
|
|
openssl_SSL_ERROR_SSL(level, "SSL_accept");
|
|
break;
|
|
default:
|
|
Msg(level, "unknown error");
|
|
}
|
|
|
|
return STAT_RETRYLATER;
|
|
}
|
|
|
|
if (openssl_handle_peer_certificate(xfd, opt_ver, E_ERROR/*!*/) < 0) {
|
|
return STAT_NORETRY;
|
|
}
|
|
|
|
return STAT_OK;
|
|
}
|
|
|
|
#endif /* WITH_LISTEN */
|
|
|
|
|
|
int
|
|
_xioopen_openssl_prepare(struct opt *opts,
|
|
struct single *xfd,/* a xio file descriptor
|
|
structure, already allocated
|
|
*/
|
|
bool server, /* SSL client: false */
|
|
bool *opt_ver,
|
|
const char *opt_cert,
|
|
SSL_CTX **ctx)
|
|
{
|
|
bool opt_fips = false;
|
|
SSL_METHOD *method;
|
|
char *me_str = NULL; /* method string */
|
|
char *ci_str = NULL; /* cipher string */
|
|
char *opt_key = NULL; /* file name of client private key */
|
|
char *opt_dhparam = NULL; /* file name of DH params */
|
|
char *opt_cafile = NULL; /* certificate authority file */
|
|
char *opt_capath = NULL; /* certificate authority directory */
|
|
char *opt_egd = NULL; /* entropy gathering daemon socket path */
|
|
bool opt_pseudo = false; /* use pseudo entropy if nothing else */
|
|
unsigned long err;
|
|
int result;
|
|
|
|
xfd->dtype = XIODATA_OPENSSL;
|
|
|
|
retropt_bool(opts, OPT_OPENSSL_FIPS, &opt_fips);
|
|
retropt_string(opts, OPT_OPENSSL_METHOD, &me_str);
|
|
retropt_string(opts, OPT_OPENSSL_CIPHERLIST, &ci_str);
|
|
retropt_bool(opts, OPT_OPENSSL_VERIFY, opt_ver);
|
|
retropt_string(opts, OPT_OPENSSL_CAFILE, &opt_cafile);
|
|
retropt_string(opts, OPT_OPENSSL_CAPATH, &opt_capath);
|
|
retropt_string(opts, OPT_OPENSSL_KEY, &opt_key);
|
|
retropt_string(opts, OPT_OPENSSL_DHPARAM, &opt_dhparam);
|
|
retropt_string(opts, OPT_OPENSSL_EGD, &opt_egd);
|
|
retropt_bool(opts,OPT_OPENSSL_PSEUDO, &opt_pseudo);
|
|
|
|
#if WITH_FIPS
|
|
if (opt_fips) {
|
|
if (!sycFIPS_mode_set(1)) {
|
|
ERR_load_crypto_strings();
|
|
ERR_print_errors(BIO_new_fp(stderr,BIO_NOCLOSE));
|
|
Error("Failed to set FIPS mode");
|
|
} else {
|
|
xio_openssl_fips = true;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
OpenSSL_add_all_algorithms();
|
|
OpenSSL_add_all_ciphers();
|
|
OpenSSL_add_all_digests();
|
|
sycSSL_load_error_strings();
|
|
|
|
/* OpenSSL preparation */
|
|
sycSSL_library_init();
|
|
|
|
/*! actions_to_seed_PRNG();*/
|
|
|
|
if (!server) {
|
|
if (me_str != 0) {
|
|
if (!strcasecmp(me_str, "SSLv2") || !strcasecmp(me_str, "SSL2")) {
|
|
method = sycSSLv2_client_method();
|
|
} else if (!strcasecmp(me_str, "SSLv3") || !strcasecmp(me_str, "SSL3")) {
|
|
method = sycSSLv3_client_method();
|
|
} else if (!strcasecmp(me_str, "SSLv23") || !strcasecmp(me_str, "SSL23") ||
|
|
!strcasecmp(me_str, "SSL")) {
|
|
method = sycSSLv23_client_method();
|
|
} else if (!strcasecmp(me_str, "TLSv1") || !strcasecmp(me_str, "TLS1") ||
|
|
!strcasecmp(me_str, "TLS")) {
|
|
method = sycTLSv1_client_method();
|
|
} else {
|
|
Error1("openssl-method=\"%s\": unknown method", me_str);
|
|
method = sycSSLv23_client_method()/*!*/;
|
|
}
|
|
} else {
|
|
method = sycSSLv23_client_method()/*!*/;
|
|
}
|
|
} else /* server */ {
|
|
if (me_str != 0) {
|
|
if (!strcasecmp(me_str, "SSLv2") || !strcasecmp(me_str, "SSL2")) {
|
|
method = sycSSLv2_server_method();
|
|
} else if (!strcasecmp(me_str, "SSLv3") || !strcasecmp(me_str, "SSL3")) {
|
|
method = sycSSLv3_server_method();
|
|
} else if (!strcasecmp(me_str, "SSLv23") || !strcasecmp(me_str, "SSL23") ||
|
|
!strcasecmp(me_str, "SSL")) {
|
|
method = sycSSLv23_server_method();
|
|
} else if (!strcasecmp(me_str, "TLSv1") || !strcasecmp(me_str, "TLS1") ||
|
|
!strcasecmp(me_str, "TLS")) {
|
|
method = sycTLSv1_server_method();
|
|
} else {
|
|
Error1("openssl-method=\"%s\": unknown method", me_str);
|
|
method = sycSSLv23_server_method()/*!*/;
|
|
}
|
|
} else {
|
|
method = sycSSLv23_server_method()/*!*/;
|
|
}
|
|
}
|
|
|
|
if (opt_egd) {
|
|
sycRAND_egd(opt_egd);
|
|
}
|
|
|
|
if (opt_pseudo) {
|
|
long int randdata;
|
|
/* initialize libc random from actual microseconds */
|
|
struct timeval tv;
|
|
struct timezone tz;
|
|
tz.tz_minuteswest = 0;
|
|
tz.tz_dsttime = 0;
|
|
if ((result = Gettimeofday(&tv, &tz)) < 0) {
|
|
Warn2("gettimeofday(%p, {0,0}): %s", &tv, strerror(errno));
|
|
}
|
|
srandom(tv.tv_sec*1000000+tv.tv_usec);
|
|
|
|
while (!RAND_status()) {
|
|
randdata = random();
|
|
Debug2("RAND_seed(0x{%lx}, "F_Zu")",
|
|
randdata, sizeof(randdata));
|
|
RAND_seed(&randdata, sizeof(randdata));
|
|
}
|
|
}
|
|
|
|
if ((*ctx = sycSSL_CTX_new(method)) == NULL) {
|
|
if (ERR_peek_error() == 0) Error("SSL_CTX_new()");
|
|
while (err = ERR_get_error()) {
|
|
Error1("SSL_CTX_new(): %s", ERR_error_string(err, NULL));
|
|
}
|
|
|
|
/*ERR_clear_error;*/
|
|
return STAT_RETRYLATER;
|
|
}
|
|
|
|
if (opt_cafile != NULL || opt_capath != NULL) {
|
|
if (sycSSL_CTX_load_verify_locations(*ctx, opt_cafile, opt_capath) != 1) {
|
|
int result;
|
|
|
|
if ((result =
|
|
openssl_SSL_ERROR_SSL(E_ERROR, "SSL_CTX_load_verify_locations"))
|
|
!= STAT_OK) {
|
|
SSL_CTX_free(*ctx); *ctx = NULL;
|
|
return STAT_RETRYLATER;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (opt_cert) {
|
|
BIO *bio;
|
|
DH *dh;
|
|
|
|
if (sycSSL_CTX_use_certificate_chain_file(*ctx, opt_cert) <= 0) {
|
|
/*! trace functions */
|
|
/*0 ERR_print_errors_fp(stderr);*/
|
|
if (ERR_peek_error() == 0)
|
|
Error2("SSL_CTX_use_certificate_file(%p, \"%s\", SSL_FILETYPE_PEM) failed",
|
|
*ctx, opt_cert);
|
|
while (err = ERR_get_error()) {
|
|
Error1("SSL_CTX_use_certificate_file(): %s",
|
|
ERR_error_string(err, NULL));
|
|
}
|
|
return STAT_RETRYLATER;
|
|
}
|
|
|
|
if (sycSSL_CTX_use_PrivateKey_file(*ctx, opt_key?opt_key:opt_cert, SSL_FILETYPE_PEM) <= 0) {
|
|
/*ERR_print_errors_fp(stderr);*/
|
|
openssl_SSL_ERROR_SSL(E_ERROR/*!*/, "SSL_CTX_use_PrivateKey_file");
|
|
return STAT_RETRYLATER;
|
|
}
|
|
|
|
if (opt_dhparam == NULL) {
|
|
opt_dhparam = (char *)opt_cert;
|
|
}
|
|
if ((bio = sycBIO_new_file(opt_dhparam, "r")) == NULL) {
|
|
Warn2("BIO_new_file(\"%s\", \"r\"): %s",
|
|
opt_dhparam, strerror(errno));
|
|
} else {
|
|
if ((dh = sycPEM_read_bio_DHparams(bio, NULL, NULL, NULL)) == NULL) {
|
|
Info1("PEM_read_bio_DHparams(%p, NULL, NULL, NULL): error", bio);
|
|
} else {
|
|
BIO_free(bio);
|
|
if (sycSSL_CTX_set_tmp_dh(*ctx, dh) == 0) {
|
|
Error2("SSL_CTX_set_tmp_dh(%p, %p): error", ctx, dh);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* set pre ssl-connect options */
|
|
/* SSL_CIPHERS */
|
|
if (ci_str != NULL) {
|
|
if (sycSSL_CTX_set_cipher_list(*ctx, ci_str) <= 0) {
|
|
if (ERR_peek_error() == 0)
|
|
Error1("SSL_set_cipher_list(, \"%s\") failed", ci_str);
|
|
while (err = ERR_get_error()) {
|
|
Error2("SSL_set_cipher_list(, \"%s\"): %s",
|
|
ci_str, ERR_error_string(err, NULL));
|
|
}
|
|
/*Error("SSL_new()");*/
|
|
return STAT_RETRYLATER;
|
|
}
|
|
}
|
|
|
|
if (*opt_ver) {
|
|
sycSSL_CTX_set_verify(*ctx,
|
|
SSL_VERIFY_PEER| SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
|
|
NULL);
|
|
} else {
|
|
sycSSL_CTX_set_verify(*ctx,
|
|
SSL_VERIFY_NONE,
|
|
NULL);
|
|
}
|
|
|
|
return STAT_OK;
|
|
}
|
|
|
|
|
|
/* analyses an OpenSSL error condition, prints the appropriate messages with
|
|
severity 'level' and returns one of STAT_OK, STAT_RETRYLATER, or
|
|
STAT_NORETRY */
|
|
static int openssl_SSL_ERROR_SSL(int level, const char *funcname) {
|
|
unsigned long e;
|
|
char buf[120]; /* this value demanded by "man ERR_error_string" */
|
|
|
|
e = ERR_get_error();
|
|
Debug1("ERR_get_error(): %lx", e);
|
|
if (e == ((ERR_LIB_RAND<<24)|
|
|
(RAND_F_SSLEAY_RAND_BYTES<<12)|
|
|
(RAND_R_PRNG_NOT_SEEDED)) /*0x24064064*/) {
|
|
Error("too few entropy; use options \"egd\" or \"pseudo\"");
|
|
return STAT_NORETRY;
|
|
} else {
|
|
Msg2(level, "%s(): %s", funcname, ERR_error_string(e, buf));
|
|
return level==E_ERROR ? STAT_NORETRY : STAT_RETRYLATER;
|
|
}
|
|
return STAT_OK;
|
|
}
|
|
|
|
static const char *openssl_verify_messages[] = {
|
|
/* 0 */ "ok",
|
|
/* 1 */ NULL,
|
|
/* 2 */ "unable to get issuer certificate",
|
|
/* 3 */ "unable to get certificate CRL",
|
|
/* 4 */ "unable to decrypt certificate's signature",
|
|
/* 5 */ "unable to decrypt CRL's signature",
|
|
/* 6 */ "unable to decode issuer public key",
|
|
/* 7 */ "certificate signature failure",
|
|
/* 8 */ "CRL signature failure",
|
|
/* 9 */ "certificate is not yet valid",
|
|
/* 10 */ "certificate has expired",
|
|
/* 11 */ "CRL is not yet valid",
|
|
/* 12 */ "CRL has expired",
|
|
/* 13 */ "format error in certificate's notBefore field",
|
|
/* 14 */ "format error in certificate's notAfter field",
|
|
/* 15 */ "format error in CRL's lastUpdate field",
|
|
/* 16 */ "format error in CRL's nextUpdate field",
|
|
/* 17 */ "out of memory",
|
|
/* 18 */ "self signed certificate",
|
|
/* 19 */ "self signed certificate in certificate chain",
|
|
/* 20 */ "unable to get local issuer certificate",
|
|
/* 21 */ "unable to verify the first certificate",
|
|
/* 22 */ "certificate chain too long",
|
|
/* 23 */ "certificate revoked",
|
|
/* 24 */ "invalid CA certificate",
|
|
/* 25 */ "path length constraint exceeded",
|
|
/* 26 */ "unsupported certificate purpose",
|
|
/* 27 */ "certificate not trusted",
|
|
/* 28 */ "certificate rejected",
|
|
/* 29 */ "subject issuer mismatch",
|
|
/* 30 */ "authority and subject key identifier mismatch",
|
|
/* 31 */ "authority and issuer serial number mismatch",
|
|
/* 32 */ "key usage does not include certificate signing",
|
|
/* 33 */ NULL,
|
|
/* 34 */ NULL,
|
|
/* 35 */ NULL,
|
|
/* 36 */ NULL,
|
|
/* 37 */ NULL,
|
|
/* 38 */ NULL,
|
|
/* 39 */ NULL,
|
|
/* 40 */ NULL,
|
|
/* 41 */ NULL,
|
|
/* 42 */ NULL,
|
|
/* 43 */ NULL,
|
|
/* 44 */ NULL,
|
|
/* 45 */ NULL,
|
|
/* 46 */ NULL,
|
|
/* 47 */ NULL,
|
|
/* 48 */ NULL,
|
|
/* 49 */ NULL,
|
|
/* 50 */ "application verification failure",
|
|
} ;
|
|
|
|
static int openssl_handle_peer_certificate(struct single *xfd,
|
|
bool opt_ver, int level) {
|
|
X509 *peer_cert;
|
|
char *str;
|
|
char buff[2048]; /* hold peer certificate */
|
|
int status;
|
|
|
|
/* SSL_CTX_add_extra_chain_cert
|
|
SSL_get_verify_result
|
|
*/
|
|
if ((peer_cert = SSL_get_peer_certificate(xfd->para.openssl.ssl)) != NULL) {
|
|
Debug("peer certificate:");
|
|
if ((str = X509_NAME_oneline(X509_get_subject_name(peer_cert), buff, sizeof(buff))) != NULL)
|
|
Debug1("\tsubject: %s", str); /*free (str); SIGSEGV*/
|
|
if ((str = X509_NAME_oneline(X509_get_issuer_name(peer_cert), buff, sizeof(buff))) != NULL)
|
|
Debug1("\tissuer: %s", str); /*free (str); SIGSEGV*/
|
|
}
|
|
|
|
if (peer_cert) {
|
|
if (opt_ver) {
|
|
long verify_result;
|
|
if ((verify_result = sycSSL_get_verify_result(xfd->para.openssl.ssl)) == X509_V_OK) {
|
|
Info("accepted peer certificate");
|
|
status = STAT_OK;
|
|
} else {
|
|
const char *message = NULL;
|
|
if (verify_result >= 0 &&
|
|
(size_t)verify_result <
|
|
sizeof(openssl_verify_messages)/sizeof(char*))
|
|
{
|
|
message = openssl_verify_messages[verify_result];
|
|
}
|
|
if (message) {
|
|
Msg1(level, "%s", message);
|
|
} else {
|
|
Msg1(level, "rejected peer certificate with error %ld", verify_result);
|
|
}
|
|
status = STAT_RETRYLATER;
|
|
}
|
|
} else {
|
|
Notice("no check of certificate");
|
|
status = STAT_OK;
|
|
}
|
|
} else {
|
|
if (opt_ver) {
|
|
Msg(level, "no peer certificate");
|
|
status = STAT_RETRYLATER;
|
|
} else {
|
|
Notice("no peer certificate and no check");
|
|
status = STAT_OK;
|
|
}
|
|
}
|
|
|
|
X509_free(peer_cert);
|
|
return status;
|
|
}
|
|
|
|
static int xioSSL_set_fd(struct single *xfd, int level) {
|
|
unsigned long err;
|
|
|
|
/* assign a network connection to the SSL object */
|
|
if (xfd->rfd == xfd->wfd) {
|
|
if (sycSSL_set_fd(xfd->para.openssl.ssl, xfd->rfd) <= 0) {
|
|
Msg(level, "SSL_set_fd() failed");
|
|
while (err = ERR_get_error()) {
|
|
Msg2(level, "SSL_set_fd(, %d): %s",
|
|
xfd->wfd, ERR_error_string(err, NULL));
|
|
}
|
|
return STAT_RETRYLATER;
|
|
}
|
|
} else {
|
|
if (xfd->rfd >= 0) {
|
|
if (sycSSL_set_rfd(xfd->para.openssl.ssl, xfd->rfd) <= 0) {
|
|
Msg(level, "SSL_set_rfd() failed");
|
|
while (err = ERR_get_error()) {
|
|
Msg2(level, "SSL_set_rfd(, %d): %s",
|
|
xfd->rfd, ERR_error_string(err, NULL));
|
|
}
|
|
return STAT_RETRYLATER;
|
|
}
|
|
}
|
|
if (xfd->wfd >= 0) {
|
|
if (sycSSL_set_wfd(xfd->para.openssl.ssl, xfd->wfd) <= 0) {
|
|
Msg(level, "SSL_set_wfd() failed");
|
|
while (err = ERR_get_error()) {
|
|
Msg2(level, "SSL_set_wfd(, %d): %s",
|
|
xfd->wfd, ERR_error_string(err, NULL));
|
|
}
|
|
return STAT_RETRYLATER;
|
|
}
|
|
}
|
|
}
|
|
return STAT_OK;
|
|
}
|
|
|
|
|
|
/* ...
|
|
in case of an error condition, this function check forever and retry
|
|
options and ev. sleeps an interval. It returns NORETRY when the caller
|
|
should not retry for any reason. */
|
|
static int xioSSL_connect(struct single *xfd, bool opt_ver, int level) {
|
|
char error_string[120];
|
|
int errint, status, ret;
|
|
unsigned long err;
|
|
|
|
/* connect via SSL by performing handshake */
|
|
if ((ret = sycSSL_connect(xfd->para.openssl.ssl)) <= 0) {
|
|
/*if (ERR_peek_error() == 0) Msg(level, "SSL_connect() failed");*/
|
|
errint = SSL_get_error(xfd->para.openssl.ssl, ret);
|
|
switch (errint) {
|
|
case SSL_ERROR_NONE:
|
|
/* this is not an error, but I dare not continue for security reasons*/
|
|
Msg(level, "ok");
|
|
status = STAT_RETRYLATER;
|
|
case SSL_ERROR_ZERO_RETURN:
|
|
Msg(level, "connection closed (wrong version number?)");
|
|
status = STAT_RETRYLATER;
|
|
break;
|
|
case SSL_ERROR_WANT_READ:
|
|
case SSL_ERROR_WANT_WRITE:
|
|
case SSL_ERROR_WANT_CONNECT:
|
|
case SSL_ERROR_WANT_X509_LOOKUP:
|
|
Msg(level, "nonblocking operation did not complete");
|
|
status = STAT_RETRYLATER;
|
|
break; /*!*/
|
|
case SSL_ERROR_SYSCALL:
|
|
if (ERR_peek_error() == 0) {
|
|
if (ret == 0) {
|
|
Msg(level, "SSL_connect(): socket closed by peer");
|
|
} else if (ret == -1) {
|
|
Msg1(level, "SSL_connect(): %s", strerror(errno));
|
|
}
|
|
} else {
|
|
Msg(level, "I/O error"); /*!*/
|
|
while (err = ERR_get_error()) {
|
|
ERR_error_string_n(err, error_string, sizeof(error_string));
|
|
Msg4(level, "SSL_connect(): %s / %s / %s / %s", error_string,
|
|
ERR_lib_error_string(err), ERR_func_error_string(err),
|
|
ERR_reason_error_string(err));
|
|
}
|
|
}
|
|
status = STAT_RETRYLATER;
|
|
break;
|
|
case SSL_ERROR_SSL:
|
|
status = openssl_SSL_ERROR_SSL(level, "SSL_connect");
|
|
if (openssl_handle_peer_certificate(xfd, opt_ver, level/*!*/) < 0) {
|
|
return STAT_RETRYLATER;
|
|
}
|
|
break;
|
|
default:
|
|
Msg(level, "unknown error");
|
|
status = STAT_RETRYLATER;
|
|
break;
|
|
}
|
|
return status;
|
|
}
|
|
return STAT_OK;
|
|
}
|
|
|
|
/* on result < 0: errno is set (at least to EIO) */
|
|
ssize_t xioread_openssl(struct single *pipe, void *buff, size_t bufsiz) {
|
|
unsigned long err;
|
|
char error_string[120];
|
|
int _errno = EIO; /* if we have no better idea about nature of error */
|
|
int errint, ret;
|
|
|
|
ret = sycSSL_read(pipe->para.openssl.ssl, buff, bufsiz);
|
|
if (ret < 0) {
|
|
errint = SSL_get_error(pipe->para.openssl.ssl, ret);
|
|
switch (errint) {
|
|
case SSL_ERROR_NONE:
|
|
/* this is not an error, but I dare not continue for security reasons*/
|
|
Error("ok");
|
|
case SSL_ERROR_ZERO_RETURN:
|
|
Error("connection closed by peer");
|
|
break;
|
|
case SSL_ERROR_WANT_READ:
|
|
case SSL_ERROR_WANT_WRITE:
|
|
case SSL_ERROR_WANT_CONNECT:
|
|
case SSL_ERROR_WANT_X509_LOOKUP:
|
|
Error("nonblocking operation did not complete");
|
|
break; /*!*/
|
|
case SSL_ERROR_SYSCALL:
|
|
if (ERR_peek_error() == 0) {
|
|
if (ret == 0) {
|
|
Error("SSL_read(): socket closed by peer");
|
|
} else if (ret == -1) {
|
|
_errno = errno;
|
|
Error1("SSL_read(): %s", strerror(errno));
|
|
}
|
|
} else {
|
|
Error("I/O error"); /*!*/
|
|
while (err = ERR_get_error()) {
|
|
ERR_error_string_n(err, error_string, sizeof(error_string));
|
|
Error4("SSL_read(): %s / %s / %s / %s", error_string,
|
|
ERR_lib_error_string(err), ERR_func_error_string(err),
|
|
ERR_reason_error_string(err));
|
|
}
|
|
}
|
|
break;
|
|
case SSL_ERROR_SSL:
|
|
openssl_SSL_ERROR_SSL(E_ERROR, "SSL_connect");
|
|
break;
|
|
default:
|
|
Error("unknown error");
|
|
break;
|
|
}
|
|
errno = _errno;
|
|
return -1;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
ssize_t xiopending_openssl(struct single *pipe) {
|
|
int bytes = sycSSL_pending(pipe->para.openssl.ssl);
|
|
return bytes;
|
|
}
|
|
|
|
/* on result < 0: errno is set (at least to EIO) */
|
|
ssize_t xiowrite_openssl(struct single *pipe, const void *buff, size_t bufsiz) {
|
|
unsigned long err;
|
|
char error_string[120];
|
|
int _errno = EIO; /* if we have no better idea about nature of error */
|
|
int errint, ret;
|
|
|
|
ret = sycSSL_write(pipe->para.openssl.ssl, buff, bufsiz);
|
|
if (ret < 0) {
|
|
errint = SSL_get_error(pipe->para.openssl.ssl, ret);
|
|
switch (errint) {
|
|
case SSL_ERROR_NONE:
|
|
/* this is not an error, but I dare not continue for security reasons*/
|
|
Error("ok");
|
|
case SSL_ERROR_ZERO_RETURN:
|
|
Error("connection closed by peer");
|
|
break;
|
|
case SSL_ERROR_WANT_READ:
|
|
case SSL_ERROR_WANT_WRITE:
|
|
case SSL_ERROR_WANT_CONNECT:
|
|
case SSL_ERROR_WANT_X509_LOOKUP:
|
|
Error("nonblocking operation did not complete");
|
|
break; /*!*/
|
|
case SSL_ERROR_SYSCALL:
|
|
if (ERR_peek_error() == 0) {
|
|
if (ret == 0) {
|
|
Error("SSL_write(): socket closed by peer");
|
|
} else if (ret == -1) {
|
|
_errno = errno;
|
|
Error1("SSL_write(): %s", strerror(errno));
|
|
}
|
|
} else {
|
|
Error("I/O error"); /*!*/
|
|
while (err = ERR_get_error()) {
|
|
ERR_error_string_n(err, error_string, sizeof(error_string));
|
|
Error4("SSL_write(): %s / %s / %s / %s", error_string,
|
|
ERR_lib_error_string(err), ERR_func_error_string(err),
|
|
ERR_reason_error_string(err));
|
|
}
|
|
}
|
|
break;
|
|
case SSL_ERROR_SSL:
|
|
openssl_SSL_ERROR_SSL(E_ERROR, "SSL_connect");
|
|
break;
|
|
default:
|
|
Error("unknown error");
|
|
break;
|
|
}
|
|
errno = _errno;
|
|
return -1;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
|
|
#endif /* WITH_OPENSSL */
|