/* * vim: sw=2 ts=2 expandtab * * "THE PIZZA-WARE LICENSE" (derived from "THE BEER-WARE LICENCE"): * Thomas Kolb wrote this file. As long as you retain this * notice you can do whatever you want with this stuff. If we meet some day, * and you think this stuff is worth it, you can buy me a pizza in return. * - Thomas Kolb */ #include #include #include #include #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 "favicon.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; struct MHD_Response *faviconResponse; #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, "%s %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!? } // serv the favicon, if requested if(strcmp(url, "/favicon.png") == 0) { LOG(LVL_INFO, "Serving FavIcon request."); return MHD_queue_response(connection, MHD_HTTP_OK, faviconResponse); } // 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)); } } void print_urls(int port) { struct ifaddrs *ifaddr, *ifa; int family, s; char host[INET6_ADDRSTRLEN]; if(getifaddrs(&ifaddr) == -1) { LOG(LVL_ERR, "getifaddrs failed: %s", strerror(errno)); return; } for(ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { if(ifa->ifa_addr == NULL) { continue; } family = ifa->ifa_addr->sa_family; if(family == AF_INET || family == AF_INET6) { s = getnameinfo(ifa->ifa_addr, (family == AF_INET) ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6), host, INET6_ADDRSTRLEN, NULL, 0, NI_NUMERICHOST); if(s != 0) { LOG(LVL_ERR, "getnameinfo failed: %s", gai_strerror(s)); continue; } if(family == AF_INET) { LOG(LVL_INFO, "%-8s: http://%s:%i", ifa->ifa_name, host, port); } else { // IPv6 LOG(LVL_INFO, "%-8s: http://[%s]:%i", ifa->ifa_name, host, port); } } } freeifaddrs(ifaddr); } int main(int argc, char ** argv) { struct MHD_Daemon *d, *d6; struct stat sBuf; int port; logger_init(); #ifndef DEBUG // don't show debug output in release build logger_set_verbosity(LVL_INFO); #endif 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"); // static response for favicon faviconResponse = create_favicon_response(); 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_ERR, "Cannot start up libmicrohttpd listening on IPv4."); } d6 = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION | MHD_USE_IPv6, port, NULL, NULL, &connection_handler, NULL, MHD_OPTION_NOTIFY_COMPLETED, &request_completed, NULL, MHD_OPTION_END); if (d6 == NULL) { LOG(LVL_ERR, "Cannot start up libmicrohttpd listening on IPv6."); } if(d == NULL && d6 == NULL) { LOG(LVL_FATAL, "Both IPv4 and IPv6 servers failed to start. Terminating."); return EXIT_FAILURE; } LOG(LVL_INFO, "Startup successful. Here are the IP addresses for this computer:"); print_urls(port); while(running) { sleep(60); } LOG(LVL_INFO, "Shutting down..."); MHD_stop_daemon(d); MHD_stop_daemon(d6); MHD_destroy_response(error403Response); MHD_destroy_response(error404Response); MHD_destroy_response(error500Response); MHD_destroy_response(faviconResponse); #ifdef HAVE_MAGIC magic_close(magicCookie); #endif LOG(LVL_INFO, "Thanks for using fileshare."); logger_shutdown(); return EXIT_SUCCESS; }