src/server/daemon/sessionhandler.c

Wed, 02 Nov 2022 18:21:58 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Wed, 02 Nov 2022 18:21:58 +0100
changeset 411
bbd82eee568e
parent 410
8f4d28ac6ae2
child 412
a4e2ce073c0f
permissions
-rw-r--r--

improve sessionhandler error handling

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2013 Olaf Wintermann. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

#include "../public/nsapi.h"

#include "sessionhandler.h"
#include "httprequest.h"
#include "httpparser.h"
#include "log.h"
#include "error.h"
#include "httplistener.h"

typedef struct _event_http_io {
    HTTPRequest  *request;
    HttpParser   *parser;
    int          error;
} EventHttpIO;


int connection_read(Connection *conn, void *buf, int len) {
    return (int)read(conn->fd, buf, len);
}

int connection_write(Connection *conn, const void *buf, int len) {
    return (int)write(conn->fd, buf, len);
}

void connection_close(Connection *conn) {
    while(close(conn->fd)) {
        if(errno != EINTR) {
            log_ereport(LOG_VERBOSE, "connection close failed: %s", strerror(errno));
            break;
        }
        log_ereport(LOG_VERBOSE, "connection close: EINTR");
    }
}

int connection_ssl_read(Connection *conn, void *buf, int len) {
    int ret = SSL_read(conn->ssl, buf, len);
    if(ret <= 0) {
        conn->ssl_error = SSL_get_error(conn->ssl, ret);
    }
    return ret;
}

int connection_ssl_write(Connection *conn, const void *buf, int len) {
    int ret = SSL_write(conn->ssl, buf, len);
    if(ret <= 0) {
        conn->ssl_error = SSL_get_error(conn->ssl, ret);
    }
    return ret;
}

void connection_ssl_close(Connection *conn) {
    if(!conn->ssl_error) {
        int ret = SSL_shutdown(conn->ssl);
        if(ret != 1) {
            conn->ssl_error = SSL_get_error(conn->ssl, ret);
            log_ereport(LOG_VERBOSE, "SSL_shutdown failed: %d", conn->ssl_error);
        }
    }
    while(close(conn->fd)) {
        if(errno != EINTR) {
            log_ereport(LOG_VERBOSE, "connection close failed: %s", strerror(errno));
            break;
        }
        log_ereport(LOG_VERBOSE, "connection close: EINTR");
    }
}

void connection_destroy(Connection *conn) {
    cfg_unref(conn->listener->cfg);
    conn->close(conn);
    if(conn->ssl) {
        SSL_free(conn->ssl);
    }
    free(conn);    
}

IOStream* create_connection_iostream(
        SessionHandler *sh,
        Connection *conn,
        pool_handle_t *pool,
        WSBool *ssl)
{
    IOStream *io = NULL;
    if(conn->ssl) {
        io = sslstream_new(pool, conn->ssl);
        *ssl = 1;
    } else {
        io = sysstream_new(pool, conn->fd);
        *ssl = 0;
    }
    return io;
}


SessionHandler* create_basic_session_handler() {
    BasicSessionHandler *handler = malloc(sizeof(BasicSessionHandler));
    handler->threadpool = threadpool_new(4, 8);
    threadpool_start(handler->threadpool); // TODO: handle error
    handler->sh.enqueue_connection = basic_enq_conn;
    handler->sh.keep_alive = basic_keep_alive;
    handler->sh.create_iostream = create_connection_iostream;

    return (SessionHandler*)handler;
}

void basic_enq_conn(SessionHandler *handler, Connection *conn) {
    BasicSessionHandler *sh = (BasicSessionHandler*)handler;
    conn->session_handler = handler;
    threadpool_run(sh->threadpool, basic_run_session, conn);
}

void* basic_run_session(void *data) {
    Connection *conn = (Connection*)data;

    HTTPRequest *request = malloc(sizeof(HTTPRequest));
    http_request_init(request);
    request->connection = conn;

    // read request
    netbuf *buf = malloc(sizeof(netbuf));
    buf->rdtimeout = 120;
    buf->pos = 0;
    buf->cursize = 0;
    buf->maxsize = 2048;
    buf->sd = &conn->fd;
    buf->inbuf = malloc(2048);
    buf->errmsg = NULL;

    request->netbuf = buf;

    HttpParser *parser = http_parser_new(request);
    int state;
    int r;
    r = conn->read(conn, buf->inbuf + buf->pos, buf->maxsize - buf->pos);
    
    if(r > 0) {
        int err = 0;
        buf->cursize += r;
        while((state = http_parser_process(parser)) != 0) {
            if(state == 2) {
                log_ereport(LOG_FAILURE, "basic_run_session: invalid http request");
                err = 1;
                break;
            }
            r = conn->read(conn, buf->inbuf + buf->pos, buf->maxsize - buf->pos);
            if(r == -1) {
                log_ereport(LOG_FAILURE, "basic_run_session: IO error: %s", strerror(errno));
                err = 1;
                break;
            }
            buf->cursize += r;
        }
        
        if(!err) {
            if(http_parser_validate(parser)) {
                // process request
                r = handle_request(request, NULL, NULL); // TODO: use correct thread pool
            } else {
                log_ereport(LOG_FAILURE, "basic_run_session: http parser validation failed");
                fatal_error(request, 400);
            }
        }
    } else {
        log_ereport(LOG_FAILURE, "basic_run_session: IO error: %s", strerror(errno));
    }
    
    free(buf->inbuf);
    free(buf);
    connection_destroy(conn);
    http_parser_free(parser);
    http_request_cleanup(request);
    
    return NULL;
}

