src/server/util/io.c

Mon, 26 Dec 2016 16:46:55 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Mon, 26 Dec 2016 16:46:55 +0100
changeset 129
fd324464f56f
parent 126
631aaa01b2b5
child 133
87b405d61f64
permissions
-rw-r--r--

adds support for ssl cert chain files and improves ssl 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.
 */

#ifdef __gnu_linux__
#define _GNU_SOURCE
#endif

#include <unistd.h>
#include <stdlib.h>
#include <sys/uio.h>
#include <sys/uio.h>

#if defined(LINUX) || defined(SOLARIS)
#include <sys/sendfile.h>
#define WS_SENDFILE
#elif defined(BSD)
#if defined(__NetBSD__) || defined(__OpenBSD__)
#define net_sys_sendfile net_fallback_sendfile
#else
#define WS_SENDFILE
#endif
#else
#define net_sys_sendfile net_fallback_sendfile
#endif

#include "../daemon/vfs.h"
#include "io.h"
#include "pool.h"
#include "ucx/utils.h"

IOStream native_io_funcs = {
    (io_write_f)net_sys_write,
    (io_writev_f)net_sys_writev,
    (io_read_f)net_sys_read,
    (io_sendfile_f)net_sys_sendfile,
    (io_close_f)net_sys_close,
    NULL
};

IOStream http_io_funcs = {
    (io_write_f)net_http_write,
    (io_writev_f)net_http_writev,
    (io_read_f)net_http_read,
    (io_sendfile_f)net_http_sendfile,
    (io_close_f)net_http_close,
    (io_finish_f)net_http_finish
};

IOStream ssl_io_funcs = {
    (io_write_f)net_ssl_write,
    (io_writev_f)net_ssl_writev,
    (io_read_f)net_ssl_read,
    NULL,
    (io_close_f)net_ssl_close,
    (io_finish_f)net_ssl_finish
};


/*
 * SysStream implementation
 */

IOStream* sysstream_new(pool_handle_t *pool, int fd) {
    SysStream *st = pool_malloc(pool, sizeof(SysStream));
    st->st = native_io_funcs;
    st->fd = fd;
    return (IOStream*)st;
}

#ifdef XP_UNIX
ssize_t net_sys_write(SysStream *st, void *buf, size_t nbytes) {
    return write(st->fd, buf, nbytes);
}

ssize_t net_sys_writev(SysStream *st, struct iovec *iovec, int iovcnt) {
    return writev(st->fd, iovec, iovcnt);
}

ssize_t net_sys_read(SysStream *st, void *buf, size_t nbytes) {
    return read(st->fd, buf, nbytes);
}

#ifdef WS_SENDFILE
ssize_t net_sys_sendfile(SysStream *st, sendfiledata *sfd) {
    ssize_t ret = 0;
    off_t fileoffset = sfd->offset;
    if(sfd->fd->fd != -1) {
#ifdef BSD
        struct iovec hdvec;
        hdvec.iov_base = (void*)sfd->header;
        hdvec.iov_len = sfd->hlen;
        struct iovec trvec;
        trvec.iov_base = (void*)sfd->trailer;
        trvec.iov_len = sfd->tlen;
        struct sf_hdtr hdtr;
        hdtr.headers = &hdvec;
        hdtr.hdr_cnt = 1;
        hdtr.trailers = &trvec;
        hdtr.trl_cnt = 1;
        
        off_t len = sfd->len;
#ifdef OSX
        ret = sendfile(sfd->fd->fd, st->fd, fileoffset, &len, &hdtr, 0);
#else // BSD
        ret = sendfile(
                sfd->fd->fd,
                st->fd,
                fileoffset,
                sfd->len,
                &hdtr,
                NULL,
                0);
#endif
#else // Solaris/Linux
        if(sfd->header) {
            ret += write(st->fd, sfd->header, sfd->hlen);
        }
        ret += sendfile(st->fd, sfd->fd->fd, &fileoffset, sfd->len);
        if(sfd->trailer) {
            ret += write(st->fd, sfd->trailer, sfd->tlen);
        }
#endif
    } else {
        return net_fallback_sendfile((IOStream*)st, sfd);
    }
    
    return ret;
}
#endif

void net_sys_close(SysStream *st) {
    close(st->fd);
}

