Initial commit

- Working version with the following features:
  - Directory listing and navigation
  - Download files
  - HTTP error pages (403, 404, 500)
  - Verbose logging
This commit is contained in:
Thomas Kolb 2013-01-17 21:13:15 +01:00
commit 6cd65091ff
8 changed files with 831 additions and 0 deletions

6
.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
*.o
*.swp
fileshare
core
tags
testdir

24
Makefile Normal file
View file

@ -0,0 +1,24 @@
CC=gcc
#CFLAGS+=-O2 -Wall -march=native -pedantic -std=c99 -D_POSIX_C_SOURCE=20120607L -D_XOPEN_SOURCE $(LUA_CFLAGS)
#CFLAGS+=-O2 -Wall -march=native -pedantic -std=c99 -D_POSIX_C_SOURCE=20120607L
CFLAGS+=-g -Wall -pedantic -std=c99 -D_POSIX_C_SOURCE=20120607L
LIBS=-lmicrohttpd
TARGET=fileshare
SOURCE=main.c logger.c dirlisting.c
DEPS=logger.h templates.h dirlisting.h
OBJ=$(patsubst %.c, %.o, $(SOURCE))
$(TARGET): $(OBJ) $(DEPS)
$(CC) -o $(TARGET) $(OBJ) $(LIBS)
%.o: %.c $(DEPS)
$(CC) -c $(CFLAGS) -o $@ $< $(INCLUDES)
doc:
doxygen doxygen.conf
clean:
rm -f $(TARGET)
rm -f $(OBJ)

269
dirlisting.c Normal file
View file