void basic_keep_alive(SessionHandler *handler, Connection *conn) {
    connection_destroy(conn);
}


/* ----- event session handler ----- */

SessionHandler* create_event_session_handler() {
    EventSessionHandler *handler = malloc(sizeof(EventSessionHandler));
    handler->eventhandler = get_default_event_handler();
    handler->sh.enqueue_connection = evt_enq_conn;
    handler->sh.keep_alive = evt_keep_alive;
    handler->sh.create_iostream = create_connection_iostream;
    return (SessionHandler*)handler;
}

void evt_enq_conn(SessionHandler *handler, Connection *conn) {
    // set socket non blocking
    int flags;
    if ((flags = fcntl(conn->fd, F_GETFL, 0)) == -1) {
        flags = 0;
    }
    if (fcntl(conn->fd, F_SETFL, flags | O_NONBLOCK) != 0) {
        log_ereport(LOG_FAILURE, "sessionhandler: fcntl failed: %s", strerror(errno));
        connection_destroy(conn);
        return;
    }
    
    HTTPRequest *request = malloc(sizeof(HTTPRequest));
    if(!request) {
        connection_destroy(conn);
        return;
    }
    http_request_init(request);
    request->connection = conn;
    conn->session_handler = handler;
    
    // TODO: remove code redundancy (basic_run_session)
    
    // read request
    netbuf *buf = malloc(sizeof(netbuf));
    if(!buf) {
        connection_destroy(conn);
        http_request_cleanup(request);
        return;
    }
    buf->rdtimeout = 120;
    buf->pos = 0;
    buf->cursize = 0;
    buf->maxsize = 2048;
    buf->sd = &conn->fd;
    buf->errmsg = NULL;
    buf->inbuf = malloc(2048);
    if(!buf->inbuf) {
        connection_destroy(conn);
        http_request_cleanup(request);
        free(buf);
        return;
    }

    request->netbuf = buf;
    
    HttpParser *parser = http_parser_new(request);
    if(!parser) {
        connection_destroy(conn);
        http_request_cleanup(request);
        free(buf->inbuf);
        free(buf);
        return;
    }
    
    EventHttpIO *io = malloc(sizeof(EventHttpIO));
    if(io == NULL) {
        connection_destroy(conn);
        http_request_cleanup(request);
        free(buf->inbuf);
        free(buf);
        http_parser_free(parser);
        return;
    }
    io->request = request;
    io->parser  = parser;
    io->error = 0;
    
    /*
     * to start the request handling, we begin with a poll on the socket,
     * 
     * evt_enq_conn() --> event handler --> handle_request()
     */
    
    Event *event = malloc(sizeof(Event));
    ZERO(event, sizeof(Event));
    event->fn = conn->ssl && !conn->ssl_accepted ? evt_request_ssl_accept : evt_request_input;
    event->finish = evt_request_finish;
    event->cookie = io;
    
    EventHandler *ev = ev_instance(((EventSessionHandler*)handler)->eventhandler);
    
    if(ev_pollin(ev, conn->fd, event) != 0) {
        // TODO: ev_pollin should log, intercept some errors here
        log_ereport(LOG_FAILURE, "Cannot enqueue connection");
        evt_request_error(ev, event);
    }
}

int evt_request_ssl_accept(EventHandler *handler, Event *event) {
    EventHttpIO *io = event->cookie;
    Connection  *conn = io->request->connection;
    
    int ret = SSL_accept(conn->ssl);
    if(ret <= 0) {
        int error = SSL_get_error(conn->ssl, ret);
        char *errstr;
        switch(error) {
            default: errstr = "unknown"; break;
            case SSL_ERROR_WANT_READ: {
                event->events = EVENT_POLLIN;
                return 1;
            }
            case SSL_ERROR_WANT_WRITE: {
                event->events = EVENT_POLLOUT;
                return 1;
            }
            case SSL_ERROR_ZERO_RETURN: errstr = "SSL_ERROR_ZERO_RETURN"; break;
            case SSL_ERROR_WANT_CONNECT: errstr = "SSL_ERROR_WANT_CONNECT"; break;
            case SSL_ERROR_WANT_ACCEPT: errstr = "SSL_ERROR_WANT_ACCEPT"; break;
            case SSL_ERROR_WANT_X509_LOOKUP: errstr = "SSL_ERROR_WANT_X509_LOOKUP"; break;
            case SSL_ERROR_SYSCALL: errstr = "SSL_ERROR_SYSCALL"; break;
            case SSL_ERROR_SSL: errstr = "SSL_ERROR_SSL"; break; 
        }
        log_ereport(LOG_VERBOSE, "SSL accept error[%d]: %s", error, errstr);
        event->finish = evt_request_error;
        io->error = 1;
        return 0;
    }
    conn->ssl_accepted = WS_TRUE;
    
    // SSL_accept successful, start request input now
    event->fn = evt_request_input;
    return evt_request_input(handler, event);
}

