#include #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; }; struct RequestRange { off_t start; off_t length; }; 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 parse_range(const char *range, off_t total_len, struct RequestRange *result) { char *numstr; char *dashptr; int dashpos; off_t num; LOG(LVL_DEBUG, "Requested Range is %s", range); if(range == NULL) { result->start = 0; result->length = total_len; return 0; } numstr = strchr(range, '='); if(numstr == NULL) { return -1; } numstr += 1; LOG(LVL_DUMP, "Numeric part of range: %s", numstr); dashptr = strchr(numstr, '-'); if(dashptr == NULL) { // no '-' found return -1; } dashpos = dashptr - numstr; LOG(LVL_DUMP, "Dash found at position: %i", dashpos); if(dashpos == 0) { // example: bytes=-500 // -> letzte 500 bytes ausgeben if(sscanf(dashptr+1, "%li", &num) != 1) { return -1; } // total = 1000, num = 100 -> start=500, length=500 result->start = total_len - num - 1; result->length = num; } else { // examples: bytes=100-200; bytes=300- // parse the first number if(sscanf(numstr, "%li", &num) != 1) { return -1; } result->start = num; if(numstr[dashpos + 1] == '\0') { // everything from num to the end result->length = total_len - num; return 0; } // a complete range was given -> parse the second number if(sscanf(numstr+dashpos+1, "%li", &num) != 1) { return -1; } if(num < result->start) { return -1; } result->length = num - result->start + 1; } return 0; } int serv_regular_file(struct MHD_Connection *connection, struct ConnectionState *connstate) { struct MHD_Response *response; struct RequestRange range; const char *rangestr; char buf[256]; int ret; LOG(LVL_DEBUG, "Serving %s as a regular file.", connstate->localFileName); rangestr = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range"); if(0 != parse_range(rangestr, connstate->targetStat.st_size, &range)) { LOG(LVL_ERR, "Cannot parse range header."); return MHD_queue_response(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, error500Response); } LOG(LVL_DUMP, "Requested range: start=%lu, length=%lu", range.start, range.length); // 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_at_offset( range.length, fd, range.start); // build content range header sprintf(buf, "bytes %li-%li/%li", range.start, range.start+range.length-1, connstate->targetStat.st_size); MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_RANGE, buf); LOG(LVL_DUMP, "Content-Range: %s", buf); #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); } // signal handler for SIGTERM, SIGINT, etc. // sets the flag for a clean shutdown void sig_shutdown_handler(int sig) { LOG(LVL_DEBUG, "Handling signal: %i", sig); running = 0; } void init_signal_handlers(void) { struct sigaction sa; sa.sa_handler = sig_shutdown_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sa.sa_restorer = NULL; if(sigaction(SIGTERM, &sa, NULL) == -1) { LOG(LVL_ERR, "sigaction [SIGTERM] failed: %s", strerror(errno)); } if(sigaction(SIGINT, &sa, NULL) == -1) { LOG(LVL_ERR, "sigaction [SIGINT] failed: %s", strerror(errno)); } sa.sa_handler = SIG_IGN; if(sigaction(SIGPIPE, &sa, NULL) == -1) { LOG(LVL_ERR, "sigaction [SIGPIPE] failed: %s", strerror(errno)); } } 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 // setup the signal handlers init_signal_handlers(); // 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) { sleep(60); } 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 LOG(LVL_INFO, "Thanks for using fileshare."); logger_shutdown(); return EXIT_SUCCESS; }