/* $Id: async_ssl.C,v 1.9 2005/08/09 22:37:53 dm Exp $ */
/*
*
* Copyright (C) 2005 David Mazieres (dm@uun.org)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*
*/
#include "async_ssl.h"
#undef output
aiossl::aiossl (int fd, size_t rbufsize)
: aios (fd, rbufsize), ssl (NULL), bss (NULL), inbss (NULL),
need_handshake (true), iocblock (false), finlock (false)
{
}
aiossl::~aiossl ()
{
if (ssl)
SSL_free (ssl);
}
void
aiossl::finalize ()
{
if (finlock)
return;
else if (!ssl)
aios::finalize ();
else if (fd < 0 || sent_shutdown || (err && err != ETIMEDOUT))
delete this;
else if (err == ETIMEDOUT) {
weof = true;
dooutput ();
delete this;
}
else {
weof = true;
if (!gettimeout ())
settimeout (60);
finlock = true;
iocb ();
finlock = false;
if (fd < 0 || sent_shutdown || err)
delete this;
}
}
int
aiossl::doerr (int err, int silent)
{
switch (int err2 = SSL_get_error (ssl, err)) {
case SSL_ERROR_NONE:
return err;
case SSL_ERROR_ZERO_RETURN:
errno = EPIPE;
return 0;
case SSL_ERROR_WANT_READ:
if (inbss) {
assert (!BIO_ctrl (inbss, BIO_CTRL_INFO, 0, NULL));
inbss = NULL;
bss = BIO_new_socket (fd, BIO_NOCLOSE);
SSL_set_bio (ssl, bss, bss);
}
if (!rcbset) {
rcbset = true;
fdcb (fd, selread, wrap (this, &aiossl::iocb));
}
errno = EAGAIN;
return -1;
case SSL_ERROR_WANT_WRITE:
if (!wcbset) {
wcbset = true;
fdcb (fd, selwrite, wrap (this, &aiossl::iocb));
}
errno = EAGAIN;
return -1;
default:
if (!silent)
warn << "SSL error: " << ssl_err (err2) << "\n";
errno = EIO;
return -1;
}
}
int
aiossl::doinput ()
{
if (!ssl)
return aios::doinput ();
const iovec *iov = inb.iniov ();
int iovcnt = inb.iniovcnt ();
int nread = 0;
while (iovcnt > 0) {
int n = SSL_read (ssl, iov->iov_base, iov->iov_len);
if (n >= implicit_cast<ssize_t> (iov->iov_len)) {
assert (implicit_cast<size_t> (n) == iov->iov_len);
inb.addbytes (n);
nread += n;
iov++;
iovcnt--;
}
else if (n > 0) {
inb.addbytes (n);
nread += n;
return nread;
}
else {
n = doerr (n);
return nread > 0 ? nread : n;
}
}
return nread;
}
int
aiossl::dooutput ()
{
if (!ssl)
return aios::dooutput ();
int ret = 0;
if (oldout) {
ret = oldout->output (fd);
if (!oldout->resid ())
oldout = NULL;
if (ret < 0 || oldout)
return ret;
}
while (outb.tosuio ()->resid ()) {
const iovec *iov = outb.tosuio ()->iov ();
int n = SSL_write (ssl, iov->iov_base, iov->iov_len);
if (n > 0) {
ret = 1;
outb.tosuio ()->rembytes (n);
}
else {
n = doerr (n); // May set wantread or wantwrite
return ret ? ret : n;
}
}
if (weof && !outb.tosuio ()->resid () && !sent_shutdown) {
int n = doerr (SSL_shutdown (ssl), true);
if (n < 0 && errno == EAGAIN)
return n;
sent_shutdown = true;
}
return ret;
}
inline str
mkstr (char *name)
{
str ret (name);
free (name);
return ret;
}
bool
aiossl::verify ()
{
if (SSL_get_verify_result (ssl) != X509_V_OK)
return false;
X509 *cert = SSL_get_peer_certificate (ssl);
if (!cert)
return false;
char buf[257] = "";
X509_NAME *name = X509_get_subject_name (cert);
subject_dn = mkstr (X509_NAME_oneline (name, NULL, 0));
X509_NAME_get_text_by_NID (name, NID_commonName, buf, sizeof (buf) - 1);
buf[sizeof (buf) - 1] = '\0';
subject = buf;
name = X509_get_issuer_name (cert);
issuer_dn = mkstr (X509_NAME_oneline (name, NULL, 0));
X509_NAME_get_text_by_NID (name, NID_commonName, buf, sizeof (buf) - 1);
buf[sizeof (buf) - 1] = '\0';
issuer = buf;
X509_free (cert);
return true;
}
void
aiossl::iocb ()
{
if (iocblock) {
again = true;
return;
}
iocblock = true;
ref<aios> hold = mkref (this); // Don't let this be freed under us
assert (fd >= 0);
if (rcbset) {
fdcb (fd, selread, NULL);
rcbset = false;
}
if (wcbset) {
fdcb (fd, selwrite, NULL);
wcbset = false;
}
if (oldout) {
int err = dooutput ();
if (oldout) {
iocblock = false;
if (err < 0)
fail (errno);
else if (!wcbset) {
wcbset = true;
fdcb (fd, selwrite, wrap (this, &aiossl::iocb));
}
return;
}
}
if (need_handshake) {
int err;
if (server)
err = SSL_accept (ssl);
else
err = SSL_connect (ssl);
err = doerr (err);
if (err <= 0) {
if (errno != EAGAIN)
fail (errno);
iocblock = false;
return;
}
else {
need_handshake = false;
cipher = SSL_get_cipher_name (ssl);
if (debugname)
warnx << debugname << errpref << "SSL-cipher " << cipher << "\n";
if (verify ()) {
if (debugname) {
warnx << debugname << errpref << "SSL-cert issuer=" << issuer
<< ", subject=" << subject << "\n";
//warnx << debugname << errpref << "Subject: " << subject_dn << "\n";
//warnx << debugname << errpref << "Issuer: " << issuer_dn << "\n";
}
if (vcb)
(*vcb) ();
}
}
}
do {
again = false;
if (fd >= 0 && reading ())
input ();
if (fd >= 0 && (writing () || (weof && !sent_shutdown) || oldout))
output ();
} while (again);
iocblock = false;
}
void
aiossl::setincb ()
{
if (!ssl) {
aios::setincb ();
return;
}
if (!rcbset)
iocb ();
}
void
aiossl::setoutcb ()
{
if (!ssl) {
aios::setoutcb ();
return;
}
if (!wcbset)
iocb ();
}
void
aiossl::startssl (SSL_CTX *ctx, bool s)
{
assert (!ssl);
assert (fd >= 0);
fdcb (fd, selread, NULL);
fdcb (fd, selwrite, NULL);
wcbset = rcbset = false;
ssl = SSL_new (ctx);
bss = BIO_new_socket (fd, BIO_NOCLOSE);
inbss = BIO_new (BIO_s_mem ());
BIO_set_mem_eof_return (inbss, -1);
while (inb.outiovcnt () > 0) {
const iovec *iov = inb.outiov ();
BIO_write (inbss, iov->iov_base, iov->iov_len);
inb.rembytes (iov->iov_len);
}
oldout = New refcounted<suio>;
oldout->take (outb.tosuio ());
SSL_set_bio (ssl, inbss, bss);
SSL_set_mode (ssl, (SSL_MODE_ENABLE_PARTIAL_WRITE
| SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER));
server = s;
need_handshake = true;
sent_shutdown = false;
rdpref = " *=> ";
wrpref = " <=* ";
errpref = " *** ";
iocb ();
}
syntax highlighted by Code2HTML, v. 0.9.1