int evt_request_input(EventHandler *handler, Event *event) {    
    EventHttpIO *io = event->cookie;
    HttpParser  *parser  = io->parser;
    HTTPRequest *request = io->request;
    Connection  *conn = io->request->connection;
    netbuf      *buf     = request->netbuf;
    
    int state;
    int r;
    r = conn->read(
            conn,
            buf->inbuf + buf->pos,
            buf->maxsize - buf->pos);
    if(r <= 0) {
        if(conn->ssl) {
            // SSL specific error handling
            switch(conn->ssl_error) {
                case SSL_ERROR_WANT_READ: {
                    event->events = EVENT_POLLIN;
                    return 1;
                }
                case SSL_ERROR_WANT_WRITE: {
                    event->events = EVENT_POLLOUT;
                    return 1;
                }
            }
        }
        
        event->finish = evt_request_error;
        io->error = 1;
        return 0;
    }
    //fwrite(buf->inbuf + buf->pos, 1, r, stdout);
    //printf("\n");
    
    buf->cursize += r;
    state = http_parser_process(parser);
    if(state == 2) {
        // parse error
        fatal_error(request, 400);
        log_ereport(LOG_VERBOSE, "http parser: bad request");
        //printf("\n\n%.*s\n\n", parser->request->netbuf->cursize, parser->request->netbuf->inbuf);
        //fflush(stdout);
        event->finish = evt_request_error;
        io->error = 2;
        return 0;
    } else if(state == 1) {
        /*
         * we need more data -> return 1 to tell the event handler to
         * continue polling
         */
        event->events = EVENT_POLLIN;
        return 1;
    }
    
    // we are done with reading
    
    // set socket blocking
    int flags;
    if (-1 == (flags = fcntl(request->connection->fd, F_GETFL, 0))) {
        flags = 0;
    }
    if (fcntl(request->connection->fd, F_SETFL, flags & ~O_NONBLOCK) != 0) {
        // just close the connection if fcntl fails
        event->finish = evt_request_error;
        io->error = 3;
        return 0;
    }  
    
    if(!http_parser_validate(parser)) {
        log_ereport(LOG_FAILURE, "http_parser_validate failed");
        fatal_error(request, 400);
        event->finish = evt_request_error;
        return 0;
    }
    
    /*
     * process request
     * 
     * We return 0 to finish request input. The event handler than stops
     * polling and executes event->finish (evt_request_input_finish)
     */
    return 0;
}

int evt_request_finish(EventHandler *h, Event *event) { 
    EventHttpIO *io = event->cookie;
    HttpParser  *parser  = io->parser;
    HTTPRequest *request = io->request;
      
    int r = handle_request(request, NULL, h);
    if(r != 0) {
        // TODO: error message
        connection_destroy(request->connection);
    }
    
    /*
     * handle_request can return before the request is finished, but it copies
     * all important data. We can free request, parser and event
     * 
     * don't free request->netbuf and request->connection
     */
    http_request_cleanup(request);
    http_parser_free(parser);
    
    free(io);
    free(event);
    
    return 0;
}

int evt_request_error(EventHandler *h, Event *event) { 
    EventHttpIO *io = event->cookie;
    HttpParser  *parser  = io->parser;
    HTTPRequest *request = io->request;
    
    if(event->error) {
        log_ereport(LOG_VERBOSE, "sessionhandler http io error: %d fd: %d", io->error, request->connection->fd);
    }
    
    free(request->netbuf->inbuf);
    free(request->netbuf);
    
    connection_destroy(request->connection);
    
    http_request_cleanup(request);
    http_parser_free(parser);
    
    free(io);
    free(event);
    
    return 0;
}

void evt_keep_alive(SessionHandler *handler, Connection *conn) {
    // TODO: set timeout
    
    /* TODO:
     * Don't just re-enqueue the connection
     * create a evt_req_init function which does most of the evt_enq_conn stuff
     * but don't poll.
     * evt_keep_alive should poll and if an event occurs:
     *   evt_req_init
     *   evt_request_input
     * evt_enq_conn should do:
     *   evt_req_init
     *   ev_pollin
     */
    evt_enq_conn(handler, conn);
}

mercurial