@ -0,0 +1,269 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdint.h>
#include <strings.h>
#include <errno.h>
#include <sys/stat.h>
#include <linux/limits.h>
#include <linux/unistd.h>
#include <dirent.h>
#include "logger.h"
#include "templates.h"
#define FLG_USED 1
#define FLG_ISDIR 2
struct Entry {
char *name;
size_t size;
uint8_t flags;
};
const char* format_size(size_t s) {
static char buf[32];
const char *prefixes = " kMGTPEZY";
float fs = s;
int divisions = 0;
while(fs > 1024) {
fs /= 1024.0f;
divisions++;
}
if(divisions == 0) {
sprintf(buf, "%lu B", s);
} else {
sprintf(buf, "%.2f %ciB", fs, prefixes[divisions]);
}
return buf;
}
static int compare_entries(const void *sp1,const void *sp2 ) {
const struct Entry *e1 = sp1;
const struct Entry *e2 = sp2;
return( strcasecmp(e1->name, e2->name) );
}
char* gen_html(const char *url, struct Entry *entries, size_t numentries) {
char *result;
size_t allocated;
size_t i;
char buf[256];
char fullpath[PATH_MAX];
// allocate a buffer for the result string
allocated = 64 * numentries + strlen(HEADER1 HEADER2 FOOTER) + 4096;
LOG(LVL_DEBUG, "initially allocating result string - size: %lu bytes", allocated * sizeof(char));
result = malloc(allocated * sizeof(char));
result[0] = '\0';
// this is still safe
strcat(result, HEADER1);
sprintf(buf, "<title>Directory listing for %s</title>", url);
strcat(result, buf);
strcat(result, HEADER2);
sprintf(buf, "<h1>Directory listing for %s</h1>", url);
strcat(result, buf);
int isrootdir = strcmp(url, "/") == 0;
// Begin Navigation
strcat(result, "<p>Navigation: ");
// tokenize the URL: init
char *token, *urlclone;
urlclone = strdup(url);
token = strtok(urlclone, "/");
strcpy(fullpath, "/");
if(isrootdir) {
strcat(result, "/");
} else {
strcat(result, "<a href=\"/\">/</a>");
// create a link for each token in URL
do {
LOG(LVL_DUMP, "Found token of %s: %s", url, token);
strcat(fullpath, token);
strcat(fullpath, "/");
sprintf(buf, " <a href=\"%s\">%s</a> /", fullpath, token);
strcat(result, buf);
}
while((token = strtok(NULL, "/")) != NULL);
}
free(urlclone);
strcat(result, "</p>");
// begin listing subdirs
strcat(result, "<h2>Sub-directories</h2>");
strcat(result, "<ul>");
for(i = 0; i < numentries; i++) {
// skip all non-directories
if(!(entries[i].flags & FLG_ISDIR))
continue;
if(isrootdir) {
sprintf(buf, "<li><a href=\"/%s/\">%s/</a></li>\n",
entries[i].name, entries[i].name);
} else {
sprintf(buf, "<li><a href=\"%s/%s/\">%s/</a></li>\n",
url, entries[i].name, entries[i].name);
}
strcat(result, buf);
}
strcat(result, "</ul>");
// begin listing files
strcat(result, "<h2>Files</h2>");
strcat(result, "<ul>");
// here the listing entries are generated
for(i = 0; i < numentries; i++) {
size_t entrylen = 32 + 2 * strlen(entries[i].name);
size_t resultlen = strlen(result);
// skip all directories
if(entries[i].flags & FLG_ISDIR)
continue;
if(allocated < resultlen + entrylen) {
allocated *= 2;
LOG(LVL_DEBUG, "reallocating result string - new size: %lu bytes", allocated * sizeof(char));
char *tmpresult = realloc(result, allocated * sizeof(char));
if(!tmpresult) {
LOG(LVL_ERR, "realloc failed for result string: %s", strerror(errno));
break;
}
result = tmpresult;
}
if(isrootdir) {
sprintf(buf, "<li><a href=\"/%s\">%s</a> [%s]</li>\n",
entries[i].name, entries[i].name, format_size(entries[i].size));
} else {
sprintf(buf, "<li><a href=\"%s/%s\">%s</a> [%s]</li>\n",
url, entries[i].name, entries[i].name, format_size(entries[i].size));
}
strcat(result, buf);
}
strcat(result, "</ul>");
strcat(result, FOOTER);
return result;
}
char* create_dirlisting(const char *url, const char *localpath) {
char *result;
struct Entry *entries;
size_t numentries, allocated;
size_t i;
DIR *dir;
struct dirent *de;
struct stat s;
char fullpath[PATH_MAX];
if((dir = opendir(localpath)) == NULL) {
LOG(LVL_ERR, "Cannot opendir %s: %s", localpath, strerror(errno));
return NULL;
}
// allocate some entry pointers
allocated = 100;
numentries = 0;
entries = malloc(allocated * sizeof(struct Entry));
if(!entries) {
LOG(LVL_ERR, "malloc failed for directory entries: %s", strerror(errno));
return NULL;
}
// initialize all entry strings with NULL
for(i = 0; i < allocated; i++) {
entries[i].flags = 0;
}
while((de = readdir(dir)) != NULL) {
if(de->d_name[0] == '.') {
continue;
}
// check if entries array is full
if(numentries == allocated) {
// double the size of the entries array
LOG(LVL_DEBUG, "reallocating entry list - new size: %lu bytes", 2 * allocated * sizeof(struct Entry));
struct Entry *tmpentries = realloc(entries, 2 * allocated * sizeof(struct Entry));
if(!tmpentries) {
LOG(LVL_ERR, "realloc failed for directory entries: %s", strerror(errno));
free(entries);
return NULL;
}
entries = tmpentries;
// initialize the new entries
for(i = allocated; i < 2*allocated; i++) {
entries[i].flags = 0;
}
allocated *= 2;
}
snprintf(fullpath, PATH_MAX, "%s/%s", localpath, de->d_name);
if(stat(fullpath, &s) == -1) {
LOG(LVL_WARN, "Cannot stat %s while listing directories: %s",
fullpath, strerror(errno));
// stat failed -> ignore this entry
continue;
}
entries[numentries].name = strdup(de->d_name);
entries[numentries].flags |= FLG_USED;
entries[numentries].size = s.st_size;
if(S_ISDIR(s.st_mode)) {
entries[numentries].flags |= FLG_ISDIR;
}
numentries++;
}
closedir(dir);
qsort(entries, numentries, sizeof(struct Entry), compare_entries);
result = gen_html(url, entries, numentries);
for(i = 0; i < numentries; i++) {
if(entries[i].flags & FLG_USED) free(entries[i].name);
}
free(entries);
return result;
}

