826 lines
23 KiB
C
826 lines
23 KiB
C
/*
|
|
* vim: sw=2 ts=2 expandtab
|
|
*
|
|
* "THE PIZZA-WARE LICENSE" (derived from "THE BEER-WARE LICENCE"):
|
|
* Thomas Kolb <cfr34k@tkolb.de> 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 <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <signal.h>
|
|
|
|
#include <arpa/inet.h>
|
|
#include <ifaddrs.h>
|
|
#include <netdb.h>
|
|
#include <sys/socket.h>
|
|
|
|
#include <linux/limits.h>
|
|
|
|
#include <microhttpd.h>
|
|
|
|
#ifdef HAVE_MAGIC
|
|
#include <magic.h>
|
|
#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);
|
|
}
|
|
|
|
void get_client_ip_from_connection(struct MHD_Connection *connection,
|
|
struct ConnectionState *connstate) {
|
|
struct sockaddr **ci =
|
|
(struct sockaddr **)MHD_get_connection_info(
|
|
connection, MHD_CONNECTION_INFO_CLIENT_ADDRESS);
|
|
|
|
if(!ci) {
|
|
LOG(LVL_ERR, "Could not determine client address.");
|
|
return;
|
|
}
|
|
|
|
struct sockaddr *sa = *ci;
|
|
|
|
// convert ip address and extract port
|
|
uint16_t port = 0;
|
|
switch(sa->sa_family) {
|
|
case AF_INET:
|
|
inet_ntop(AF_INET, &(((struct sockaddr_in*)sa)->sin_addr), connstate->clientIP, IPSTR_MAXLEN);
|
|
|
|
port = ntohs(((struct sockaddr_in*)sa)->sin_port);
|
|
break;
|
|
|
|
case AF_INET6:
|
|
strcpy(connstate->clientIP, "[");
|
|
inet_ntop(AF_INET6, &(((struct sockaddr_in6*)sa)->sin6_addr), connstate->clientIP+1, IPSTR_MAXLEN-1);
|
|
strcat(connstate->clientIP, "]");
|
|
|
|
port = ntohs(((struct sockaddr_in6*)sa)->sin6_port);
|
|
break;
|
|
|
|
default:
|
|
LOG(LVL_ERR, "Address family is invalid: %i.", sa->sa_family);
|
|
return;
|
|
};
|
|
|
|
strcat(connstate->clientIP, ":");
|
|
|
|
int offset = strlen(connstate->clientIP);
|
|
|
|
sprintf(connstate->clientIP + offset, "%u", port);
|
|
}
|
|
|
|
int parse_range(const char *range, off_t total_len, struct RequestRange *result) {
|
|
char *numstr;
|
|
char *dashptr;
|
|
int dashpos;
|
|
long long 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, "%lli", &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, "%lli", &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, "%lli", &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 %lli-%lli/%lli",
|
|
(long long)range.start,
|
|
(long long)range.start+range.length-1,
|
|
(long long)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
|
|
"<title>%s</title>"
|
|
HEADER2
|
|
"<h1>%s</h1><p>%s</p>"
|
|
"<p><a href=\".\">Back to the directory listing.</a></p>"
|
|
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);
|
|
|
|
// get the remote address as a string
|
|
get_client_ip_from_connection(connection, connstate);
|
|
|
|
LOG(LVL_INFO, "%s - %s %s (local: %s)",
|
|
connstate->clientIP, 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;
|
|
}
|
|
|
|
// serv the favicon, if requested
|
|
if(strcmp(url, "/favicon.png") == 0) {
|
|
LOG(LVL_DEBUG, "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);
|
|
}
|
|
|
|
return MHD_YES;
|
|
} else {
|
|
connstate = *ptr;
|
|
}
|
|
|
|
if(connstate->requestType == GET) {
|
|
if (0 != *upload_data_size) {
|
|
return MHD_NO; // upload data in a GET!?
|
|
}
|
|
|
|
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 = NULL;
|
|
struct MHD_Daemon *d6 = NULL;
|
|
|
|
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] <dir>", 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();
|
|
|
|
// start daemons
|
|
#if MHD_VERSION <= 0x00092100
|
|
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.");
|
|
} else {
|
|
LOG(LVL_DEBUG, "IPv4 daemon startup successful.");
|
|
}
|
|
|
|
unsigned int flags = MHD_USE_THREAD_PER_CONNECTION | MHD_USE_IPv6;
|
|
#else
|
|
unsigned int flags = MHD_USE_THREAD_PER_CONNECTION | MHD_USE_DUAL_STACK;
|
|
#endif // MHD_VERSION
|
|
|
|
d6 = MHD_start_daemon(flags,
|
|
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.");
|
|
} else {
|
|
LOG(LVL_DEBUG, "IPv6/Dualstack daemon startup successful.");
|
|
}
|
|
|
|
if(d == NULL && d6 == NULL) {
|
|
LOG(LVL_FATAL, "Both IPv4 and IPv6/Dualstack 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...");
|
|
|
|
#if MHD_VERSION <= 0x00092100
|
|
MHD_stop_daemon(d);
|
|
#endif // MHD_VERSION
|
|
|
|
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;
|
|
}
|