#include #include #include #include #include #include #include #include #include #include #ifdef HAVE_MAGIC #include #endif #include "logger.h" #include "templates.h" #include "dirlisting.h" #include "util.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; #ifdef HAVE_MAGIC magic_t magicCookie; #endif int running = 1; 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); } int serv_regular_file(struct MHD_Connection *connection, struct ConnectionState *connstate) { struct MHD_Response *response; int ret; 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); #ifdef HAVE_MAGIC // if libmagic is available, determine the correct MIME type for the file const char *mimeType = magic_file(magicCookie, connstate->localFileName); LOG(LVL_DEBUG, "MIME type for %s: %s", connstate->localFileName, mimeType); MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, mimeType); #endif ret = MHD_queue_response(connection, MHD_HTTP_OK, response); MHD_destroy_response(response); return ret; } int serv_directory(struct MHD_Connection *connection, struct ConnectionState *connstate) { struct MHD_Response *response; int ret; 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; } 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; 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)) { return serv_regular_file(connection, connstate); } else if(S_ISDIR(connstate->targetStat.st_mode)) { return serv_directory(connection, connstate); } 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 []\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; } #ifdef HAVE_MAGIC // initialize libmagic magicCookie = magic_open(MAGIC_SYMLINK | MAGIC_MIME_TYPE); if(!magicCookie) { LOG(LVL_ERR, "Cannot allocate magic cookie"); } else { // load the default database if(magic_load(magicCookie, NULL) == -1) { LOG(LVL_ERR, "Cannot load default magic database: %s", magic_error(magicCookie)); magic_close(magicCookie); magicCookie = 0; } else { LOG(LVL_INFO, "libmagic initialized successfully."); } } #endif // 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); #ifdef HAVE_MAGIC magic_close(magicCookie); #endif logger_shutdown(); return EXIT_SUCCESS; }