From 4d589ee8bf6746da0e706999de93fed4e4f18e46 Mon Sep 17 00:00:00 2001 From: Godmar Back Date: Fri, 13 Apr 2018 00:41:36 -0400 Subject: [PATCH] initial set of files --- README.md | 10 ++ install-dependencies.sh | 20 +++ src/Makefile | 22 +++ src/buffer.h | 106 ++++++++++++ src/bufio.c | 209 +++++++++++++++++++++++ src/bufio.h | 18 ++ src/hexdump.c | 71 ++++++++ src/hexdump.h | 2 + src/http.c | 369 ++++++++++++++++++++++++++++++++++++++++ src/http.h | 61 +++++++ src/main.c | 69 ++++++++ src/socket.c | 138 +++++++++++++++ src/socket.h | 7 + 13 files changed, 1102 insertions(+) create mode 100644 README.md create mode 100644 install-dependencies.sh create mode 100644 src/Makefile create mode 100644 src/buffer.h create mode 100644 src/bufio.c create mode 100644 src/bufio.h create mode 100644 src/hexdump.c create mode 100644 src/hexdump.h create mode 100644 src/http.c create mode 100644 src/http.h create mode 100644 src/main.c create mode 100644 src/socket.c create mode 100644 src/socket.h diff --git a/README.md b/README.md new file mode 100644 index 0000000..538e7c5 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ + +This repository contains the base files for the CS 3214 +"Personal Secure Server" project. + +To get started, run the script: + +. install-dependencies.sh + +Then cd into src and type make. + diff --git a/install-dependencies.sh b/install-dependencies.sh new file mode 100644 index 0000000..fc428d5 --- /dev/null +++ b/install-dependencies.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# +# Run this script to install the required Jansson and JWT packages. +# +BASE=`pwd` +test -d ${BASE}/deps || mkdir ${BASE}/deps +git clone git@github.com:akheron/jansson.git +(cd jansson; + autoreconf -fi; + ./configure --prefix=${BASE}/deps; + make install +) + +git clone git@github.com:benmcollins/libjwt.git +(cd libjwt; + autoreconf -fi; + env PKG_CONFIG_PATH=../deps/lib/pkgconfig:${PKG_CONFIG_PATH} ./configure --prefix=${BASE}/deps; + make install +) diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..62c46b1 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,22 @@ +DEP_BASE_DIR=../deps +DEP_INCLUDE_DIR=$(DEP_BASE_DIR)/include +DEP_LIB_DIR=$(abspath $(DEP_BASE_DIR)/lib) + +CFLAGS=-g -pthread -std=gnu11 -Wall -Werror -Wmissing-prototypes -I$(DEP_INCLUDE_DIR) + +# include lib directory into runtime path to facilitate dynamic linking +LDFLAGS=-Wl,-rpath -Wl,$(DEP_LIB_DIR) +LDLIBS=-L$(DEP_LIB_DIR) -ljwt -ljansson -lcrypto -ldl + +HEADERS=socket.h http.h hexdump.h buffer.h bufio.h +OBJ=main.o socket.o hexdump.o http.o bufio.o + +all: server + +$(OBJ) : $(HEADERS) + +server: $(OBJ) + $(CC) $(LDFLAGS) -o $@ $(OBJ) $(LDLIBS) + +clean: + /bin/rm $(OBJ) diff --git a/src/buffer.h b/src/buffer.h new file mode 100644 index 0000000..b07ff8f --- /dev/null +++ b/src/buffer.h @@ -0,0 +1,106 @@ +#ifndef __BUFFER_H_ +#define __BUFFER_H_ +/* + * A realloc-based dynamic buffer implementation for C. + * + * Written by Scott Pruett. + * + * Note: the buffer implementation is similar to Java's StringBuffer + * in that it supports a dynamically growing array of bytes. + * It supports append* operations that copy data to the buffer, + * extending it as necessary. + * + * Aiming at efficiency, this buffer is not written as a fully encapsulated + * data type. Rather, some implementation details are exposed. + * + * Because the buffer uses realloc, you cannot keep pointers into the + * buffer around across calls that may reallocate the buffer. + * + * The buffer is not thread-safe. + * This buffer handles out-of-memory situations by exiting the process. + */ +#include +#include +#include + +typedef struct { + char* buf; // underlying storage + int len; // current end; last valid byte is buf[len-1] + int cap; // allocated amount of storage +} buffer_t; + +/* + * Initialize a buffer and allocate initialsize storage. + */ +static inline void buffer_init(buffer_t *buf, int initialsize) +{ + buf->len = 0; + buf->cap = initialsize; + buf->buf = malloc(buf->cap); + if (buf->buf == NULL) { + perror("can't alloc memory: "); + exit(EXIT_FAILURE); + } +} + +/* Reset this buffer, truncating storage to size. */ +static inline void buffer_reset(buffer_t *buf, int size) +{ + buf->len = 0; + if (buf->cap > size) { + buf->buf = realloc(buf->buf, size); + buf->cap = size; + } +} + +/* Delete this buffer, freeing any storage. */ +static inline void buffer_delete(buffer_t *buf) +{ + free(buf->buf); +} + +/* + * To minimize copying, the buffer also has the ability to reserve + * space for I/O operations, see buffer_ensure_capacity below. + * In this case, the caller must adjust the buffer's len following + * the operation. + * + * Returns a pointer p to the first usable byte; it is guaranteed that + * bytes buf[p:p+len] point to usable memory. Once written, the + * caller should increment buf.len by the actual number of bytes + * written into the buffer. + */ +static inline char *buffer_ensure_capacity(buffer_t *buf, int len) { + if (buf->len + len >= buf->cap) { + int cap = buf->cap * 2 + len; + buf->buf = realloc(buf->buf, cap); + if (buf->buf == NULL) { + perror("can't alloc memory: "); + exit(1); + } + buf->cap = cap; + } + return &buf->buf[buf->len]; +} + +/* Append mem[0:len] to the buffer, expanding it as necessary. */ +static inline void buffer_append(buffer_t *buf, void *mem, int len) { + buffer_ensure_capacity(buf, len); + memcpy(&buf->buf[buf->len], mem, len); + buf->len += len; +} + +/* Append c to the buffer, expanding it as necessary. */ +static inline void buffer_appendc(buffer_t *buf, char c) { + buffer_ensure_capacity(buf, 1); + buf->buf[buf->len] = c; + buf->len++; +} + +/* Append zero-terminated string str to the buffer, expanding it as necessary. */ +static inline void buffer_appends(buffer_t *buf, char *str) { + int len = strlen(str); + buffer_append(buf, str, len); +} + +#endif diff --git a/src/bufio.c b/src/bufio.c new file mode 100644 index 0000000..4e6fcee --- /dev/null +++ b/src/bufio.c @@ -0,0 +1,209 @@ +/* + * Support for buffered I/O + * + * The 'bufio' struct implements buffered I/O for a socket, similar + * to Java's BufferedReader. + * + * Since it encapsulates a connection's socket, it also provides + * methods for sending data. + * + * Written by G. Back for CS 3214 Spring 2018 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bufio.h" + +/*****************************************************************/ +struct bufio { + int socket; // underlying socket file descriptor + size_t bufpos; // offset of next byte to be read + buffer_t buf; // holds data that was received +}; + +static const int BUFSIZE = 1536; +static int min(int a, int b) { return a < b ? a : b; } + +/* Create a new bufio object from a socket. */ +struct bufio * +bufio_create(int socket) +{ + struct bufio * rc = malloc(sizeof(*rc)); + if (rc == NULL) { + perror("malloc"); + exit(EXIT_FAILURE); + } + + rc->bufpos = 0; + rc->socket = socket; + buffer_init(&rc->buf, BUFSIZE); + return rc; +} + +/* Close a bufio object, freeing its storage and closing its socket. */ +void +bufio_close(struct bufio * self) +{ + if (close(self->socket)) + perror("close"); + + buffer_delete(&self->buf); + free(self); +} + +static ssize_t +bytes_buffered(struct bufio *self) +{ + return self->buf.len - self->bufpos; +} + +const int TRUNCATE_THRESHOLD = 10000; // tune me + +/* + * Discard already read data, shifting any buffered data into a new buffer. + * This will invalidate all offsets. + * + * This method is provided to avoid accumulating all data received + * on a long-running HTTP/1.1 connection into a single buffer. + */ +void bufio_truncate(struct bufio * self) +{ + if (self->buf.len > TRUNCATE_THRESHOLD) { + int unread = bytes_buffered(self); + assert(unread >= 0); + if (unread == 0) { + buffer_reset(&self->buf, BUFSIZE); + } else { + buffer_t oldbuf = self->buf; + buffer_init(&self->buf, BUFSIZE); + buffer_append(&self->buf, oldbuf.buf + self->bufpos, unread); + buffer_delete(&oldbuf); + } + self->bufpos = 0; + } +} + +static ssize_t +read_more(struct bufio *self) +{ + char * buf = buffer_ensure_capacity(&self->buf, BUFSIZE); + int bread = recv(self->socket, buf, BUFSIZE, MSG_NOSIGNAL); + if (bread < 1) + return bread; + + self->buf.len += bread; + return bread; +} + +/* Given an offset into the buffer, return a char *. + * This pointer will be valid only until the next call + * to any of the bufio_read* function. + */ +char * +bufio_offset2ptr(struct bufio *self, size_t offset) +{ + assert (offset < self->buf.len); + return self->buf.buf + offset; +} + +/* Given an pointer into the buffer, return an offset + * that can be used to mark the position of something + * in the buffer (e.g., a header) + */ +size_t +bufio_ptr2offset(struct bufio *self, char *ptr) +{ + size_t offset = ptr - self->buf.buf; + assert (0 <= offset && offset < self->buf.len); + return offset; +} + +/* Read one byte from the socket into the buffer. + * Returns 0 on EOF, -1 on error, else returns 1. + */ +ssize_t +bufio_readbyte(struct bufio *self, char *out) +{ + if (bytes_buffered(self) == 0) { + int rc = read_more(self); + if (rc <= 0) + return rc; + } + + *out = self->buf.buf[self->bufpos++]; + return 1; +} + +/* Read until newline (\n) is encountered. + * Sets *line_offset to the buffer offset where the next line + * starts. + * + * Returns number of bytes read, and -1 on error. + * + * If EOF is encountered, the line may not be terminated + * with a \n. + */ +ssize_t +bufio_readline(struct bufio *self, size_t *line_offset) +{ + *line_offset = self->bufpos; + char p = 0; + while (p != '\n') { + int rc = bufio_readbyte(self, &p); + if (rc < 0) + return rc; + if (rc == 0) + break; + } + return self->bufpos - *line_offset; +} + +/* Attempt to read a fixed number of bytes into the buffer. + * Sets *buf_offset to the offset in the buffer pointing to + * the first byte read. + * + * Returns the actual number of bytes read, or -1 on error. + */ +ssize_t +bufio_read(struct bufio *self, size_t count, size_t *buf_offset) +{ + *buf_offset = self->bufpos; + while (bytes_buffered(self) < count) { + int rc = read_more(self); + if (rc < 0) + return rc; + if (rc == 0) + break; + } + ssize_t bytes_read = min(bytes_buffered(self), count); + self->bufpos += bytes_read; + return bytes_read; +} + +/* Send a file out to the socket. + * See sendfile(2) for return value. + */ +ssize_t +bufio_sendfile(struct bufio *self, int fd, off_t *off, int filesize) +{ + return sendfile(self->socket, fd, off, filesize); +} + +/* + * Send data contained in 'resp' to the socket. + * See send(2) for return value. + */ +ssize_t +bufio_sendbuffer(struct bufio *self, buffer_t * resp) +{ + return send(self->socket, resp->buf, resp->len, MSG_NOSIGNAL); +} diff --git a/src/bufio.h b/src/bufio.h new file mode 100644 index 0000000..3e3c67c --- /dev/null +++ b/src/bufio.h @@ -0,0 +1,18 @@ +#ifndef _BUFIO_H +#define _BUFIO_H + +#include "buffer.h" + +struct bufio; +struct bufio * bufio_create(int socket); +void bufio_close(struct bufio * self); +void bufio_truncate(struct bufio * self); +ssize_t bufio_readbyte(struct bufio *self, char *out); +ssize_t bufio_readline(struct bufio *self, size_t *line_offset); +ssize_t bufio_read(struct bufio *self, size_t count, size_t *buf_offset); +char * bufio_offset2ptr(struct bufio *self, size_t offset); +size_t bufio_ptr2offset(struct bufio *self, char *ptr); +ssize_t bufio_sendfile(struct bufio *self, int fd, off_t *off, int filesize); +ssize_t bufio_sendbuffer(struct bufio *self, buffer_t *response); + +#endif /* _BUFIO_H */ diff --git a/src/hexdump.c b/src/hexdump.c new file mode 100644 index 0000000..d617067 --- /dev/null +++ b/src/hexdump.c @@ -0,0 +1,71 @@ +/* + * Copyright (c) 1996 The University of Utah and + * the Computer Systems Laboratory at the University of Utah (CSL). + * All rights reserved. + * + * Permission to use, copy, modify and distribute this software is hereby + * granted provided that (1) source code retains these copyright, permission, + * and disclaimer notices, and (2) redistributions including binaries + * reproduce the notices in supporting documentation, and (3) all advertising + * materials mentioning features or use of this software display the following + * acknowledgement: ``This product includes software developed by the + * Computer Systems Laboratory at the University of Utah.'' + * + * THE UNIVERSITY OF UTAH AND CSL ALLOW FREE USE OF THIS SOFTWARE IN ITS "AS + * IS" CONDITION. THE UNIVERSITY OF UTAH AND CSL DISCLAIM ANY LIABILITY OF + * ANY KIND FOR ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * CSL requests users of this software to return to csl-dist@cs.utah.edu any + * improvements that they make and grant CSL redistribution rights. + */ + +#include +#include +#include "hexdump.h" + +/* + * Print a buffer hexdump style. Example: + * + * .---------------------------------------------------------------------------. + * | 00000000 2e2f612e 6f757400 5445524d 3d787465 ./a.out.TERM=xte | + * | 00000010 726d0048 4f4d453d 2f616673 2f63732e rm.HOME=/afs/cs. | + * | 00000020 75746168 2e656475 2f686f6d 652f6c6f utah.edu/home/lo | + * | 00000030 6d657700 5348454c 4c3d2f62 696e2f74 mew.SHELL=/bin/t | + * | 00000040 63736800 4c4f474e 414d453d 6c6f6d65 csh.LOGNAME=lome | + * | 00000050 77005553 45523d6c 6f6d6577 00504154 w.USER=lomew.PAT | + * | 00000060 483d2f61 66732f63 732e7574 61682e65 H=/afs/cs.utah.e | + * | 00000070 64752f68 6f6d652f 6c6f6d65 772f6269 du/home/lomew/bi | + * | 00000080 6e2f4073 79733a2f 6166732f 63732e75 n/@sys:/afs/cs.u | + * | 00000090 7461682e 6564 tah.ed | + * `---------------------------------------------------------------------------' + * + * It might be useful to have an option for printing out little-endianly. + * Adapted from Godmar's hook.c. + */ +void hexdump(void *buf, size_t len) +{ + size_t i, j; + char *b = (char *)buf; + + printf(".---------------------------------------------------------------------------.\n"); + for (i = 0; i < len; i += 16) { + printf("| %08lx ", i); + for (j = i; j < i+16; j++) { + if (j % 4 == 0) + printf(" "); + if (j >= len) + printf(" "); + else + printf("%02x", (unsigned char)b[j]); + } + + printf(" "); + for (j = i; j < i+16; j++) + if (j >= len) + printf(" "); + else + printf("%c", isgraph(b[j]) ? b[j] : '.'); + printf(" |\n"); + } + printf("`---------------------------------------------------------------------------'\n"); +} diff --git a/src/hexdump.h b/src/hexdump.h new file mode 100644 index 0000000..8817a64 --- /dev/null +++ b/src/hexdump.h @@ -0,0 +1,2 @@ + +void hexdump(void *buf, size_t len); diff --git a/src/http.c b/src/http.c new file mode 100644 index 0000000..a7c42e4 --- /dev/null +++ b/src/http.c @@ -0,0 +1,369 @@ +/* + * A partial implementation of HTTP/1.0 + * + * This code is mainly intended as a replacement for the book's 'tiny.c' server + * It provides a *partial* implementation of HTTP/1.0 which can form a basis for + * the assignment. + * + * @author G. Back for CS 3214 Spring 2018 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "http.h" +#include "hexdump.h" +#include "socket.h" +#include "bufio.h" + +// Need macros here because of the sizeof +#define CRLF "\r\n" +#define STARTS_WITH(field_name, header) \ + (!strncasecmp(field_name, header, sizeof(header) - 1)) + +char * server_root; // root from which static files are served + + +/* Parse HTTP request line, setting req_method, req_path, and req_version. */ +static int +http_parse_request(struct http_transaction *ta) +{ + size_t req_offset; + ssize_t len = bufio_readline(ta->client->bufio, &req_offset); + if (len <= 0) + return len; + + char *request = bufio_offset2ptr(ta->client->bufio, req_offset); + char *endptr; + char *method = strtok_r(request, " ", &endptr); + if (method == NULL) + return -1; + + if (!strcmp(method, "GET")) + ta->req_method = HTTP_GET; + else if (!strcmp(method, "POST")) + ta->req_method = HTTP_POST; + else + ta->req_method = HTTP_UNKNOWN; + + char *req_path = strtok_r(NULL, " ", &endptr); + if (req_path == NULL) + return -1; + + ta->req_path = bufio_ptr2offset(ta->client->bufio, req_path); + + char *http_version = strtok_r(NULL, CRLF, &endptr); + if (http_version == NULL) // would be HTTP 0.9 + return -1; + + if (!strcmp(http_version, "HTTP/1.1")) + ta->req_version = HTTP_1_1; + else if (!strcmp(http_version, "HTTP/1.0")) + ta->req_version = HTTP_1_0; + else + return -1; + + return 0; +} + +/* Process HTTP headers. */ +static int +http_process_headers(struct http_transaction *ta) +{ + for (;;) { + size_t header_offset; + ssize_t len = bufio_readline(ta->client->bufio, &header_offset); + char *header = bufio_offset2ptr(ta->client->bufio, header_offset); + if (len <= 0) + return len; + + if (len == 2 && STARTS_WITH(header, CRLF)) // empty CRLF + return 0; + + header[len-2] = '\0'; + /* Each header field consists of a name followed by a + * colon (":") and the field value. Field names are + * case-insensitive. The field value MAY be preceded by + * any amount of LWS, though a single SP is preferred. + */ + char *endptr; + char *field_name = strtok_r(header, ":", &endptr); + char *field_value = strtok_r(NULL, " \t", &endptr); // skip leading & trailing OWS + + if (field_name == NULL) + return -1; + + // printf("Header: %s: %s\n", field_name, field_value); + if (!strcasecmp(field_name, "Content-Length")) { + ta->req_content_len = atoi(field_value); + } + + /* Handle other headers here. */ + } + + return 0; +} + +const int MAX_HEADER_LEN = 2048; + +/* add a formatted header to the response buffer. */ +void +http_add_header(buffer_t * resp, char* key, char* fmt, ...) +{ + va_list ap; + + buffer_appends(resp, key); + buffer_appends(resp, ": "); + + va_start(ap, fmt); + char *error = buffer_ensure_capacity(resp, MAX_HEADER_LEN); + resp->len += vsnprintf(error, MAX_HEADER_LEN, fmt, ap); + va_end(ap); + + buffer_appends(resp, "\r\n"); +} + +/* add a content-length header. */ +static void +add_content_length(buffer_t *res, size_t len) +{ + http_add_header(res, "Content-Length", "%ld", len); +} + +/* start the response by writing the first line of the response + * to the response buffer. Used in send_response_header */ +static void +start_response(struct http_transaction * ta, buffer_t *res) +{ + buffer_appends(res, "HTTP/1.0 "); + + switch (ta->resp_status) { + case HTTP_OK: + buffer_appends(res, "200 OK"); + break; + case HTTP_BAD_REQUEST: + buffer_appends(res, "400 Bad Request"); + break; + case HTTP_PERMISSION_DENIED: + buffer_appends(res, "403 Permission Denied"); + break; + case HTTP_NOT_FOUND: + buffer_appends(res, "404 Not Found"); + break; + case HTTP_METHOD_NOT_ALLOWED: + buffer_appends(res, "405 Method Not Allowed"); + break; + case HTTP_REQUEST_TIMEOUT: + buffer_appends(res, "408 Request Timeout"); + break; + case HTTP_REQUEST_TOO_LONG: + buffer_appends(res, "414 Request Too Long"); + break; + case HTTP_NOT_IMPLEMENTED: + buffer_appends(res, "501 Not Implemented"); + break; + case HTTP_SERVICE_UNAVAILABLE: + buffer_appends(res, "503 Service Unavailable"); + break; + case HTTP_INTERNAL_ERROR: + default: + buffer_appends(res, "500 Internal Server Error"); + break; + } + buffer_appends(res, CRLF); +} + +/* Send response headers to client */ +static int +send_response_header(struct http_transaction *ta) +{ + buffer_t response; + buffer_init(&response, 80); + + start_response(ta, &response); + if (bufio_sendbuffer(ta->client->bufio, &response) == -1) + return -1; + + buffer_appends(&ta->resp_headers, CRLF); + if (bufio_sendbuffer(ta->client->bufio, &ta->resp_headers) == -1) + return -1; + + buffer_delete(&response); + return 0; +} + +/* Send a full response to client with the content in resp_body. */ +static bool +send_response(struct http_transaction *ta) +{ + // add content-length. All other headers must have already been set. + add_content_length(&ta->resp_headers, ta->resp_body.len); + + if (send_response_header(ta) == -1) + return false; + + return bufio_sendbuffer(ta->client->bufio, &ta->resp_body) != -1; +} + +const int MAX_ERROR_LEN = 2048; + +/* Send an error response. */ +static bool +send_error(struct http_transaction * ta, enum http_response_status status, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + char *error = buffer_ensure_capacity(&ta->resp_body, MAX_ERROR_LEN); + ta->resp_body.len += vsnprintf(error, MAX_ERROR_LEN, fmt, ap); + va_end(ap); + ta->resp_status = status; + return send_response(ta); +} + +/* Send Not Found response. */ +static bool +send_not_found(struct http_transaction *ta) +{ + return send_error(ta, HTTP_NOT_FOUND, "File %s not found", + bufio_offset2ptr(ta->client->bufio, ta->req_path)); +} + +/* A start at assigning an appropriate mime type. Real-world + * servers use more extensive lists such as /etc/mime.types + */ +static const char * +guess_mime_type(char *filename) +{ + char *suffix = strrchr(filename, '.'); + if (suffix == NULL) + return "text/plain"; + + if (!strcasecmp(suffix, ".html")) + return "text/html"; + + if (!strcasecmp(suffix, ".gif")) + return "image/gif"; + + if (!strcasecmp(suffix, ".png")) + return "image/png"; + + if (!strcasecmp(suffix, ".jpg")) + return "image/jpeg"; + + if (!strcasecmp(suffix, ".js")) + return "text/javascript"; + + return "text/plain"; +} + +/* Handle HTTP transaction for static files. */ +static bool +handle_static_asset(struct http_transaction *ta, char *basedir) +{ + char fname[PATH_MAX]; + + char *req_path = bufio_offset2ptr(ta->client->bufio, ta->req_path); + // The code below is vulnerable to an attack. Can you see + // which? Fix it to avoid indirect object reference (IDOR) attacks. + snprintf(fname, sizeof fname, "%s%s", basedir, req_path); + + if (access(fname, R_OK)) { + if (errno == EACCES) + return send_error(ta, HTTP_PERMISSION_DENIED, "Permission denied."); + else + return send_not_found(ta); + } + + // Determine file size + struct stat st; + int rc = stat(fname, &st); + if (rc == -1) + return send_error(ta, HTTP_INTERNAL_ERROR, "Could not stat file."); + + int filefd = open(fname, O_RDONLY); + if (filefd == -1) { + return send_not_found(ta); + } + + ta->resp_status = HTTP_OK; + add_content_length(&ta->resp_headers, st.st_size); + http_add_header(&ta->resp_headers, "Content-Type", "%s", guess_mime_type(fname)); + + rc = send_response_header(ta); + if (rc == -1) + goto out; + + rc = bufio_sendfile(ta->client->bufio, filefd, NULL, st.st_size); +out: + close(filefd); + return rc; +} + + +static int +handle_api(struct http_transaction *ta) +{ + return send_error(ta, HTTP_NOT_IMPLEMENTED, "API not implemented"); +} + +/* Set up an http client, associating it with a bufio buffer. */ +void +http_setup_client(struct http_client *self, struct bufio *bufio) +{ + self->bufio = bufio; +} + +/* Handle a single HTTP transaction. Returns true on success. */ +bool +http_handle_transaction(struct http_client *self) +{ + struct http_transaction ta; + memset(&ta, 0, sizeof ta); + ta.client = self; + + if (http_parse_request(&ta) == -1) + return -1; + + if (http_process_headers(&ta) == -1) + return -1; + + if (ta.req_content_len > 0) { + int rc = bufio_read(self->bufio, ta.req_content_len, &ta.req_body); + if (rc != ta.req_content_len) + return -1; + + // To see the body, use this: + // char *body = bufio_offset2ptr(ta.client->bufio, ta.req_body); + // hexdump(body, ta.req_content_len); + } + + buffer_init(&ta.resp_headers, 1024); + http_add_header(&ta.resp_headers, "Server", "CS3214-Personal-Server"); + buffer_init(&ta.resp_body, 0); + + bool rc; + char *req_path = bufio_offset2ptr(ta.client->bufio, ta.req_path); + if (STARTS_WITH(req_path, "/api")) { + rc = handle_api(&ta); + } else + if (STARTS_WITH(req_path, "/private")) { + /* not implemented */ + } else { + rc = handle_static_asset(&ta, server_root); + } + + buffer_delete(&ta.resp_headers); + buffer_delete(&ta.resp_body); + + return rc; +} diff --git a/src/http.h b/src/http.h new file mode 100644 index 0000000..94a43ea --- /dev/null +++ b/src/http.h @@ -0,0 +1,61 @@ +#ifndef _HTTP_H +#define _HTTP_H + +#include +#include + +#include "buffer.h" +struct bufio; + +enum http_method { + HTTP_GET, + HTTP_POST, + HTTP_UNKNOWN +}; + +enum http_version { + HTTP_1_0, + HTTP_1_1 +}; + +enum http_response_status { + HTTP_OK = 200, + HTTP_BAD_REQUEST = 400, + HTTP_PERMISSION_DENIED = 403, + HTTP_NOT_FOUND = 404, + HTTP_METHOD_NOT_ALLOWED = 405, + HTTP_REQUEST_TIMEOUT = 408, + HTTP_REQUEST_TOO_LONG = 414, + HTTP_INTERNAL_ERROR = 500, + HTTP_NOT_IMPLEMENTED = 501, + HTTP_SERVICE_UNAVAILABLE = 503 +}; + +struct http_transaction { + /* request related fields */ + enum http_method req_method; + enum http_version req_version; + size_t req_path; // expressed as offset into the client's bufio. + size_t req_body; // ditto + int req_content_len; // content length of request body + + + /* response related fields */ + enum http_response_status resp_status; + buffer_t resp_headers; + buffer_t resp_body; + + struct http_client *client; +}; + +struct http_client { + struct bufio *bufio; +}; + +void http_setup_client(struct http_client *, struct bufio *bufio); +bool http_handle_transaction(struct http_client *); +void http_add_header(buffer_t * resp, char* key, char* fmt, ...); + +extern char *server_root; + +#endif /* _HTTP_H */ diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..44c4cc2 --- /dev/null +++ b/src/main.c @@ -0,0 +1,69 @@ +/* + * Skeleton files for personal server assignment. + * + * @author Godmar Back + * written for CS3214, Spring 2018. + */ + +#include +#include +#include +#include "buffer.h" +#include "hexdump.h" +#include "http.h" +#include "socket.h" +#include "bufio.h" + +/* + * A non-concurrent, iterative server that serves one client at a time. + * For each client, it handles exactly 1 HTTP transaction. + */ +static void +server_loop(char *port_string) +{ + int accepting_socket = socket_open_bind_listen(port_string, 1024); + for (;;) { + int client_socket = socket_accept_client(accepting_socket); + if (client_socket == -1) + return; + + struct http_client client; + http_setup_client(&client, bufio_create(client_socket)); + http_handle_transaction(&client); + bufio_close(client.bufio); + } +} + +static void +usage(char * av0) +{ + fprintf(stderr, "Usage: %s [-p port] [-R rootdir] [-h]\n", av0); + exit(EXIT_FAILURE); +} + +int +main(int ac, char *av[]) +{ + int opt; + char *port_string = NULL; + while ((opt = getopt(ac, av, "hp:R:")) != -1) { + switch (opt) { + case 'p': + port_string = optarg; + break; + + case 'R': + server_root = optarg; + break; + + case 'h': + default: /* '?' */ + usage(av[0]); + } + } + + fprintf(stderr, "Using port %s\n", port_string); + server_loop(port_string); + exit(EXIT_SUCCESS); +} + diff --git a/src/socket.c b/src/socket.c new file mode 100644 index 0000000..9c80fc4 --- /dev/null +++ b/src/socket.c @@ -0,0 +1,138 @@ +/* + * Support functions for dealing with sockets. + * + * Note: these functions cannot be used out of the box. + * In particular, support for protocol independent programming + * is not fully implemented. See below. + * + * Written by G. Back for CS 3214 Spring 2018. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "socket.h" + +/* + * Find a suitable IPv4 address to bind to, create a socket, bind it, + * invoke listen to get the socket ready for accepting clients. + * + * This function does not implement proper support for protocol-independent/ + * dual-stack binding. Adding this is part of the assignment. + * + * Returns -1 on error, setting errno. + * Returns socket file descriptor otherwise. + */ +int +socket_open_bind_listen(char * port_number_string, int backlog) +{ + struct addrinfo *info, *pinfo; + struct addrinfo hint; + + memset(&hint, 0, sizeof hint); + + hint.ai_flags = AI_PASSIVE | // we're looking for an address to bind to + AI_NUMERICSERV; // service port is numeric, don't look in /etc/services + + hint.ai_protocol = IPPROTO_TCP; // only interested in TCP + int rc = getaddrinfo(NULL, port_number_string, &hint, &info); + if (rc != 0) { + fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(rc)); + return -1; + } + + char printed_addr[1024]; + for (pinfo = info; pinfo; pinfo = pinfo->ai_next) { + assert (pinfo->ai_protocol == IPPROTO_TCP); + int rc = getnameinfo(pinfo->ai_addr, pinfo->ai_addrlen, + printed_addr, sizeof printed_addr, NULL, 0, + NI_NUMERICHOST); + if (rc != 0) { + fprintf(stderr, "getnameinfo error: %s\n", gai_strerror(rc)); + return -1; + } + + /* Uncomment this to see the address returned + printf("%s: %s\n", pinfo->ai_family == AF_INET ? "AF_INET" : + pinfo->ai_family == AF_INET6 ? "AF_INET6" : "?", + printed_addr); + */ + + /* Skip any non-IPv4 addresses. + * Adding support for protocol independence/IPv6 is part of the project. + */ + if (pinfo->ai_family != AF_INET) + continue; + + int s = socket(pinfo->ai_family, pinfo->ai_socktype, pinfo->ai_protocol); + if (s == -1) { + perror("socket"); + return -1; + } + + // See https://stackoverflow.com/a/3233022 for a good explanation of what this does + int opt = 1; + setsockopt (s, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof (opt)); + + rc = bind(s, pinfo->ai_addr, pinfo->ai_addrlen); + if (rc == -1) { + perror("bind"); + close(s); + return -1; + } + + rc = listen(s, backlog); + if (rc == -1) { + perror("listen"); + close(s); + return -1; + } + + freeaddrinfo(info); + return s; + } + fprintf(stderr, "No suitable address to bind port %s found\n", port_number_string); + return -1; +} + +/** + * Accept a client, blocking if necessary. + * + * Returns file descriptor of client accepted on success, returns + * -1 on error. + */ +int +socket_accept_client(int accepting_socket) +{ + struct sockaddr peer; + socklen_t peersize = sizeof(peer); + int client = accept(accepting_socket, &peer, &peersize); + if (client == -1) { + perror("accept"); + return -1; + } + + /* The following will debug with debugging your server. + * Adjust as necessary. + */ + char peer_addr[1024], peer_port[10]; + int rc = getnameinfo(&peer, sizeof peer, + peer_addr, sizeof peer_addr, peer_port, sizeof peer_port, + NI_NUMERICHOST | NI_NUMERICSERV); + if (rc != 0) { + fprintf(stderr, "getnameinfo error: %s\n", gai_strerror(rc)); + return -1; + } + fprintf(stderr, "Accepted connection from %s:%s\n", peer_addr, peer_port); + return client; +} + diff --git a/src/socket.h b/src/socket.h new file mode 100644 index 0000000..c15697f --- /dev/null +++ b/src/socket.h @@ -0,0 +1,7 @@ +#ifndef _SOCKET_H +#define _SOCKET_H + +int socket_open_bind_listen(char * port_number_string, int backlog); +int socket_accept_client(int socket); + +#endif /* _SOCKET_H */