From d3220aecccf5873182335ebd6c60f26379201453 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Tue, 12 May 2015 12:09:44 +0200 Subject: [PATCH] added select() version --- session4/.gitignore | 4 + session4/Makefile | 23 ++++ session4/httpd.c | 259 ++++++++++++++++++++++++++++++++++++++++++++ session4/readline.c | 71 ++++++++++++ session4/readline.h | 22 ++++ session4/www | 1 + 6 files changed, 380 insertions(+) create mode 100644 session4/.gitignore create mode 100644 session4/Makefile create mode 100644 session4/httpd.c create mode 100644 session4/readline.c create mode 100644 session4/readline.h create mode 120000 session4/www diff --git a/session4/.gitignore b/session4/.gitignore new file mode 100644 index 0000000..67d5972 --- /dev/null +++ b/session4/.gitignore @@ -0,0 +1,4 @@ +httpd +httpd6 +httpdgai +acme.com/ diff --git a/session4/Makefile b/session4/Makefile new file mode 100644 index 0000000..9a756cd --- /dev/null +++ b/session4/Makefile @@ -0,0 +1,23 @@ +TARGETS = httpd httpd6 httpdgai + +CC = gcc +CFLAGS = -g -std=c99 + +all: $(TARGETS) Makefile + +httpd6: CFLAGS += -DIPV6 +httpdgai: CFLAGS += -DGAI + +httpdgai httpd httpd6: httpd.c readline.c + $(CC) $(CFLAGS) $^ -o $@ + +test: httpd + sudo -i && ./httpd www 80 & + wget http://localhost:80 + killall httpd + +clean: + rm -f $(TARGETS) + rm -f *.o *~ + +.PHONY: all clean diff --git a/session4/httpd.c b/session4/httpd.c new file mode 100644 index 0000000..137172d --- /dev/null +++ b/session4/httpd.c @@ -0,0 +1,259 @@ +#define _GNU_SOURCE +#define _POSIX_C_SOURCE 200809L + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "readline.h" + +#define ARRAY_LEN(x) (sizeof(x) / sizeof(x[0])) + +#define HTTP(c, m) (struct status) { .code = c, .msg = m } + +static const char err[] = "\r\n" + "\r\n

%u %s