#elif defined(XP_WIN32)

ssize_t net_sys_write(SysStream *st, void *buf, size_t nbytes) {
    int ret = send(st->socket, buf, nbytes, 0);
    if(ret == SOCKET_ERROR) {
        return IO_ERROR;
    }
    return ret;
}

ssize_t net_sys_writev(SysStream *st, struct iovec *iovec, int iovcnt) {
    // TODO
}

ssize_t net_sys_read(SysStream *st, void *buf, size_t nbytes) {
    int ret = recv(st->socket, buf, nbytes, 0);
    if(ret == SOCKET_ERROR) {
        return IO_ERROR;
    }
    return ret;
}

ssize_t net_sys_sendfile(SysStream *st, sendfiledata *sfd) {
    // TODO
}

void net_sys_close(SysStream *st) {
    closesocket(st->socket);
}

#endif


/*
 * HttpStream implementation
 */

IOStream* httpstream_new(pool_handle_t *pool, IOStream *fd) {
    HttpStream *st = pool_malloc(pool, sizeof(HttpStream));
    st->st = http_io_funcs;
    st->fd = fd;
    st->max_read = 0;
    st->read = 0;
    st->chunked_enc = WS_FALSE;
    st->buffered = WS_FALSE;
    return (IOStream*)st;
}

ssize_t net_http_write(HttpStream *st, void *buf, size_t nbytes) {
    IOStream *fd = st->fd;
    if(st->chunked_enc) {
        // TODO: on some plattforms iov_len is smaller than size_t
        struct iovec io[3];
        char chunk_len[16];
        io[0].iov_base = chunk_len;
        io[0].iov_len = snprintf(chunk_len, 16, "%zx\r\n", nbytes);
        io[1].iov_base = buf;
        io[1].iov_len = nbytes;
        io[2].iov_base = "\r\n";
        io[2].iov_len = 2;
        ssize_t r = fd->writev(fd, io, 3);
        return r - io[0].iov_len;
    } else {
        return fd->write(fd, buf, nbytes);
    }
}

ssize_t net_http_writev(HttpStream *st, struct iovec *iovec, int iovcnt) {
    IOStream *fd = st->fd;
    if(st->chunked_enc) {
        struct iovec *io = calloc(iovcnt + 1, sizeof(struct iovec));
        char chunk_len[16];
        io[0].iov_base = chunk_len;
        size_t len = 0;
        for(int i=0;i<iovcnt;i++) {
            len += iovec[i].iov_len;
        }
        io[0].iov_len = snprintf(chunk_len, 16, "\r\n%zx\r\n", len);
        memcpy(io + 1, iovec, iovcnt * sizeof(struct iovec));
        ssize_t r = fd->writev(fd, io, iovcnt + 1);
        return r - io[0].iov_len;
    } else {
        return fd->writev(fd, iovec, iovcnt);
    }
}

ssize_t net_http_read(HttpStream *st, void *buf, size_t nbytes) {
    if(st->max_read != 0 && st->read >= st->max_read) {
        return 0;
    }
    ssize_t r = st->fd->read(st->fd, buf, nbytes);
    st->read += r;
    return r;
}

ssize_t net_http_sendfile(HttpStream *st, sendfiledata *sfd) {  
    ssize_t ret = 0;
    // TODO: support chunked transfer encoding
    if(st->fd->sendfile) {
        ret = st->fd->sendfile(st->fd, sfd);
    } else {
        ret = net_fallback_sendfile((IOStream*)st, sfd);
    }
    
    return ret;
}

void net_http_close(HttpStream *st) {
    st->fd->close(st->fd);
}

void net_http_finish(HttpStream *st) {
    if(st->chunked_enc) {
        st->fd->write(st->fd, "0\r\n\r\n", 5);
    }
}


/*
 * SSLStream implementation
 */

IOStream* sslstream_new(pool_handle_t *pool, SSL *ssl) {
    SSLStream *st = pool_malloc(pool, sizeof(SSLStream));
    st->st = ssl_io_funcs;
    st->ssl = ssl;
    return (IOStream*)st;
}

ssize_t net_ssl_write(SSLStream *st, void *buf, size_t nbytes) {
    return SSL_write(st->ssl, buf, nbytes);
}

