Added an initial implementation of HTTPS.
authorFredrik Tolf <fredrik@dolda2000.com>
Tue, 31 Aug 2010 15:28:16 +0000 (17:28 +0200)
committerFredrik Tolf <fredrik@dolda2000.com>
Tue, 31 Aug 2010 15:28:16 +0000 (17:28 +0200)
Should be complemented with some output of certificate information
in the request.

configure.in
src/Makefile.am
src/htparser.c
src/htparser.h
src/plaintcp.c
src/ssl-gnutls.c [new file with mode: 0644]

index 2e274a2..fd42fd9 100644 (file)
@@ -32,6 +32,28 @@ if test "$HAS_XATTR" = yes; then
 fi
 AC_SUBST(XATTR_LIBS)
 
+AH_TEMPLATE(HAVE_GNUTLS, [define to use the GnuTLS library for SSL support])
+AC_ARG_WITH(gnutls, [  --with-gnutls           Enable SSL support with the GnuTLS library])
+HAS_GNUTLS=""
+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])
+fi
+if test -z "$HAS_GNUTLS";  then
+       AC_CHECK_HEADER(gnutls/gnutls.h, [], [HAS_GNUTLS=no])
+fi
+if test "$HAS_GNUTLS" != no; then HAS_GNUTLS=yes; fi
+if test "$with_gnutls" = yes -a "$HAS_GNUTLS" = no; then
+       AC_MSG_ERROR([*** cannot find GnuTLS on this system])
+fi
+if test "$HAS_GNUTLS" = yes; then
+       GNUTLS_LIBS=-lgnutls
+       GNUTLS_CPPFLAGS=-D_GNU_SOURCE
+       AC_DEFINE(HAVE_GNUTLS)
+fi
+AC_SUBST(GNUTLS_CPPFLAGS)
+AC_SUBST(GNUTLS_LIBS)
+
 AC_OUTPUT([
 Makefile
 src/Makefile
index 32e50fb..738d8a1 100644 (file)
@@ -1,11 +1,13 @@
 bin_PROGRAMS = htparser dirplex sendfile callcgi patplex userplex htls
 noinst_PROGRAMS = debugsink
 
-htparser_SOURCES = htparser.c htparser.h plaintcp.c
+htparser_SOURCES = htparser.c htparser.h plaintcp.c ssl-gnutls.c
 dirplex_SOURCES = dirplex.c
 debugsink_SOURCES = debugsink.c
 
 LDADD = $(top_srcdir)/lib/libht.a
 AM_CPPFLAGS = -I$(top_srcdir)/lib
 
+htparser_CPPFLAGS = $(AM_CPPFLAGS) @GNUTLS_CPPFLAGS@
+htparser_LDADD = $(LDADD) @GNUTLS_LIBS@
 sendfile_LDADD = $(LDADD) -lmagic @XATTR_LIBS@
index 3fcca75..4835549 100644 (file)
@@ -353,7 +353,7 @@ static void usage(FILE *out)
 {
     fprintf(out, "usage: htparser [-hSf] [-u USER] [-r ROOT] PORTSPEC... -- ROOT [ARGS...]\n");
     fprintf(out, "\twhere PORTSPEC is HANDLER[:PAR[=VAL][(,PAR[=VAL])...]] (try HANDLER:help)\n");
-    fprintf(out, "\tavailable handlers are `plain'.\n");
+    fprintf(out, "\tavailable handlers are `plain' and `ssl'.\n");
 }
 
 static void addport(char *spec)
@@ -388,6 +388,10 @@ 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
+    } else if(!strcmp(nm, "ssl")) {
+       handlegnussl(pars.d, pars.b, vals.b);
+#endif
     } else {
        flog(LOG_ERR, "htparser: unknown port handler `%s'", nm);
        exit(1);
index a84086c..f98624a 100644 (file)
@@ -8,6 +8,11 @@ struct conn {
 
 void serve(FILE *in, struct conn *conn);
 
+int listensock4(int port);
+int listensock6(int port);
 void handleplain(int argc, char **argp, char **argv);
+#ifdef HAVE_GNUTLS
+void handlegnussl(int argc, char **argp, char **argv);
+#endif
 
 #endif
index 864efe1..00b6f3c 100644 (file)
@@ -46,7 +46,7 @@ struct tcpconn {
     struct tcpport *port;
 };
 
-static int listensock4(int port)
+int listensock4(int port)
 {
     struct sockaddr_in name;
     int fd;
@@ -70,7 +70,7 @@ static int listensock4(int port)
     return(fd);
 }
 
-static int listensock6(int port)
+int listensock6(int port)
 {
     struct sockaddr_in6 name;
     int fd;
@@ -163,7 +163,8 @@ void handleplain(int argc, char **argp, char **argv)
     for(i = 0; i < argc; i++) {
        if(!strcmp(argp[i], "help")) {
            printf("plain handler parameters:\n");
-           printf("\tport=TCP-PORT (default is 80)\n");
+           printf("\tport=TCP-PORT   [80]\n");
+           printf("\t\tThe TCP port to listen on.\n");
            exit(0);
        } else if(!strcmp(argp[i], "port")) {
            port = atoi(argv[i]);
diff --git a/src/ssl-gnutls.c b/src/ssl-gnutls.c
new file mode 100644 (file)
index 0000000..bad5f4a
--- /dev/null
@@ -0,0 +1,320 @@
+/*
+    ashd - A Sane HTTP Daemon
+    Copyright (C) 2008  Fredrik Tolf <fredrik@dolda2000.com>
+
+    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 <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <errno.h>
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <utils.h>
+#include <mt.h>
+#include <mtio.h>
+#include <req.h>
+#include <log.h>
+
+#include "htparser.h"
+
+#ifdef HAVE_GNUTLS
+
+#include <gnutls/gnutls.h>
+
+struct sslport {
+    int fd;
+    int sport;
+    gnutls_certificate_credentials_t creds;
+};
+
+struct sslconn {
+    int fd;
+    struct sslport *port;
+    struct sockaddr_storage name;
+    gnutls_session_t sess;
+    struct charbuf in;
+};
+
+static gnutls_dh_params_t dhparams;
+
+static int tlsblock(int fd, gnutls_session_t sess, time_t to)
+{
+    if(gnutls_record_get_direction(sess))
+       return(block(fd, EV_WRITE, to));
+    else
+       return(block(fd, EV_READ, to));
+}
+
+static ssize_t sslread(void *cookie, char *buf, size_t len)
+{
+    struct sslconn *ssl = cookie;
+    ssize_t xf;
+    int ret;
+
+    while(ssl->in.d == 0) {
+       sizebuf(ssl->in, ssl->in.d + 1024);
+       ret = gnutls_record_recv(ssl->sess, ssl->in.b + ssl->in.d, ssl->in.s - ssl->in.d);
+       if((ret == GNUTLS_E_INTERRUPTED) || (ret == GNUTLS_E_AGAIN)) {
+           if(tlsblock(ssl->fd, ssl->sess, 60) == 0) {
+               errno = ETIMEDOUT;
+               return(-1);
+           }
+       } else if(ret < 0) {
+           errno = EIO;
+           return(-1);
+       } else if(ret == 0) {
+           return(0);
+       } else {
+           ssl->in.d += ret;
+       }
+    }
+    xf = min(ssl->in.d, len);
+    memcpy(buf, ssl->in.b, xf);
+    memmove(ssl->in.b, ssl->in.b + xf, ssl->in.d -= xf);
+    return(xf);
+}
+
+static ssize_t sslwrite(void *cookie, const char *buf, size_t len)
+{
+    struct sslconn *ssl = cookie;
+    int ret;
+    size_t off;
+    
+    off = 0;
+    while(off < len) {
+       ret = gnutls_record_send(ssl->sess, buf + off, len - off);
+       if((ret == GNUTLS_E_INTERRUPTED) || (ret == GNUTLS_E_AGAIN)) {
+           if(tlsblock(ssl->fd, ssl->sess, 60) == 0) {
+               if(off == 0) {
+                   errno = ETIMEDOUT;
+                   return(-1);
+               }
+               return(off);
+           }
+       } else if(ret < 0) {
+           if(off == 0) {
+               errno = EIO;
+               return(-1);
+           }
+           return(off);
+       } else {
+           off += ret;
+       }
+    }
+    return(off);
+}
+
+static int sslclose(void *cookie)
+{
+    return(0);
+}
+
+static cookie_io_functions_t iofuns = {
+    .read = sslread,
+    .write = sslwrite,
+    .close = sslclose,
+};
+
+static int initreq(struct conn *conn, struct hthead *req)
+{
+    struct sslconn *ssl = conn->pdata;
+    char nmbuf[256];
+    
+    if(ssl->name.ss_family == AF_INET) {
+       headappheader(req, "X-Ash-Address", inet_ntop(AF_INET, &((struct sockaddr_in *)&ssl->name)->sin_addr, nmbuf, sizeof(nmbuf)));
+       headappheader(req, "X-Ash-Port", sprintf3("%i", ntohs(((struct sockaddr_in *)&ssl->name)->sin_port)));
+    } else if(ssl->name.ss_family == AF_INET6) {
+       headappheader(req, "X-Ash-Address", inet_ntop(AF_INET6, &((struct sockaddr_in6 *)&ssl->name)->sin6_addr, nmbuf, sizeof(nmbuf)));
+       headappheader(req, "X-Ash-Port", sprintf3("%i", ntohs(((struct sockaddr_in6 *)&ssl->name)->sin6_port)));
+    }
+    headappheader(req, "X-Ash-Server-Port", sprintf3("%i", ssl->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);
+    struct conn conn;
+    struct sslconn ssl;
+    gnutls_session_t sess;
+    int ret;
+    FILE *in;
+    
+    fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
+    gnutls_init(&sess, GNUTLS_SERVER);
+    gnutls_set_default_priority(sess);
+    gnutls_credentials_set(sess, GNUTLS_CRD_CERTIFICATE, pd->creds);
+    gnutls_certificate_server_set_request(sess, GNUTLS_CERT_REQUEST);
+    gnutls_transport_set_ptr(sess, (gnutls_transport_ptr_t)(intptr_t)fd);
+    while((ret = gnutls_handshake(sess)) != 0) {
+       if((ret != GNUTLS_E_INTERRUPTED) && (ret != GNUTLS_E_AGAIN))
+           goto out;
+       if(tlsblock(fd, sess, 60) <= 0)
+           goto out;
+    }
+    memset(&conn, 0, sizeof(conn));
+    memset(&ssl, 0, sizeof(ssl));
+    conn.pdata = &ssl;
+    conn.initreq = initreq;
+    ssl.fd = fd;
+    ssl.port = pd;
+    ssl.name = name;
+    ssl.sess = sess;
+    bufinit(ssl.in);
+    in = fopencookie(&ssl, "r+", iofuns);
+    serve(in, &conn);
+    
+out:
+    gnutls_deinit(sess);
+    close(fd);
+}
+
+static void listenloop(struct muth *muth, va_list args)
+{
+    vavar(struct sslport *, pd);
+    int ns;
+    struct sockaddr_storage name;
+    socklen_t namelen;
+    
+    while(1) {
+       namelen = sizeof(name);
+       block(pd->fd, EV_READ, 0);
+       ns = accept(pd->fd, (struct sockaddr *)&name, &namelen);
+       if(ns < 0) {
+           flog(LOG_ERR, "accept: %s", strerror(errno));
+           goto out;
+       }
+       mustart(servessl, ns, name, pd);
+    }
+    
+out:
+    close(pd->fd);
+    free(pd);
+}
+
+static void init(void)
+{
+    static int inited = 0;
+    int ret;
+    
+    if(inited)
+       return;
+    inited = 1;
+    if((ret = gnutls_global_init()) != 0) {
+       flog(LOG_ERR, "could not initialize GnuTLS: %s", gnutls_strerror(ret));
+       exit(1);
+    }
+    if(((ret = gnutls_dh_params_init(&dhparams)) != 0) ||
+       ((ret = gnutls_dh_params_generate2(dhparams, 2048)) != 0)) {
+       flog(LOG_ERR, "GnuTLS could not generate Diffie-Hellman parameters: %s", gnutls_strerror(ret));
+       exit(1);
+    }
+}
+
+void handlegnussl(int argc, char **argp, char **argv)
+{
+    int i, ret, port, fd;
+    gnutls_certificate_credentials_t creds;
+    struct sslport *pd;
+    char *crtfile, *keyfile;
+    
+    init();
+    port = 443;
+    gnutls_certificate_allocate_credentials(&creds);
+    keyfile = crtfile = NULL;
+    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("\ttrust=CA-FILE   [no default]\n");
+           printf("\t\tThe name of a file to read trusted certificates from.\n");
+           printf("\t\tMay be given multiple times.\n");
+           printf("\tcrl=CRL-FILE    [no default]\n");
+           printf("\t\tThe name of a file to read revocation lists from.\n");
+           printf("\t\tMay be given multiple times.\n");
+           printf("\tport=PORT       [443]\n");
+           printf("\t\tThe TCP port to listen on.\n");
+           printf("\n");
+           printf("\tAll X.509 data files must be PEM-encoded.\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], "trust")) {
+           if((ret = gnutls_certificate_set_x509_trust_file(creds, argv[i], GNUTLS_X509_FMT_PEM)) != 0) {
+               flog(LOG_ERR, "ssl: could not load trust file `%s': %s", argv[i], gnutls_strerror(ret));
+               exit(1);
+           }
+       } else if(!strcmp(argp[i], "crl")) {
+           if((ret = gnutls_certificate_set_x509_crl_file(creds, argv[i], GNUTLS_X509_FMT_PEM)) != 0) {
+               flog(LOG_ERR, "ssl: could not load CRL file `%s': %s", argv[i], gnutls_strerror(ret));
+               exit(1);
+           }
+       } 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((ret = gnutls_certificate_set_x509_key_file(creds, crtfile, keyfile, GNUTLS_X509_FMT_PEM)) != 0) {
+       flog(LOG_ERR, "ssl: could not load certificate or key: %s", gnutls_strerror(ret));
+       exit(1);
+    }
+    gnutls_certificate_set_dh_params(creds, dhparams);
+    if((fd = listensock6(port)) < 0) {
+       flog(LOG_ERR, "could not listen on IPv6 port (port %i): %s", port, strerror(errno));
+       exit(1);
+    }
+    omalloc(pd);
+    pd->fd = fd;
+    pd->sport = port;
+    pd->creds = creds;
+    mustart(listenloop, pd);
+    if((fd = listensock6(port)) < 0) {
+       if(errno != EADDRINUSE) {
+           flog(LOG_ERR, "could not listen on IPv6 port (port %i): %s", port, strerror(errno));
+           exit(1);
+       }
+    } else {
+       omalloc(pd);
+       pd->fd = fd;
+       pd->sport = port;
+       pd->creds = creds;
+       mustart(listenloop, pd);
+    }
+}
+
+#endif