\r\n"; + +int request(int outfd) { + int closing = 1; + struct stat st; + struct status{ int code; const char *msg; } rc; + + int infd; + char *method, *path, *version, line[1024]; + if (readline(outfd, line, sizeof(line)) > 0) { + method = strtok(line, " "); + path = strtok(NULL, " "); + version = strtok(NULL, "\r\n"); + + if (!method || !path || !version) + rc = HTTP(400, "Bad Request"); + + if (strcasecmp(method, "GET")) + rc = HTTP(405, "Method Not Allowed"); + else if (strcasecmp(version, "HTTP/1.1")) + rc = HTTP(505, "HTTP Version not supported"); + else { + /* Parse Headers */ + char header[1024]; + while (readline(outfd, header, sizeof(header))) { + char *key = strtok(header, ":"); + char *value = strtok(NULL, "\r\n"); + + if (strcmp(key, "\r\n") == 0) + break; + + if (key && value) { + printf("Header: %s => %s\n", key, value); + + if (strcasecmp(key, "Connection") == 0 && strcasecmp(value+1, "keep-alive") == 0) + closing = 0; + } + } + + if (!strcmp(path, "/")) + path = "/index.html"; + + errno = 0; + if (stat(path, &st) || !S_ISREG(st.st_mode)) + goto err; + + infd = open(path, O_RDONLY); +err: switch (errno) { + case 0: rc = HTTP(200, "OK"); break; + case EACCES: rc = HTTP(403, "Forbidden"); break; + case ENOENT: rc = HTTP(404, "Not Found"); break; + case ENAMETOOLONG: rc = HTTP(414, "Request-URL Too Long"); break; + default: rc = HTTP(500, "Internal Server Error"); break; + } + } + + printf(" >>> %s %s %s\r\n", method, path, version); /* debug */ + } + else + return 1; + + printf(" <<< HTTP/1.1 %u %s\r\n", rc.code, rc.msg); + + dprintf(outfd, "HTTP/1.1 %u %s\r\n", rc.code, rc.msg); + //dprintf(outfd, "Connection: close\r\n"); + + switch (rc.code) { + case 200: + dprintf(outfd, "Content-length: %lu\r\n\r\n", st.st_size); + + if (!sendfile(outfd, infd, 0, st.st_size)) + error(-1, errno, "sendfile"); + + close(infd); + break; + + default: + dprintf(outfd, err, rc.code, rc.msg); + break; + } + + return closing; +} + +int main(int argc, char *argv[]) { + int val = 1, accept_fd; + char lname[NI_MAXHOST], lserv[NI_MAXSERV]; + + if (argc != 3 && argc != 4) + error(-1, 0, "usage: %s HTDOCS PORT\n", argv[0]); + + char *host = argc == 4 ? argv[3] : NULL; + char *port = argv[2]; + +#ifdef GAI + struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_flags = AI_PASSIVE | AI_ADDRCONFIG, + .ai_socktype = SOCK_STREAM + }, *res; + + if (getaddrinfo(host, port, &hints, &res)) + error(-1, errno, "Failed to get socket address"); + + struct sockaddr *sap = res[0].ai_addr; + socklen_t salen = res[0].ai_addrlen; +#else + #ifdef IPV6 + struct sockaddr_in6 sa = { + .sin6_family = AF_INET6, + .sin6_addr = IN6ADDR_ANY_INIT, + .sin6_port = htons(atoi(argv[2])) + }; + #else + struct sockaddr_in sa = { + .sin_family = AF_INET, + .sin_addr.s_addr = INADDR_ANY, + .sin_port = htons(atoi(argv[2])) + }; + #endif + struct sockaddr *sap = (struct sockaddr *) &sa; + socklen_t salen = sizeof(sa); +#endif + + if (getnameinfo(sap, salen, + lname, sizeof(lname), lserv, sizeof(lserv), 0)) + error(-1, errno, "Failed to get name"); + + accept_fd = socket(sap->sa_family, SOCK_STREAM, 0); + if (accept_fd < 0) + error(-1, errno, "Failed to create socket"); + if (setsockopt(accept_fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val))) + error(-1, errno, "Failed setsockopt"); + if (bind(accept_fd, sap, salen)) + error(-1, errno, "Failed to bind socket"); + if (listen(accept_fd, 10)) + error(-1, errno, "Failed to listen on socket"); + if (chroot(argv[1])) + error(-1, errno, "Failed to enter chroot"); + +#ifdef GAI + freeaddrinfo(res); +#endif + + int pool[] = { [0 ... 31] = -1 }; + fd_set readfds; + + for (;;) { + int result; + /* Waiting for new data */ + do { + FD_ZERO(&readfds); + FD_SET(STDIN_FILENO, &readfds); + FD_SET(accept_fd, &readfds); + + int nfds = accept_fd; + for (int i = 0; i < ARRAY_LEN(pool); i++) { + if (pool[i] >= 0) { + FD_SET(pool[i], &readfds); + nfds = pool[i] > nfds ? pool[i] : nfds; + } + } + + result = select(nfds + 1, &readfds, NULL, NULL, NULL); + } while (result == -1 && errno == EINTR); + + /* New connection? */ + if (FD_ISSET(accept_fd, &readfds)) { + /* Search emptly slot in pool */ + for (int i = 0; i < ARRAY_LEN(pool); i++) { + if (pool[i] < 0) { + struct sockaddr_storage peer; + socklen_t peerlen = sizeof(peer); + + pool[i] = accept(accept_fd, (struct sockaddr *) &peer, &peerlen); + if (pool[i] < 0) + error(-1, errno, "Failed accept"); + + char pname[NI_MAXHOST], pserv[NI_MAXSERV]; + int ret = getnameinfo((struct sockaddr *) &peer, peerlen, + pname, sizeof(pname), pserv, sizeof(pserv), 0); + if (ret) + printf("New connection: (%s) => %s:%s\n", + gai_strerror(ret), lname, lserv); + else + printf("New connection: %s:%s => %s:%s\n", + pname, pserv, lname, lserv); + + break; + } + } + } + + /* Handle existing connections */ + for (int i = 0; i < ARRAY_LEN(pool); i++) { + if (FD_ISSET(pool[i], &readfds)) { + if (request(pool[i])) { + printf("Closing connection...\n"); + + shutdown(pool[i], SHUT_RDWR); + pool[i] = -1; + } + } + } + + /* Key pressed? */ + if (FD_ISSET(STDIN_FILENO, &readfds)) { + switch (getchar()) { + case 'q': /* Quit server */ + goto quit; + + case 'k': /* Close all connections immeadiately */ + printf("Killing all connections!\n"); + + for (int i = 0; i < ARRAY_LEN(pool); i++) { + close(pool[i]); + pool[i] = -1; + } + break; + + default: + fprintf(stderr, "Unknown key\n"); + } + } + } + +quit: + printf("Goodbye!\n"); + + return 0; +} diff --git a/session4/readline.c b/session4/readline.c new file mode 100644 index 0000000..0cc1bb6 --- /dev/null +++ b/session4/readline.c @@ -0,0 +1,71 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2015. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU Lesser General Public License as published * +* by the Free Software Foundation, either version 3 or (at your option) * +* any later version. This program is distributed without any warranty. * +* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* read_line.c + + Implementation of readLine(). +*/ +#include +#include +#include "readline.h" /* Declaration of readLine() */ + +/* Read characters from 'fd' until a newline is encountered. If a newline + character is not encountered in the first (n - 1) bytes, then the excess + characters are discarded. The returned string placed in 'buf' is + null-terminated and includes the newline character if it was read in the + first (n - 1) bytes. The function return value is the number of bytes + placed in buffer (which includes the newline character if encountered, + but excludes the terminating null byte). */ + +ssize_t +readline(int fd, void *buffer, size_t n) +{ + ssize_t numRead; /* # of bytes fetched by last read() */ + size_t totRead; /* Total bytes read so far */ + char *buf; + char ch; + + if (n <= 0 || buffer == NULL) { + errno = EINVAL; + return -1; + } + + buf = buffer; /* No pointer arithmetic on "void *" */ + + totRead = 0; + for (;;) { + numRead = read(fd, &ch, 1); + + if (numRead == -1) { + if (errno == EINTR) /* Interrupted --> restart read() */ + continue; + else + return -1; /* Some other error */ + + } else if (numRead == 0) { /* EOF */ + if (totRead == 0) /* No bytes read; return 0 */ + return 0; + else /* Some bytes read; add '\0' */ + break; + + } else { /* 'numRead' must be 1 if we get here */ + if (totRead < n - 1) { /* Discard > (n - 1) bytes */ + totRead++; + *buf++ = ch; + } + + if (ch == '\n') + break; + } + } + + *buf = '\0'; + return totRead; +} diff --git a/session4/readline.h b/session4/readline.h new file mode 100644 index 0000000..4121893 --- /dev/null +++ b/session4/readline.h @@ -0,0 +1,22 @@ +/*************************************************************************\ +* Copyright (C) Michael Kerrisk, 2015. * +* * +* This program is free software. You may use, modify, and redistribute it * +* under the terms of the GNU Lesser General Public License as published * +* by the Free Software Foundation, either version 3 or (at your option) * +* any later version. This program is distributed without any warranty. * +* See the files COPYING.lgpl-v3 and COPYING.gpl-v3 for details. * +\*************************************************************************/ + +/* read_line.h + + Header file for read_line.c. +*/ +#ifndef READ_LINE_H +#define READ_LINE_H + +#include + +ssize_t readline(int fd, void *buffer, size_t n); + +#endif diff --git a/session4/www b/session4/www new file mode 120000 index 0000000..764ad2f --- /dev/null +++ b/session4/www @@ -0,0 +1 @@ +../session2/www/ \ No newline at end of file