From 3c5954e94a1b6a035e8afcaa7e8eeec76c817eba Mon Sep 17 00:00:00 2001 From: Fredrik Tolf Date: Fri, 28 Jul 2023 15:36:10 +0200 Subject: [PATCH] htparser: Add an initial OpenSSL backend. It is still lacking somewhat important features like SNI support, however. --- configure.ac | 34 ++++++ src/Makefile.am | 6 +- src/htparser.c | 5 +- src/htparser.h | 3 + src/ssl-openssl.c | 302 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 346 insertions(+), 4 deletions(-) create mode 100644 src/ssl-openssl.c diff --git a/configure.ac b/configure.ac index 16f9218..5f2d06b 100644 --- a/configure.ac +++ b/configure.ac @@ -97,8 +97,12 @@ fi AC_SUBST(XATTR_LIBS) AH_TEMPLATE(HAVE_GNUTLS, [define to use the GnuTLS library for SSL support]) +AH_TEMPLATE(HAVE_OPENSSL, [define to use the OpenSSL library for SSL support]) AC_ARG_WITH(gnutls, AS_HELP_STRING([--with-gnutls], [enable SSL support with the GnuTLS library])) +AC_ARG_WITH(openssl, AS_HELP_STRING([--with-openssl], [enable SSL support with the OpenSSL library])) HAS_GNUTLS="" +HAS_OPENSSL="" + if test "$with_gnutls" = no; then HAS_GNUTLS=no; fi if test -z "$HAS_GNUTLS"; then AC_CHECK_LIB(gnutls, gnutls_global_init, [:], [HAS_GNUTLS=no]) @@ -118,6 +122,36 @@ fi AC_SUBST(GNUTLS_CPPFLAGS) AC_SUBST(GNUTLS_LIBS) +if test "$with_openssl" = no; then + HAS_OPENSSL=no +elif test -z "$with_openssl" -a "$HAS_GNUTLS" = yes; then + HAS_OPENSSL=no +fi +if test -z "$HAS_OPENSSL"; then + AC_CHECK_LIB(ssl, SSL_CTX_new, [:], [HAS_OPENSSL=no]) +fi +if test -z "$HAS_OPENSSL"; then + AC_CHECK_LIB(crypto, ERR_get_error, [:], [HAS_OPENSSL=no]) +fi +if test -z "$HAS_OPENSSL"; then + AC_CHECK_HEADER(openssl/ssl.h, [], [HAS_OPENSSL=no]) +fi +if test "$HAS_OPENSSL" != no; then HAS_OPENSSL=yes; fi +if test "$with_openssl" = yes -a "$HAS_OPENSSL" = no; then + AC_MSG_ERROR([*** cannot find OpenSSL on this system]) +fi +if test "$HAS_OPENSSL" = yes; then + OPENSSL_LIBS="-lssl -lcrypto" + GNUTLS_CPPFLAGS= + AC_DEFINE(HAVE_OPENSSL) +fi +AC_SUBST(OPENSSL_CPPFLAGS) +AC_SUBST(OPENSSL_LIBS) + +if test "$HAS_GNUTLS" = yes -a "$HAS_OPENSSL" = yes; then + AC_MSG_ERROR([*** only one of GnuTLS and OpenSSL must be enabled]) +fi + AC_CONFIG_FILES([ Makefile src/Makefile diff --git a/src/Makefile.am b/src/Makefile.am index 1c1b8a8..3bc8963 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -4,12 +4,12 @@ bin_PROGRAMS = htparser sendfile callcgi patplex userplex htls \ callscgi accesslog htextauth callfcgi multifscgi \ errlogger httimed psendfile httrcall htpipe ratequeue -htparser_SOURCES = htparser.c htparser.h plaintcp.c ssl-gnutls.c +htparser_SOURCES = htparser.c htparser.h plaintcp.c ssl-gnutls.c ssl-openssl.c LDADD = $(top_srcdir)/lib/libht.a AM_CPPFLAGS = -I$(top_srcdir)/lib -htparser_CPPFLAGS = $(AM_CPPFLAGS) @GNUTLS_CPPFLAGS@ -htparser_LDADD = $(LDADD) @GNUTLS_LIBS@ +htparser_CPPFLAGS = $(AM_CPPFLAGS) @GNUTLS_CPPFLAGS@ @OPENSSL_CPPFLAGS@ +htparser_LDADD = $(LDADD) @GNUTLS_LIBS@ @OPENSSL_LIBS@ sendfile_LDADD = $(LDADD) -lmagic @XATTR_LIBS@ psendfile_LDADD = $(LDADD) -lmagic @XATTR_LIBS@ diff --git a/src/htparser.c b/src/htparser.c index e281abd..5491855 100644 --- a/src/htparser.c +++ b/src/htparser.c @@ -580,9 +580,12 @@ static void addport(char *spec) /* XXX: It would be nice to decentralize this, but, meh... */ if(!strcmp(nm, "plain")) { handleplain(pars.d, pars.b, vals.b); -#ifdef HAVE_GNUTLS +#if defined HAVE_GNUTLS } else if(!strcmp(nm, "ssl")) { handlegnussl(pars.d, pars.b, vals.b); +#elif defined HAVE_OPENSSL + } else if(!strcmp(nm, "ssl")) { + handleossl(pars.d, pars.b, vals.b); #endif } else { flog(LOG_ERR, "htparser: unknown port handler `%s'", nm); diff --git a/src/htparser.h b/src/htparser.h index d9f014d..946ed51 100644 --- a/src/htparser.h +++ b/src/htparser.h @@ -20,6 +20,9 @@ void handleplain(int argc, char **argp, char **argv); #ifdef HAVE_GNUTLS void handlegnussl(int argc, char **argp, char **argv); #endif +#ifdef HAVE_OPENSSL +void handleossl(int argc, char **argp, char **argv); +#endif extern struct mtbuf listeners; diff --git a/src/ssl-openssl.c b/src/ssl-openssl.c new file mode 100644 index 0000000..c8785a3 --- /dev/null +++ b/src/ssl-openssl.c @@ -0,0 +1,302 @@ +/* + ashd - A Sane HTTP Daemon + Copyright (C) 2008 Fredrik Tolf + + 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 3 of the License, 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, see . +*/ + +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include +#endif +#include +#include +#include +#include +#include +#include + +#include "htparser.h" + +#include +#include + +struct sslport { + int fd, sport; + SSL_CTX *ctx; +}; + +struct sslconn { + struct sslport *port; + int fd; + SSL *ssl; + struct sockaddr *name; + socklen_t namelen; +}; + +static int tlsblock(int fd, int err, int to) +{ + if(err == SSL_ERROR_WANT_READ) { + if(block(fd, EV_READ, to) <= 0) + return(1); + return(0); + } else if(err == SSL_ERROR_WANT_WRITE) { + if(block(fd, EV_WRITE, to) <= 0) + return(1); + return(0); + } else { + return(1); + } +} + +static ssize_t sslread(void *cookie, void *buf, size_t len) +{ + struct sslconn *sdat = cookie; + int ret, err, nb; + size_t off; + + off = 0; + while(off < len) { + nb = ((len - off) > INT_MAX) ? INT_MAX : (len - off); + if((ret = SSL_read(sdat->ssl, buf, nb)) <= 0) { + if(off > 0) + return(off); + err = SSL_get_error(sdat->ssl, ret); + if(err == SSL_ERROR_ZERO_RETURN) { + return(0); + } else if((err == SSL_ERROR_WANT_READ) || (err == SSL_ERROR_WANT_WRITE)) { + if(tlsblock(sdat->fd, err, 60)) { + errno = ETIMEDOUT; + return(-1); + } + } else { + if(err != SSL_ERROR_SYSCALL) + errno = EPROTO; + return(-1); + } + } else { + off += ret; + } + } + return(off); +} + +static ssize_t sslwrite(void *cookie, const void *buf, size_t len) +{ + struct sslconn *sdat = cookie; + int ret, err, nb; + size_t off; + + off = 0; + while(off < len) { + nb = ((len - off) > INT_MAX) ? INT_MAX : (len - off); + if((ret = SSL_write(sdat->ssl, buf, nb)) <= 0) { + if(off > 0) + return(off); + err = SSL_get_error(sdat->ssl, ret); + if((err == SSL_ERROR_WANT_READ) || (err == SSL_ERROR_WANT_WRITE)) { + if(tlsblock(sdat->fd, err, 60)) { + errno = ETIMEDOUT; + return(-1); + } + } else { + if(err != SSL_ERROR_SYSCALL) + errno = EIO; + return(-1); + } + } else { + off += ret; + } + } + return(off); +} + +static int sslclose(void *cookie) +{ + return(0); +} + +static struct bufioops iofuns = { + .read = sslread, + .write = sslwrite, + .close = sslclose, +}; + +static int initreq(struct conn *conn, struct hthead *req) +{ + struct sslconn *sdat = conn->pdata; + struct sockaddr_storage sa; + socklen_t salen; + + headappheader(req, "X-Ash-Address", formathaddress(sdat->name, sdat->namelen)); + if(sdat->name->sa_family == AF_INET) + headappheader(req, "X-Ash-Port", sprintf3("%i", ntohs(((struct sockaddr_in *)sdat->name)->sin_port))); + else if(sdat->name->sa_family == AF_INET6) + headappheader(req, "X-Ash-Port", sprintf3("%i", ntohs(((struct sockaddr_in6 *)sdat->name)->sin6_port))); + salen = sizeof(sa); + if(!getsockname(sdat->fd, (struct sockaddr *)&sa, &salen)) + headappheader(req, "X-Ash-Server-Address", formathaddress((struct sockaddr *)&sa, salen)); + headappheader(req, "X-Ash-Server-Port", sprintf3("%i", sdat->port->sport)); + headappheader(req, "X-Ash-Protocol", "https"); + return(0); +} + +static void servessl(struct muth *muth, va_list args) +{ + vavar(int, fd); + vavar(struct sockaddr_storage, name); + vavar(struct sslport *, pd); + int ret; + SSL *ssl; + struct conn conn; + struct sslconn sdat; + + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK); + ssl = SSL_new(pd->ctx); + SSL_set_fd(ssl, fd); + while((ret = SSL_accept(ssl)) <= 0) { + if(tlsblock(fd, SSL_get_error(ssl, ret), 60)) + goto out; + } + memset(&conn, 0, sizeof(conn)); + memset(&sdat, 0, sizeof(sdat)); + conn.pdata = &sdat; + conn.initreq = initreq; + sdat.port = pd; + sdat.fd = fd; + sdat.ssl = ssl; + sdat.name = (struct sockaddr *)&name; + sdat.namelen = sizeof(name); + serve(bioopen(&sdat, &iofuns), fd, &conn); + while((ret = SSL_shutdown(ssl)) < 0) { + if(tlsblock(fd, SSL_get_error(ssl, ret), 60)) + goto out; + } +out: + SSL_free(ssl); + close(fd); +} + +static void listenloop(struct muth *muth, va_list args) +{ + vavar(struct sslport *, pd); + int i, ns, n; + struct sockaddr_storage name; + socklen_t namelen; + + fcntl(pd->fd, F_SETFL, fcntl(pd->fd, F_GETFL) | O_NONBLOCK); + while(1) { + namelen = sizeof(name); + if(block(pd->fd, EV_READ, 0) == 0) + goto out; + for(n = 0; n < 100; n++) { + if((ns = accept(pd->fd, (struct sockaddr *)&name, &namelen)) < 0) { + if(errno == EAGAIN) + break; + if(errno == ECONNABORTED) + continue; + flog(LOG_ERR, "accept: %s", strerror(errno)); + goto out; + } + mustart(servessl, ns, name, pd); + } + } + +out: + close(pd->fd); + free(pd); + for(i = 0; i < listeners.d; i++) { + if(listeners.b[i] == muth) + bufdel(listeners, i); + } +} + +void handleossl(int argc, char **argp, char **argv) +{ + int i, port, fd; + SSL_CTX *ctx; + char *crtfile, *keyfile; + struct sslport *pd; + + ctx = SSL_CTX_new(TLS_server_method()); + if(!ctx) { + flog(LOG_ERR, "ssl: could not create context: %s", ERR_error_string(ERR_get_error(), NULL)); + exit(1); + } + port = 443; + for(i = 0; i < argc; i++) { + if(!strcmp(argp[i], "help")) { + printf("ssl handler parameters:\n"); + printf("\tcert=CERT-FILE [mandatory]\n"); + printf("\t\tThe name of the file to read the certificate from.\n"); + printf("\tkey=KEY-FILE [same as CERT-FILE]\n"); + printf("\t\tThe name of the file to read the private key from.\n"); + printf("\tport=PORT [443]\n"); + printf("\t\tThe TCP port to listen on.\n"); + exit(0); + } else if(!strcmp(argp[i], "cert")) { + crtfile = argv[i]; + } else if(!strcmp(argp[i], "key")) { + keyfile = argv[i]; + } else if(!strcmp(argp[i], "port")) { + port = atoi(argv[i]); + } else { + flog(LOG_ERR, "unknown parameter `%s' to ssl handler", argp[i]); + exit(1); + } + } + if(crtfile == NULL) { + flog(LOG_ERR, "ssl: needs certificate file at the very least"); + exit(1); + } + if(keyfile == NULL) + keyfile = crtfile; + if(SSL_CTX_use_certificate_file(ctx, crtfile, SSL_FILETYPE_PEM) <= 0) { + flog(LOG_ERR, "ssl: could not load certificate: %s", ERR_error_string(ERR_get_error(), NULL)); + exit(1); + } + if(SSL_CTX_use_PrivateKey_file(ctx, keyfile, SSL_FILETYPE_PEM) <= 0) { + flog(LOG_ERR, "ssl: could not load certificate: %s", ERR_error_string(ERR_get_error(), NULL)); + exit(1); + } + if(!SSL_CTX_check_private_key(ctx)) { + flog(LOG_ERR, "ssl: key and certificate do not match"); + exit(1); + } + if((fd = listensock6(port)) < 0) { + flog(LOG_ERR, "could not listen on IPv65 port (port %i): %s", port, strerror(errno)); + exit(1); + } + omalloc(pd); + pd->fd = fd; + pd->sport = port; + pd->ctx = ctx; + bufadd(listeners, mustart(listenloop, pd)); + if((fd = listensock4(port)) < 0) { + if(errno != EADDRINUSE) { + flog(LOG_ERR, "could not listen on IPv4 port (port %i): Is", port, strerror(errno)); + exit(1); + } + } else { + omalloc(pd); + pd->fd = fd; + pd->sport = port; + pd->ctx = ctx; + bufadd(listeners, mustart(listenloop, pd)); + } +} -- 2.11.0