6
dirlisting.h Normal file
View file

@ -0,0 +1,6 @@
#ifndef DIRLISTING_H
#define DIRLISTING_H
char* create_dirlisting(const char *url, const char *localpath);
#endif // DIRLISTING_H

151
logger.c Normal file
View file

@ -0,0 +1,151 @@
#include <stdio.h>
#include <malloc.h>
#include <time.h>
#include <sys/time.h>
#include "logger.h"
// define constants
const char *LOGGER_STR_FATAL = "FATAL";
const char *LOGGER_STR_ERR = "ERROR";
const char *LOGGER_STR_WARN = "WARN ";
const char *LOGGER_STR_INFO = "INFO ";
const char *LOGGER_STR_DEBUG = "DEBUG";
const char *LOGGER_STR_DUMP = "DUMP ";
const char *LOGGER_COLOR_FATAL = "\033[1;31m";
const char *LOGGER_COLOR_ERR = "\033[1;31m";
const char *LOGGER_COLOR_WARN = "\033[1;33m";
const char *LOGGER_COLOR_INFO = "\033[1;32m";
const char *LOGGER_COLOR_DEBUG = "\033[1m";
const char *LOGGER_COLOR_DUMP = "\033[1;30m";
const char *LOGGER_COLOR_NONE = "\033[0m";
// global variables
sem_t logger_semaphore;
int logger_verbosity;
int logger_use_colors;
void logger_init(void) {
// Initialize the semaphore
sem_init(&logger_semaphore, 0, 1);
logger_verbosity = 2147483647;
logger_use_colors = 1;
}
void logger_shutdown(void) {
sem_destroy(&logger_semaphore);
}
void logger_enable_colors(int enable) {
logger_use_colors = enable;
}
void logger_set_verbosity(int verbosity) {
logger_verbosity = verbosity;
}
void logger_debug_message(const char *prefix, const char *fmt, va_list ap) {
/* Guess we need no more than 100 bytes. */
int n, size = 100;
char *p, *np;
va_list internal_ap;
if ((p = (char*)malloc(size)) == NULL) {
fprintf(stderr, "[%s] FATAL: Cannot allocate string buffer while processing arguments.\n", LOGGER_STR_ERR);
return;
}
while (1) {
/* Try to print in the allocated space. */
va_copy(internal_ap, ap);
n = vsnprintf(p, size, fmt, internal_ap);
va_end(internal_ap);
/* If that worked, return the string. */
if (n > -1 && n < size)
break;
/* Else try again with more space. */
if (n > -1) /* glibc 2.1 */
size = n+1; /* precisely what is needed */
else /* glibc 2.0 */
size *= 2; /* twice the old size */
if ((np = (char*)realloc (p, size)) == NULL) {
free(p);
fprintf(stderr, "[%s] FATAL: Cannot reallocate string buffer while processing arguments.\n", LOGGER_STR_ERR);
return;
} else {
p = np;
}
}
sem_wait(&logger_semaphore);
fprintf(stderr, "%s %s\n", prefix, p);
sem_post(&logger_semaphore);
free(p);
}
void logger_log(int level, const char *format, ...) {
va_list argptr;
char timebuf[32];
char timebuf2[32];
char prefixbuf[64];
const char *prefixcolor = "", *prefixtext = "";
if(level > logger_verbosity)
return;
struct timeval tv;
gettimeofday(&tv, NULL);
strftime(timebuf, 32, "%Y-%M-%d %H:%M:%S.%%03d", localtime(&(tv.tv_sec)));
snprintf(timebuf2, 32, timebuf, tv.tv_usec/1000);
if(level >= LVL_DUMP) {
if(logger_use_colors)
prefixcolor = LOGGER_COLOR_DUMP;
prefixtext = LOGGER_STR_DUMP;
} else if(level >= LVL_DEBUG) {
if(logger_use_colors)
prefixcolor = LOGGER_COLOR_DEBUG;
prefixtext = LOGGER_STR_DEBUG;
} else if(level >= LVL_INFO) {
if(logger_use_colors)
prefixcolor = LOGGER_COLOR_INFO;
prefixtext = LOGGER_STR_INFO;
} else if(level >= LVL_WARN) {
if(logger_use_colors)
prefixcolor = LOGGER_COLOR_WARN;
prefixtext = LOGGER_STR_WARN;
} else if(level >= LVL_ERR) {
if(logger_use_colors)
prefixcolor = LOGGER_COLOR_ERR;
prefixtext = LOGGER_STR_ERR;
} else {
if(logger_use_colors)
prefixcolor = LOGGER_COLOR_FATAL;
prefixtext = LOGGER_STR_FATAL;
}
if(logger_use_colors) {
sprintf(prefixbuf, "%s [%s%s%s]", timebuf2, prefixcolor, prefixtext, LOGGER_COLOR_NONE);
} else {
sprintf(prefixbuf, "%s [%s]", timebuf2, prefixtext);
}
va_start(argptr, format);
logger_debug_message(prefixbuf, format, argptr);
va_end(argptr);
}

27
logger.h Normal file
View file

@ -0,0 +1,27 @@
#ifndef LOGGER_H
#define LOGGER_H
#include <semaphore.h>
#include <stdarg.h>
static const int LVL_FATAL = 0; /*!< Fatal message level */
static const int LVL_ERR = 5; /*!< Error message level */
static const int LVL_WARN = 50; /*!< Warning message level */
static const int LVL_INFO = 100; /*!< Information message level */
static const int LVL_DEBUG = 200; /*!< Debug message level */
static const int LVL_DUMP = 500; /*!< Dump message level */
extern sem_t logger_semaphore;
extern int logger_verbosity;
extern int logger_use_colors;
void logger_init(void);
void logger_shutdown(void);
void logger_enable_colors(int enable);
void logger_set_verbosity(int verbosity);
void logger_log(int level, const char *format, ...);
#define LOG(level, ...) logger_log(level, __VA_ARGS__)
#endif // LOGGER_H

269
main.c Normal file
View file

@ -0,0 +1,269 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <linux/limits.h>
#include <microhttpd.h>
#include "logger.h"
#include "templates.h"
#include "dirlisting.h"
#define DEFAULT_PORT 8888
struct ConnectionState {
char cleanedURL[PATH_MAX];
char localFileName[PATH_MAX];
struct stat targetStat;
};
char *shareRoot;
struct MHD_Response *error403Response;
struct MHD_Response *error404Response;
struct MHD_Response *error500Response;
int running = 1;
void remove_trailing_slash(char *str) {
size_t offset = strlen(str)-1;
if((offset != 0) && (str[offset] == '/')) {
str[offset] = '\0';
}
}
void request_completed(void *cls,
struct MHD_Connection * connection,
void **ptr,
enum MHD_RequestTerminationCode toe) {
struct ConnectionState *connstate = *ptr;
// TODO: show IP+Port in log
LOG(LVL_DEBUG, "Freeing state for request of %s", connstate->localFileName);
free(connstate);
}
static int connection_handler(void * cls,
struct MHD_Connection *connection,
const char *url,
const char *method,
const char *version,
const char *upload_data,
size_t *upload_data_size,
void **ptr) {
struct ConnectionState *connstate;
struct MHD_Response *response;
int ret;
if (0 != strcmp(method, "GET")) {
LOG(LVL_WARN, "Unexpected method: %s.", method);
return MHD_NO;
}
if (*ptr == NULL) {
// This is the first time this connection is seen.
// Initialize the connection state
connstate = malloc(sizeof(struct ConnectionState));
*ptr = connstate;
// set the local file name
strncpy(connstate->localFileName, shareRoot, PATH_MAX);
strncat(connstate->localFileName, url,
PATH_MAX - strlen(connstate->localFileName));
remove_trailing_slash(connstate->localFileName);
strncpy(connstate->cleanedURL, url, PATH_MAX);
remove_trailing_slash(connstate->cleanedURL);
LOG(LVL_INFO, "New %s request for %s (local: %s)",
method, url, connstate->localFileName);
return MHD_YES;
} else {
connstate = *ptr;
}
if (0 != *upload_data_size) {
return MHD_NO; // upload data in a GET!?
}
// check if url contains a "/.." sequence. If so, block the request as it is
// trying to access the parent directory (and may be an attempt to leave the
// shared space.
if(NULL != strstr(url, "/..")) {
LOG(LVL_WARN, "User is trying to access %s, which may be an attempt to leave the shared space.",
url);
return MHD_queue_response(connection, MHD_HTTP_FORBIDDEN, error403Response);
}
// check properties of the target file/dir
if(stat(connstate->localFileName, &(connstate->targetStat)) == -1) {
LOG(LVL_ERR, "Cannot stat %s: %s",
connstate->localFileName, strerror(errno));
return MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, error404Response);
}
if(S_ISREG(connstate->targetStat.st_mode)) {
LOG(LVL_DEBUG, "Serving %s as a regular file.", connstate->localFileName);
// open a file descriptor to the file
int fd = open(connstate->localFileName, O_RDONLY);
if(fd == -1) {
LOG(LVL_ERR, "Cannot open %s: %s",
connstate->localFileName, strerror(errno));
return MHD_queue_response(connection, MHD_HTTP_FORBIDDEN, error403Response);
}
// no error so far -> serve the file
response = MHD_create_response_from_fd(
connstate->targetStat.st_size,
fd);
ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
MHD_destroy_response(response);
return ret;
} else if(S_ISDIR(connstate->targetStat.st_mode)) {
LOG(LVL_DEBUG, "Generating directory listing for %s.", connstate->localFileName);
char *result = create_dirlisting(connstate->cleanedURL, connstate->localFileName);
if(!result) {
LOG(LVL_ERR, "Failed to generate dirlisting for %s.", connstate->localFileName);
return MHD_queue_response(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, error500Response);
}
response = MHD_create_response_from_buffer(
strlen(result), result, MHD_RESPMEM_MUST_FREE);
MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "text/html");
ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
MHD_destroy_response(response);
return ret;
} else {
LOG(LVL_WARN,
"%s is neither a directory nor a regular file. Don't allow the access.",
connstate->localFileName);
return MHD_queue_response(connection, MHD_HTTP_FORBIDDEN, error403Response);
}
// generate a 500 if request was not handled properly
return MHD_queue_response(
connection, MHD_HTTP_INTERNAL_SERVER_ERROR, error500Response);
}
int main(int argc, char ** argv) {
struct MHD_Daemon *d;
struct stat sBuf;
int port;
logger_init();
if (argc < 2) {
printf("%s <dir> [<port>]\n", argv[0]);
return 1;
}
// check if shareRoot is an existing directory
shareRoot = argv[1];
if(stat(shareRoot, &sBuf) == -1) {
LOG(LVL_FATAL, "Cannot stat %s: %s", shareRoot, strerror(errno));
return EXIT_FAILURE;
}
if(!S_ISDIR(sBuf.st_mode)) {
LOG(LVL_FATAL, "%s is not a directory!", shareRoot);
return EXIT_FAILURE;
}
// remove trailing / from shareRoot
remove_trailing_slash(shareRoot);
LOG(LVL_INFO, "Shared directory is: %s", shareRoot);
if(argc >= 3) {
// a port was given on the command line, try to parse it
if(sscanf(argv[2], "%i", &port) != 1) {
LOG(LVL_FATAL, "%s is not a valid port number!", argv[2]);
return EXIT_FAILURE;
}
if(port < 1 || port > 65535) {
LOG(LVL_FATAL, "Port %i is out of range [1-65535]!", port);
return EXIT_FAILURE;
}
LOG(LVL_INFO, "Using port: %i", port);
} else {
LOG(LVL_INFO, "Using default port: %i", DEFAULT_PORT);
port = DEFAULT_PORT;
}
// create the static response for error pages
error403Response = MHD_create_response_from_data(
strlen(ERROR_403),
(void*) ERROR_403,
MHD_NO,
MHD_NO);
error404Response = MHD_create_response_from_data(
strlen(ERROR_404),
(void*) ERROR_404,
MHD_NO,
MHD_NO);
error500Response = MHD_create_response_from_data(
strlen(ERROR_500),
(void*) ERROR_500,
MHD_NO,
MHD_NO);
MHD_add_response_header(error403Response, MHD_HTTP_HEADER_CONTENT_TYPE, "text/html");
MHD_add_response_header(error404Response, MHD_HTTP_HEADER_CONTENT_TYPE, "text/html");
MHD_add_response_header(error500Response, MHD_HTTP_HEADER_CONTENT_TYPE, "text/html");
d = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION,
port,
NULL,
NULL,
&connection_handler,
NULL,
MHD_OPTION_NOTIFY_COMPLETED,
&request_completed, NULL,
MHD_OPTION_END);
if (d == NULL) {
LOG(LVL_FATAL, "Cannot start up libmicrohttpd.");
return EXIT_FAILURE;
}
while(running) {
getc(stdin);
}
LOG(LVL_INFO, "Shutting down...");
MHD_stop_daemon(d);
MHD_destroy_response(error403Response);
MHD_destroy_response(error404Response);
MHD_destroy_response(error500Response);
logger_shutdown();
return EXIT_SUCCESS;
}

79
templates.h Normal file
View file

@ -0,0 +1,79 @@
#ifndef TEMPLATES_H
#define TEMPLATES_H
// generic definitions
#define HEADER1 \
"<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01//EN\"" \
" \"http://www.w3.org/TR/html4/strict.dtd\">" \
"<html>" \
" <head>"
#define HEADER2 \
" <meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\" >" \
" </head>" \
" <body>"
#define FOOTER \
" </body>" \
"</html>"
// error page definitions
#define ERROR_404 \
HEADER1 \
" <title>Error 404*i - File is imaginary</title>" \
HEADER2 \
" <h1>Error 404*i - File is imaginary</h1>" \
" <p>This page cannot be displayed because it is imaginary.<p>" \
" <p>To view this page in the real world, turn your screen by <sup>&pi;</sup>/<sub>2</sub> .</p>" \
" <pre>" \
" ^ Im\n" \
" |\n" \
" | +j\n" \
" __--+--__\n" \
" _- | -_\n" \
" / X \\\n" \
" | | |\n" \
"----+-------+-------+-----> Re\n" \
" -1 | | +1 |\n" \
" \\ | /\n" \
" --__ | __--\n" \
" --+-- \n" \
" | -j\n" \
" |\n" \
" </pre>" \
FOOTER
#define ERROR_500 \
HEADER1 \
" <title>Error 500 - Internal Server Error</title>" \
HEADER2 \
" <h1>Error 500 - Internal Server Error</h1>" \
" <pre>" \
" _______________________________________ \n" \
"/ Oops. Something went wrong. \\\n" \
"| Please contact the administrator and |\n" \
"\\ tell him how to reproduce this error. /\n" \
" --------------------------------------- \n" \
" \\ \\_______\n" \
" v__v \\ \\ O )\n" \
" (oo) ||----w |\n" \
" (__) || || \\/\\\n" \
" </pre>" \
FOOTER
#define ERROR_403 \
HEADER1 \
" <title>Error 403 - Forbidden</title>" \
HEADER2 \
" <h1>Error 403 - Forbidden</h1>" \
" <p>You are not allowed to access this file or directory.<p>" \
" <p>This error is shown under the following conditions:<p>" \
" <ul>" \
" <li>You are trying to access a file that the server isn't allowed to read</li>" \
" <li>The target filesystem entry is not a regular file or directory</li>" \
" <li>The URL contains \"/..\", which is blocked for security reasons</li>" \
" </ul>" \
FOOTER
#endif // TEMPLATES_H