diff options
Diffstat (limited to 'testcode/petal.c')
-rw-r--r-- | testcode/petal.c | 618 |
1 files changed, 618 insertions, 0 deletions
diff --git a/testcode/petal.c b/testcode/petal.c new file mode 100644 index 000000000000..14a187e3ff26 --- /dev/null +++ b/testcode/petal.c @@ -0,0 +1,618 @@ +/* + * petal.c - https daemon that is small and beautiful. + * + * Copyright (c) 2010, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 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. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * 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 REGENTS 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. + */ + +/** + * \file + * + * HTTP1.1/SSL server. + */ + +#include "config.h" +#ifdef HAVE_GETOPT_H +#include <getopt.h> +#endif +#ifdef HAVE_OPENSSL_SSL_H +#include <openssl/ssl.h> +#endif +#ifdef HAVE_OPENSSL_ERR_H +#include <openssl/err.h> +#endif +#ifdef HAVE_OPENSSL_RAND_H +#include <openssl/rand.h> +#endif +#include <openssl/x509.h> +#include <openssl/pem.h> +#include <ctype.h> +#include <signal.h> +#if defined(UNBOUND_ALLOC_LITE) || defined(UNBOUND_ALLOC_STATS) +#ifdef malloc +#undef malloc +#endif +#ifdef free +#undef free +#endif +#endif /* alloc lite or alloc stats */ + +/** verbosity for this application */ +static int verb = 0; + +/** Give petal usage, and exit (1). */ +static void +usage() +{ + printf("Usage: petal [opts]\n"); + printf(" https daemon serves files from ./'host'/filename\n"); + printf(" (no hostname: from the 'default' directory)\n"); + printf("-a addr bind to this address, 127.0.0.1\n"); + printf("-p port port number, default 443\n"); + printf("-k keyfile SSL private key file (PEM), petal.key\n"); + printf("-c certfile SSL certificate file (PEM), petal.pem\n"); + printf("-v more verbose\n"); + printf("-h show this usage help\n"); + printf("Version %s\n", PACKAGE_VERSION); + printf("BSD licensed, see LICENSE in source package for details.\n"); + printf("Report bugs to %s\n", PACKAGE_BUGREPORT); + exit(1); +} + +/** fatal exit */ +static void print_exit(const char* str) {printf("error %s\n", str); exit(1);} +/** print errno */ +static void log_errno(const char* str) +{printf("error %s: %s\n", str, strerror(errno));} + +/** parse a text IP address into a sockaddr */ +static int +parse_ip_addr(char* str, int port, struct sockaddr_storage* ret, socklen_t* l) +{ + socklen_t len = 0; + struct sockaddr_storage* addr = NULL; + struct sockaddr_in6 a6; + struct sockaddr_in a; + uint16_t p = (uint16_t)port; + int fam = 0; + memset(&a6, 0, sizeof(a6)); + memset(&a, 0, sizeof(a)); + + if(inet_pton(AF_INET6, str, &a6.sin6_addr) > 0) { + /* it is an IPv6 */ + fam = AF_INET6; + a6.sin6_family = AF_INET6; + a6.sin6_port = (in_port_t)htons(p); + addr = (struct sockaddr_storage*)&a6; + len = (socklen_t)sizeof(struct sockaddr_in6); + } + if(inet_pton(AF_INET, str, &a.sin_addr) > 0) { + /* it is an IPv4 */ + fam = AF_INET; + a.sin_family = AF_INET; + a.sin_port = (in_port_t)htons(p); + addr = (struct sockaddr_storage*)&a; + len = (socklen_t)sizeof(struct sockaddr_in); + } + if(!len) print_exit("cannot parse addr"); + *l = len; + memmove(ret, addr, len); + return fam; +} + +/** close the fd */ +static void +fd_close(int fd) +{ +#ifndef USE_WINSOCK + close(fd); +#else + closesocket(fd); +#endif +} + +/** + * Read one line from SSL + * zero terminates. + * skips "\r\n" (but not copied to buf). + * @param ssl: the SSL connection to read from (blocking). + * @param buf: buffer to return line in. + * @param len: size of the buffer. + * @return 0 on error, 1 on success. + */ +static int +read_ssl_line(SSL* ssl, char* buf, size_t len) +{ + size_t n = 0; + int r; + int endnl = 0; + while(1) { + if(n >= len) { + if(verb) printf("line too long\n"); + return 0; + } + if((r = SSL_read(ssl, buf+n, 1)) <= 0) { + if(SSL_get_error(ssl, r) == SSL_ERROR_ZERO_RETURN) { + /* EOF */ + break; + } + if(verb) printf("could not SSL_read\n"); + return 0; + } + if(endnl && buf[n] == '\n') { + break; + } else if(endnl) { + /* bad data */ + if(verb) printf("error: stray linefeeds\n"); + return 0; + } else if(buf[n] == '\r') { + /* skip \r, and also \n on the wire */ + endnl = 1; + continue; + } else if(buf[n] == '\n') { + /* skip the \n, we are done */ + break; + } else n++; + } + buf[n] = 0; + return 1; +} + +/** process one http header */ +static int +process_one_header(char* buf, char* file, size_t flen, char* host, size_t hlen, + int* vs) +{ + if(strncasecmp(buf, "GET ", 4) == 0) { + char* e = strstr(buf, " HTTP/1.1"); + if(!e) e = strstr(buf, " http/1.1"); + if(!e) { + e = strstr(buf, " HTTP/1.0"); + if(!e) e = strstr(buf, " http/1.0"); + if(!e) e = strrchr(buf, ' '); + if(!e) e = strrchr(buf, '\t'); + if(e) *vs = 10; + } + if(e) *e = 0; + if(strlen(buf) < 4) return 0; + (void)strlcpy(file, buf+4, flen); + } else if(strncasecmp(buf, "Host: ", 6) == 0) { + (void)strlcpy(host, buf+6, hlen); + } + return 1; +} + +/** read http headers and process them */ +static int +read_http_headers(SSL* ssl, char* file, size_t flen, char* host, size_t hlen, + int* vs) +{ + char buf[1024]; + file[0] = 0; + host[0] = 0; + while(read_ssl_line(ssl, buf, sizeof(buf))) { + if(verb>=2) printf("read: %s\n", buf); + if(buf[0] == 0) + return 1; + if(!process_one_header(buf, file, flen, host, hlen, vs)) + return 0; + } + return 0; +} + +/** setup SSL context */ +static SSL_CTX* +setup_ctx(char* key, char* cert) +{ + SSL_CTX* ctx = SSL_CTX_new(SSLv23_server_method()); + if(!ctx) print_exit("out of memory"); + (void)SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2); + if(!SSL_CTX_use_certificate_file(ctx, cert, SSL_FILETYPE_PEM)) + print_exit("cannot read cert"); + if(!SSL_CTX_use_PrivateKey_file(ctx, key, SSL_FILETYPE_PEM)) + print_exit("cannot read key"); + if(!SSL_CTX_check_private_key(ctx)) + print_exit("private key is not correct"); + if(!SSL_CTX_load_verify_locations(ctx, cert, NULL)) + print_exit("cannot load cert verify locations"); + return ctx; +} + +/** setup listening TCP */ +static int +setup_fd(char* addr, int port) +{ + struct sockaddr_storage ad; + socklen_t len; + int fd; + int c = 1; + int fam = parse_ip_addr(addr, port, &ad, &len); + fd = socket(fam, SOCK_STREAM, 0); + if(fd == -1) { + log_errno("socket"); + return -1; + } + if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, + (void*)&c, (socklen_t) sizeof(int)) < 0) { + log_errno("setsockopt(SOL_SOCKET, SO_REUSEADDR)"); + } + if(bind(fd, (struct sockaddr*)&ad, len) == -1) { + log_errno("bind"); + fd_close(fd); + return -1; + } + if(listen(fd, 5) == -1) { + log_errno("listen"); + fd_close(fd); + return -1; + } + return fd; +} + +/** setup SSL connection to the client */ +static SSL* +setup_ssl(int s, SSL_CTX* ctx) +{ + SSL* ssl = SSL_new(ctx); + if(!ssl) return NULL; + SSL_set_accept_state(ssl); + (void)SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); + if(!SSL_set_fd(ssl, s)) { + SSL_free(ssl); + return NULL; + } + return ssl; +} + +/** check a file name for safety */ +static int +file_name_is_safe(char* s) +{ + size_t l = strlen(s); + if(s[0] != '/') + return 0; /* must start with / */ + if(strstr(s, "/../")) + return 0; /* no updirs in URL */ + if(l>=3 && s[l-1]=='.' && s[l-2]=='.' && s[l-3]=='/') + return 0; /* ends with /.. */ + return 1; +} + +/** adjust host and filename */ +static void +adjust_host_file(char* host, char* file) +{ + size_t i, len; + /* remove a port number if present */ + if(strrchr(host, ':')) + *strrchr(host, ':') = 0; + /* lowercase */ + len = strlen(host); + for(i=0; i<len; i++) + host[i] = tolower((unsigned char)host[i]); + len = strlen(file); + for(i=0; i<len; i++) + file[i] = tolower((unsigned char)file[i]); +} + +/** check a host name for safety */ +static int +host_name_is_safe(char* s) +{ + if(strchr(s, '/')) + return 0; + if(strcmp(s, "..") == 0) + return 0; + if(strcmp(s, ".") == 0) + return 0; + return 1; +} + +/** provide file in whole transfer */ +static void +provide_file_10(SSL* ssl, char* fname) +{ + char* buf, *at; + size_t len, avail, header_reserve=1024; + FILE* in = fopen(fname, +#ifndef USE_WINSOCK + "r" +#else + "rb" +#endif + ); + int r; + const char* rcode = "200 OK"; + if(!in) { + char hdr[1024]; + rcode = "404 File not found"; + r = snprintf(hdr, sizeof(hdr), "HTTP/1.1 %s\r\n\r\n", rcode); + if(SSL_write(ssl, hdr, r) <= 0) { + /* write failure */ + } + return; + } + fseek(in, 0, SEEK_END); + len = (size_t)ftell(in); + fseek(in, 0, SEEK_SET); + /* plus some space for the header */ + buf = (char*)malloc(len+header_reserve); + if(!buf) { + fclose(in); + return; + } + avail = len+header_reserve; + at = buf; + r = snprintf(at, avail, "HTTP/1.1 %s\r\n", rcode); + at += r; + avail -= r; + r = snprintf(at, avail, "Server: petal/%s\r\n", PACKAGE_VERSION); + at += r; + avail -= r; + r = snprintf(at, avail, "Content-Length: %u\r\n", (unsigned)len); + at += r; + avail -= r; + r = snprintf(at, avail, "\r\n"); + at += r; + avail -= r; + if(avail < len) { /* robust */ + free(buf); + fclose(in); + return; + } + if(fread(at, 1, len, in) != len) { + free(buf); + fclose(in); + return; + } + fclose(in); + at += len; + avail -= len; + if(SSL_write(ssl, buf, at-buf) <= 0) { + /* write failure */ + } + free(buf); +} + +/** provide file over SSL, chunked encoding */ +static void +provide_file_chunked(SSL* ssl, char* fname) +{ + char buf[16384]; + char* at = buf; + size_t avail = sizeof(buf); + int r; + FILE* in = fopen(fname, +#ifndef USE_WINSOCK + "r" +#else + "rb" +#endif + ); + const char* rcode = "200 OK"; + if(!in) { + rcode = "404 File not found"; + } + + /* print headers */ + r = snprintf(at, avail, "HTTP/1.1 %s\r\n", rcode); + at += r; + avail -= r; + r = snprintf(at, avail, "Server: petal/%s\r\n", PACKAGE_VERSION); + at += r; + avail -= r; + r = snprintf(at, avail, "Transfer-Encoding: chunked\r\n"); + at += r; + avail -= r; + r = snprintf(at, avail, "Connection: close\r\n"); + at += r; + avail -= r; + r = snprintf(at, avail, "\r\n"); + at += r; + avail -= r; + if(avail < 16) { /* robust */ + if(in) fclose(in); + return; + } + + do { + char tmpbuf[sizeof(buf)]; + /* read chunk; space-16 for xxxxCRLF..CRLF0CRLFCRLF (3 spare)*/ + size_t red = in?fread(tmpbuf, 1, avail-16, in):0; + /* prepare chunk */ + r = snprintf(at, avail, "%x\r\n", (unsigned)red); + if(verb >= 3) + {printf("chunk len %x\n", (unsigned)red); fflush(stdout);} + at += r; + avail -= r; + if(red != 0) { + if(red > avail) break; /* robust */ + memmove(at, tmpbuf, red); + at += red; + avail -= red; + r = snprintf(at, avail, "\r\n"); + at += r; + avail -= r; + } + if(in && feof(in) && red != 0) { + r = snprintf(at, avail, "0\r\n"); + at += r; + avail -= r; + } + if(!in || feof(in)) { + r = snprintf(at, avail, "\r\n"); + at += r; + avail -= r; + } + /* send chunk */ + if(SSL_write(ssl, buf, at-buf) <= 0) { + /* SSL error */ + break; + } + + /* setup for next chunk */ + at = buf; + avail = sizeof(buf); + } while(in && !feof(in) && !ferror(in)); + + if(in) fclose(in); +} + +/** provide service to the ssl descriptor */ +static void +service_ssl(SSL* ssl, struct sockaddr_storage* from, socklen_t falen) +{ + char file[1024]; + char host[1024]; + char combined[2048]; + int vs = 11; + if(!read_http_headers(ssl, file, sizeof(file), host, sizeof(host), + &vs)) + return; + adjust_host_file(host, file); + if(host[0] == 0 || !host_name_is_safe(host)) + (void)strlcpy(host, "default", sizeof(host)); + if(!file_name_is_safe(file)) { + return; + } + snprintf(combined, sizeof(combined), "%s%s", host, file); + if(verb) { + char out[100]; + void* a = &((struct sockaddr_in*)from)->sin_addr; + if(falen != (socklen_t)sizeof(struct sockaddr_in)) + a = &((struct sockaddr_in6*)from)->sin6_addr; + out[0]=0; + (void)inet_ntop((int)((struct sockaddr_in*)from)->sin_family, + a, out, (socklen_t)sizeof(out)); + printf("%s requests %s\n", out, combined); + fflush(stdout); + } + if(vs == 10) + provide_file_10(ssl, combined); + else provide_file_chunked(ssl, combined); +} + +/** provide ssl service */ +static void +do_service(char* addr, int port, char* key, char* cert) +{ + SSL_CTX* sslctx = setup_ctx(key, cert); + int fd = setup_fd(addr, port); + int go = 1; + if(fd == -1) print_exit("could not setup sockets"); + if(verb) {printf("petal start\n"); fflush(stdout);} + while(go) { + struct sockaddr_storage from; + socklen_t flen = (socklen_t)sizeof(from); + int s = accept(fd, (struct sockaddr*)&from, &flen); + if(verb) fflush(stdout); + if(s != -1) { + SSL* ssl = setup_ssl(s, sslctx); + if(verb) fflush(stdout); + if(ssl) { + service_ssl(ssl, &from, flen); + if(verb) fflush(stdout); + SSL_shutdown(ssl); + SSL_free(ssl); + } + fd_close(s); + } else if (verb >=2) log_errno("accept"); + if(verb) fflush(stdout); + } + /* if we get a kill signal, the process dies and the OS reaps us */ + if(verb) printf("petal end\n"); + fd_close(fd); + SSL_CTX_free(sslctx); +} + +/** getopt global, in case header files fail to declare it. */ +extern int optind; +/** getopt global, in case header files fail to declare it. */ +extern char* optarg; + +/** Main routine for petal */ +int main(int argc, char* argv[]) +{ + int c; + int port = 443; + char* addr = "127.0.0.1", *key = "petal.key", *cert = "petal.pem"; +#ifdef USE_WINSOCK + WSADATA wsa_data; + if((c=WSAStartup(MAKEWORD(2,2), &wsa_data)) != 0) + { printf("WSAStartup failed\n"); exit(1); } + atexit((void (*)(void))WSACleanup); +#endif + + /* parse the options */ + while( (c=getopt(argc, argv, "a:c:k:hp:v")) != -1) { + switch(c) { + case 'a': + addr = optarg; + break; + case 'c': + cert = optarg; + break; + case 'k': + key = optarg; + break; + case 'p': + port = atoi(optarg); + break; + case 'v': + verb++; + break; + case '?': + case 'h': + default: + usage(); + } + } + argc -= optind; + argv += optind; + if(argc != 0) + usage(); + +#ifdef SIGPIPE + (void)signal(SIGPIPE, SIG_IGN); +#endif + ERR_load_crypto_strings(); + ERR_load_SSL_strings(); + OpenSSL_add_all_algorithms(); + (void)SSL_library_init(); + + do_service(addr, port, key, cert); + + CRYPTO_cleanup_all_ex_data(); + ERR_remove_state(0); + ERR_free_strings(); + RAND_cleanup(); + return 0; +} |