summaryrefslogtreecommitdiff
path: root/testcode/petal.c
diff options
context:
space:
mode:
Diffstat (limited to 'testcode/petal.c')
-rw-r--r--testcode/petal.c618
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;
+}