initial set of files

This commit is contained in:
Godmar Back 2018-04-13 00:41:36 -04:00
commit 4d589ee8bf
13 changed files with 1102 additions and 0 deletions

10
README.md Normal file
View File

@ -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.

20
install-dependencies.sh Normal file
View File

@ -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
)

22
src/Makefile Normal file
View File

@ -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)

106
src/buffer.h Normal file
View File

@ -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 <stdlib.h>
#include <string.h>
#include <errno.h>
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

209
src/bufio.c Normal file
View File

@ -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 <sys/types.h>
#include <sys/socket.h>
#include <sys/sendfile.h>
#include <netdb.h>
#include <netinet/in.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#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);
}

18
src/bufio.h Normal file
View File

@ -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 */

71
src/hexdump.c Normal file
View File

@ -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 <ctype.h>
#include <stdio.h>
#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");
}

2
src/hexdump.h Normal file
View File

@ -0,0 +1,2 @@
void hexdump(void *buf, size_t len);

369
src/http.c Normal file
View File

@ -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 <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdbool.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <fcntl.h>
#include <linux/limits.h>
#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;
}

61
src/http.h Normal file
View File

@ -0,0 +1,61 @@
#ifndef _HTTP_H
#define _HTTP_H
#include <jwt.h>
#include <stdbool.h>
#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 */

69
src/main.c Normal file
View File

@ -0,0 +1,69 @@
/*
* Skeleton files for personal server assignment.
*
* @author Godmar Back
* written for CS3214, Spring 2018.
*/
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#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);
}

138
src/socket.c Normal file
View File

@ -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 <sys/types.h>
#include <sys/socket.h>
#include <sys/sendfile.h>
#include <netdb.h>
#include <netinet/in.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#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;
}

7
src/socket.h Normal file
View File

@ -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 */