ssize_t net_ssl_writev(SSLStream *st, struct iovec *iovec, int iovcnt) {
    ssize_t r = 0;
    for(int i=0;i<iovcnt;i++) {
        int ret = SSL_write(st->ssl, iovec[i].iov_base, iovec[i].iov_len);
        if(ret <= 0) {
            return 0;
        }
        r += ret;
    }
    return r;
}

ssize_t net_ssl_read(SSLStream *st, void *buf, size_t nbytes) {
    return SSL_read(st->ssl, buf, nbytes);
}

void net_ssl_close(SSLStream *st) {
    SSL_shutdown(st->ssl);
    close(SSL_get_fd(st->ssl));
}

void net_ssl_finish(SSLStream *st) {
    
}


/* -------------------- public nsapi network functions -------------------- */

ssize_t net_read(SYS_NETFD fd, void *buf, size_t nbytes) {
    ssize_t r = ((IOStream*)fd)->read(fd, buf, nbytes);
    if(r == 0) {
        return IO_EOF;
    }
    return r;
}

ssize_t net_write(SYS_NETFD fd, void *buf, size_t nbytes) {
    ssize_t r = ((IOStream*)fd)->write(fd, buf, nbytes);
    if(r < 0) {
        return IO_ERROR;
    }  
    return r;
}

ssize_t net_writev(SYS_NETFD fd, struct iovec *iovec, int iovcnt) {
    ssize_t r = ((IOStream*)fd)->writev(fd, iovec, iovcnt);
    if(r < 0) {
        return IO_ERROR;
    }
    return r;
}

ssize_t net_printf(SYS_NETFD fd, char *format, ...) {
    va_list arg;
    va_start(arg, format);
    sstr_t buf = ucx_vasprintf(ucx_default_allocator(), format, arg);
    ssize_t r = net_write(fd, buf.ptr, buf.length);
    free(buf.ptr);
    return r;
}

ssize_t net_sendfile(SYS_NETFD fd, sendfiledata *sfd) {
    IOStream *out = fd;
    if(out->sendfile && sfd->fd && sfd->fd->fd != -1) {
        ssize_t r = out->sendfile(fd, sfd);
        if(r < 0) {
            return IO_ERROR;
        }
    } else {
        // stream/file does not support sendfile
        // do regular copy
        return net_fallback_sendfile(out, sfd);
    }
    return IO_ERROR;
}

// private
ssize_t net_fallback_sendfile(IOStream *fd, sendfiledata *sfd) {
    char *buf = malloc(4096);
    if(!buf) {
        // TODO: out of memory error
        return IO_ERROR;
    }
    char *header = (char*)sfd->header;
    int hlen = sfd->hlen;
    char *trailer = (char*)sfd->trailer;
    int tlen = sfd->tlen;
    if(header == NULL) {
        hlen = 0;
    }
    if(trailer == NULL) {
        tlen = 0;
    }

    ssize_t r;
    while(hlen > 0) {
        r = fd->write(fd, header, hlen);
        header += r;
        hlen -= r;
        if(r <= 0) {
            free(buf);
            return IO_ERROR;
        }
    }

    if(system_lseek(sfd->fd, sfd->offset, SEEK_SET) == -1) {
        free(buf);
        return IO_ERROR;
    }

    size_t length = sfd->len;
    while(length > 0) {
        if((r = system_fread(sfd->fd, buf, 4096)) <= 0) {
            break;
        }
        char *write_buf = buf;
        while(r > 0) {
            ssize_t w = fd->write(fd, write_buf, r);
            r -= w;
            length -= w;
            write_buf += w;
        }
    }
    free(buf);
    if(length > 0) {
        return IO_ERROR;
    }

    while(tlen > 0) {
        r = fd->write(fd, trailer, tlen);
        trailer += r;
        tlen -= r;
        if(r <= 0) {
            return IO_ERROR;
        }
    }

    return sfd->hlen + sfd->len + sfd->tlen;
}

int net_flush(SYS_NETFD sd) {
    // TODO: implement
    return 0;
}

void net_close(SYS_NETFD fd) {
    ((IOStream*)fd)->close(fd);
}

// private
void net_finish(SYS_NETFD fd) {
    ((IOStream*)fd)->finish(fd);
}

mercurial