/* * 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 #include #ifdef HAVE_MAGIC #include #endif #include "logger.h" #include "templates.h" #include "dirlisting.h" #include "favicon.h" #include "util.h" #include "main.h" #define DEFAULT_PORT 8888 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; int uploadEnabled = 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); // close the upload file if open if(connstate->upload_fd) { fclose(connstate->upload_fd); } if(connstate->postProcessor) { MHD_destroy_post_processor(connstate->postProcessor); } 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, uploadEnabled); 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; } int serv_result_page(struct MHD_Connection *connection, struct ConnectionState *connstate) { struct MHD_Response *response; int ret; char *message, *title; unsigned int resultCode = MHD_HTTP_OK; switch(connstate->result) { case RC_OK: message = "Request was processed successfully."; title = "Success"; resultCode = MHD_HTTP_OK; break; case RC_EXISTS: message = "The file you tried to upload already exists on the server."; title = "File already exists"; resultCode = MHD_HTTP_INTERNAL_SERVER_ERROR; break; case RC_OPEN_FAILED: message = "Could not open the file on the server for writing."; title = "Open failed"; resultCode = MHD_HTTP_INTERNAL_SERVER_ERROR; break; case RC_WRONG_TARGET: message = "You tried to put a new file into something which is not a directory."; title = "Target error"; resultCode = MHD_HTTP_INTERNAL_SERVER_ERROR; break; case RC_WRITE_FAILED: message = "A write error occurred while storing uploaded data to the file."; title = "Write error"; resultCode = MHD_HTTP_INTERNAL_SERVER_ERROR; break; } char *buffer = malloc(strlen(HEADER1 HEADER2 FOOTER) + 1024); sprintf(buffer, HEADER1 "%s" HEADER2 "

%s

%s

" "

Back to the directory listing.

" FOOTER, title, title, message); response = MHD_create_response_from_buffer( strlen(buffer), buffer, MHD_RESPMEM_MUST_FREE); MHD_add_response_header(response, MHD_HTTP_HEADER_CONTENT_TYPE, "text/html"); ret = MHD_queue_response(connection, resultCode, response); MHD_destroy_response(response); return ret; } static int post_processor(void *cls, enum MHD_ValueKind kind, const char *key, const char *filename, const char *content_type, const char *transfer_encoding, const char *data, uint64_t off, size_t size) { LOG(LVL_DUMP, "Entering POST handler (key=%s, filename=%s, contentType=%s, transferEncoding=%s, size=%u)", key, filename, content_type, transfer_encoding, size); struct ConnectionState *ci = (struct ConnectionState*)cls; if(kind == MHD_POSTDATA_KIND) { if(strcmp("data", key) == 0) { if(ci->upload_fd == NULL) { // file pointer not allocated yet // check if we are storing the new file into a directory if(!S_ISDIR(ci->targetStat.st_mode)) { LOG(LVL_WARN, "Cannot upload %s: Target path is not an existing directory.", filename); ci->result = RC_WRONG_TARGET; return MHD_NO; } // determine the path where to store the uploaded file strncpy(ci->uploadFilename, ci->localFileName, PATH_MAX); strcat(ci->uploadFilename, "/"); strncat(ci->uploadFilename, filename, PATH_MAX-strlen(ci->uploadFilename)); // check if the file already exists FILE *tmp = fopen(ci->uploadFilename, "rb"); if(tmp) { LOG(LVL_WARN, "Cannot upload to %s: file exists.", ci->uploadFilename); ci->result = RC_EXISTS; fclose(tmp); return MHD_NO; } // file does not exist and can therefore be opened for writing ci->upload_fd = fopen(ci->uploadFilename, "wb"); if(!ci->upload_fd) { LOG(LVL_WARN, "Cannot upload to %s: file cannot be created.", ci->uploadFilename); ci->result = RC_OPEN_FAILED; return MHD_NO; } if(0 == fwrite(data, size, 1, ci->upload_fd)) { LOG(LVL_WARN, "Cannot upload to %s: write operation failed.", ci->uploadFilename); ci->result = RC_WRITE_FAILED; return MHD_NO; } LOG(LVL_WARN, "Upload to %s started successfully.", ci->uploadFilename); } else { // file descriptor is already open if(0 == fwrite(data, size, 1, ci->upload_fd)) { LOG(LVL_WARN, "Cannot upload to %s: write operation failed.", ci->uploadFilename); ci->result = RC_WRITE_FAILED; return MHD_NO; } } } return MHD_YES; } return MHD_NO; } int key_value_iterator(void *cls, enum MHD_ValueKind kind, const char *key, const char *value) { struct ConnectionState *connstate = (struct ConnectionState*)cls; if(kind == MHD_GET_ARGUMENT_KIND) { if(strcmp(key, "upload") == 0) { connstate->uploadRequest = 1; return MHD_NO; // this is the only argument we search for } return MHD_YES; } else { return MHD_NO; } } 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 (*ptr == NULL) { // This is the first time this connection is seen. // Initialize the connection state connstate = malloc(sizeof(struct ConnectionState)); *ptr = connstate; connstate->result = RC_OK; connstate->upload_fd = NULL; connstate->uploadRequest = 0; // 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); if (0 == strcmp(method, "GET") || 0 == strcmp(method, "HEAD")) { // process GET arguments MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, key_value_iterator, connstate); connstate->postProcessor = NULL; connstate->requestType = GET; } else if(0 == strcmp(method, "POST")) { if(!uploadEnabled) { LOG(LVL_WARN, "Uploads are disabled, so POST requests are denied.", connstate->localFileName); return MHD_queue_response(connection, MHD_HTTP_FORBIDDEN, error403Response); } else { connstate->postProcessor = MHD_create_post_processor(connection, 1024*1024, post_processor, connstate); connstate->requestType = POST; } } else { LOG(LVL_WARN, "Unexpected method: %s.", method); return MHD_NO; } // 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); } return MHD_YES; } else { connstate = *ptr; } if(connstate->requestType == GET) { 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); } 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); } } else if(connstate->requestType == POST) { if (*upload_data_size != 0) { MHD_post_process (connstate->postProcessor, upload_data, *upload_data_size); *upload_data_size = 0; return MHD_YES; } else { return serv_result_page(connection, connstate); } } // 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 parse_cmdline(int argc, char **argv, int *port, int *enableUpload, char **shareRoot) { int c; *enableUpload = 0; *port = DEFAULT_PORT; while ((c = getopt (argc, argv, "up:")) != -1) { switch (c) { case 'u': *enableUpload = 1; break; case 'p': // a port was given on the command line, try to parse it if(sscanf(optarg, "%i", port) != 1) { LOG(LVL_FATAL, "%s is not a valid port number!", optarg); return 1; } if(*port < 1 || *port > 65535) { LOG(LVL_FATAL, "Port %i is out of range [1-65535]!", *port); return 1; } break; case '?': if (optopt == 'p') { LOG(LVL_ERR, "Option -%c requires an argument.\n", optopt); } else if (isprint (optopt)) { LOG(LVL_ERR, "Unknown option `-%c'.\n", optopt); } else { LOG(LVL_ERR, "Unknown option character `\\x%x'.\n", optopt); } return 1; default: return 1; } } if(optind < argc) { // found non-option argument -> use as shared dir *shareRoot = argv[optind]; return 0; } return 1; } int main(int argc, char ** argv) { struct MHD_Daemon *d, *d6; struct stat sBuf; int port; logger_init(); LOG(LVL_INFO, "Welcome to fileshare " VERSION); #ifndef DEBUG // don't show debug output in release build logger_set_verbosity(LVL_INFO); #endif // parse command line arguments if(parse_cmdline(argc, argv, &port, &uploadEnabled, &shareRoot)) { LOG(LVL_ERR, "Failed to parse command line!"); LOG(LVL_INFO, "Usage: %s [arguments] ", argv[0]); LOG(LVL_INFO, ""); LOG(LVL_INFO, "Arguments:"); LOG(LVL_INFO, ""); LOG(LVL_INFO, "\t-u Enable Uploads"); LOG(LVL_INFO, "\t-p port Change the listening port."); LOG(LVL_INFO, ""); return 1; } if(uploadEnabled) { LOG(LVL_WARN, "Uploads are enabled. Users can create new files anywhere in %s !", shareRoot); } // check if shareRoot is an existing directory 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); LOG(LVL_INFO, "Using port: %i", 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; }