Skip to content

Instantly share code, notes, and snippets.

@hugsy

hugsy/websrv.c Secret

Created January 18, 2017 01:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hugsy/00d74ecac86297efc6772e415f307176 to your computer and use it in GitHub Desktop.
Save hugsy/00d74ecac86297efc6772e415f307176 to your computer and use it in GitHub Desktop.
/*
* WebSrv - Simple, buggy web server
*
* (c) 2015 Samuel Groß
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define min(a, b) (((a) < (b)) ? (a) : (b))
#define PORT 80
#define TIMEOUT 120
#define CRLF "\r\n"
#define CRLF2 "\r\n\r\n"
#define WEBROOT "webroot/"
struct sockaddr_in client;
char buf[2048];
ssize_t bufsz;
int die(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
exit(-1);
}
void wait_for_child(int sig)
{
while (waitpid(-1, NULL, WNOHANG) > 0);
}
void handle_alarm(int sig)
{
puts("Client timed out...");
exit(0);
}
void http_send(int socket, const char *fmt, ...)
{
char msg[2048], *pos;
va_list args;
memset(msg, 0, sizeof(msg));
va_start(args, fmt);
vsprintf(msg, fmt, args);
va_end(args);
send(socket, msg, strlen(msg), 0);
}
int send_error(int socket, int code, const char* msg)
{
char* body = ""
"<html>\n"
" <head>\n"
" <title>No.</title>\n"
" </head>\n"
" <body>\n"
" <center><h1>%d %s</h1></center>\n"
" <hr><center>Super Secure Web Server v.3.1.33.7</center>\n"
" </body>\n"
"</html>";
http_send(socket, "HTTP/1.1 %d %s" CRLF, code, msg);
http_send(socket, "Content-Type: text/html" CRLF);
http_send(socket, "Content-Length: %d" CRLF2, strlen(body) + 3 + strlen(msg) - 4);
http_send(socket, body, code, msg);
return code;
}
int handle_led_cmd(int socket, char* cmd)
{
if (strcmp(cmd, "ledon") == 0)
system("./led on");
else
system("./led off");
char* body = "OK";
http_send(socket, "HTTP/1.1 200 OK" CRLF);
http_send(socket, "Content-Length: %d" CRLF2, strlen(body));
http_send(socket, "%s", body);
return 200;
}
static void* get_range_header(char* req, size_t reqlen)
{
const char *s = "\r\nRange: bytes=";
return memmem(req, reqlen, s, strlen(s));
}
static int get_ranges_from_header(char *request, unsigned long *start , unsigned long *end)
{
char *req = request+strlen("\r\nRange: bytes=");
char *c, _c;
char *tmp;
for(c=req; *c!='\r' && *c!='\n' && *c!='-' && *c!=' ' && *c!='\0'; c++);
if (*c!='-')
return -1;
// get beginning of range
_c = *c;
*c = '\0';
tmp = strdupa(req);
*start = strtoul(tmp, NULL, 10);
*c = _c;
// get end of range
req = ++c;
for(c=req; *c!='\r' && *c!='\n' && *c!=' ' && *c!='\0'; c++);
_c = *c;
*c = '\0';
tmp = strdupa(req);
*end = strtoul(tmp, NULL, 10);
*c = _c;
return 0;
}
int handle_req(int socket, char* request, size_t len)
{
FILE* f;
long fsize;
char buf[2048], *file, *fend;
if (memcmp(request, "GET", 3) != 0) {
return send_error(socket, 501, "Not Implemented");
}
/*
* Determine requested file
*/
file = request + 4;
fend = memchr(file, ' ', len-4);
if (!fend)
return send_error(socket, 400, "Bad Request");
*fend = 0;
if (strcmp(file, "/") == 0)
file = "index.html";
if (strcmp(file, "/ledon") == 0 || strcmp(file, "/ledoff") == 0)
return handle_led_cmd(socket, file+1);
printf("%s:%d request for file '%s'\n", inet_ntoa(client.sin_addr), htons(client.sin_port), file);
strcpy(buf, WEBROOT);
strcat(buf, file);
/*
* Open file
*/
f = fopen(buf, "r");
if (!f)
return send_error(socket, 404, "Not Found");
fseek(f, 0, SEEK_END);
fsize = ftell(f);
fseek(f, 0, SEEK_SET);
/*
* Follow HTTP Range header directive if provided
*/
unsigned long start, end;
char *ptr;
int r;
start = end = 0;
ptr = get_range_header(request, len);
if (ptr){
if(get_ranges_from_header(ptr, &start, &end)==0){
if (start && end){
printf("%s:%d reading range of file '%s' from %u-%u\n", inet_ntoa(client.sin_addr), htons(client.sin_port), file, start, end);
if (lseek(fileno(f), start, SEEK_SET)==-1){
perror("lseek() failed:");
} else {
fsize = end - start;
}
}
}
}
if (fsize==0){
fsize=0x8000;
}
/*
* Send header
*/
http_send(socket, "HTTP/1.1 200 OK" CRLF);
http_send(socket, "Content-Type: text/html" CRLF);
http_send(socket, "Content-Length: %d" CRLF2, fsize);
/*
* Send body
*/
while ((len = fread(buf, 1, sizeof(buf), f)) > 0) {
send(socket, buf, len, 0);
if(--fsize <= 0)
break;
}
fclose(f);
return 200;
}
int handle_single_request(int socket)
{
ssize_t len, cntlen;
char req[4096];
char *ptr, *pos;
/*
* Read Header
*/
ptr = req;
while (1) {
// we could write directly into 'ptr', but this makes reversing a bit more interesting I guess
if (bufsz == 0) {
bufsz = recv(socket, buf, sizeof(buf), 0);
if (bufsz <= 0)
return -1;
}
memcpy(ptr, buf, bufsz);
ptr += bufsz;
bufsz = 0;
pos = memmem(req, ptr - req, CRLF2, 4);
if (pos) {
bufsz = ptr - (pos + 4);
ptr = pos + 4;
memcpy(buf, ptr, bufsz);
*ptr = 0; // make it a c string
break;
}
}
/*
* Read Body
*/
pos = strcasestr(req, "Content-Length:");
if (pos) {
pos += 15;
while (isspace(*pos)) pos++;
cntlen = atoi(pos);
while (cntlen > 0) {
if (bufsz == 0) {
len = recv(socket, ptr, cntlen, 0);
if (len <= 0)
return -1;
cntlen -= len;
ptr += len;
} else {
len = min(bufsz, cntlen);
memcpy(ptr, buf, len);
ptr += len;
cntlen -= len;
bufsz -= len;
if (bufsz != 0) {
memmove(buf, buf + len, bufsz);
}
}
}
}
/*
* Process Request
*/
return handle_req(socket, req, ptr - req);
}
void handle_client(int socket)
{
int code, reqcount = 0;
while (1) {
reqcount++;
code = handle_single_request(socket);
if (code < 0) return;
printf("%s:%d request #%d => %d\n", inet_ntoa(client.sin_addr), htons(client.sin_port), reqcount, code);
}
}
int main()
{
int sockfd, clientfd, pid;
unsigned int clilen;
struct sockaddr_in serv_addr;
struct sigaction sa;
/*
* Set up the signal handlers
*/
sa.sa_handler = wait_for_child;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
if (sigaction(SIGCHLD, &sa, NULL) == -1)
die("sigaction() failed: %s\n", strerror(errno));
sa.sa_handler = handle_alarm;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
if (sigaction(SIGALRM, &sa, NULL) == -1)
die("sigaction() failed: %s\n", strerror(errno));
/*
* Set up socket
*/
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
die("socket() failed: %s\n", strerror(errno));
int val = 1;
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) == -1)
die("setsockopt() failed with %s\n", strerror(errno));
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(PORT);
if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
die("bind() failed: %s\n", strerror(errno));
if (listen(sockfd, 5) == -1)
die("listen() failed: %s\n", strerror(errno));
/*
* Server loop
*/
while (1) {
clilen = sizeof(client);
clientfd = accept(sockfd, (struct sockaddr*)&client, &clilen);
if (clientfd < 0)
die("accept() failed: %s\n", strerror(errno));
printf("New connection from %s on port %d\n", inet_ntoa(client.sin_addr), htons(client.sin_port));
pid = fork();
if (pid < 0) {
die("fork() failed: %s\n", strerror(errno));
} else if (pid == 0) {
/*
* TODO: Even though we use CAP_NET_BIND_SERVICE we might still want to drop privs so child can't interfere with parent
*/
close(sockfd);
alarm(TIMEOUT);
handle_client(clientfd);
printf("%s:%d disconnected\n", inet_ntoa(client.sin_addr), htons(client.sin_port));
close(clientfd);
exit(0);
} else {
close(clientfd);
}
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment