Thomas Kolb
6cd65091ff
- Working version with the following features: - Directory listing and navigation - Download files - HTTP error pages (403, 404, 500) - Verbose logging
270 lines
7.2 KiB
C
270 lines
7.2 KiB
C
#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;
|
|
}
|