Add http file transfer capability to core and http plugin
authorHenning Rogge <henning.rogge@fkie.fraunhofer.de>
Tue, 23 Jun 2015 08:07:02 +0000 (10:07 +0200)
committerHenning Rogge <henning.rogge@fkie.fraunhofer.de>
Tue, 23 Jun 2015 08:07:02 +0000 (10:07 +0200)
src-plugins/subsystems/oonf_http.c
src-plugins/subsystems/oonf_http.h
src-plugins/subsystems/oonf_stream_socket.c
src-plugins/subsystems/oonf_stream_socket.h
src-plugins/subsystems/os_linux/os_socket_linux.h
src-plugins/subsystems/os_socket.h

index a5dfb2d..2f0d441 100644 (file)
  *
  */
 
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <fcntl.h>
+
 #include "common/avl.h"
 #include "common/avl_comp.h"
 #include "common/common_types.h"
 /* Definitions */
 #define LOG_HTTP _oonf_http_subsystem.logging
 
+struct _http_config {
+  struct oonf_stream_managed_config smc;
+  char *www_dir;
+  int www_dir_fd;
+};
 /* HTTP text constants */
 static const char HTTP_VERSION_1_0[] = "HTTP/1.0";
 static const char HTTP_VERSION_1_1[] = "HTTP/1.1";
@@ -68,6 +78,7 @@ static const char HTTP_GET[] = "GET";
 static const char HTTP_POST[] = "POST";
 
 static const char HTTP_CONTENT_LENGTH[] = "Content-Length";
+static const char HTTP_CONTENT_TYPE[] = "Content-Type";
 
 static const char HTTP_RESPONSE_200[] = "OK";
 static const char HTTP_RESPONSE_400[] = "Bad Request";
@@ -81,6 +92,7 @@ static const char HTTP_RESPONSE_503[] = "Service Unavailable";
 
 /* http to telnet bridge path */
 static const char HTTP_TO_TELNET[] = "/telnet/";
+static const char HTTP_FILES[] = "/www/";
 
 /* prototypes */
 static int _init(void);
@@ -91,6 +103,8 @@ static enum oonf_stream_session_state _cb_receive_data(
     struct oonf_stream_session *session);
 static void _cb_create_error(struct oonf_stream_session *session,
     enum oonf_stream_errors error);
+static void _cb_cleanup_session(struct oonf_stream_session *);
+
 static bool _auth_okay(struct oonf_http_handler *handler,
     struct oonf_http_session *session);
 static void _create_http_error(struct oonf_stream_session *session,
@@ -98,7 +112,8 @@ static void _create_http_error(struct oonf_stream_session *session,
 static struct oonf_http_handler *_get_site_handler(const char *uri);
 static const char *_get_headertype_string(enum oonf_http_result type);
 static void _create_http_header(struct oonf_stream_session *session,
-    enum oonf_http_result code, const char *content_type);
+    enum oonf_http_result code, const char *content_type,
+    size_t content_length);
 static int _parse_http_header(char *header_data, size_t header_len,
     struct oonf_http_session *header);
 static size_t _parse_query_string(char *s,
@@ -106,15 +121,20 @@ static size_t _parse_query_string(char *s,
 static void  _decode_uri(char *src);
 static enum oonf_http_result _cb_telnet_handler(
       struct autobuf *out, struct oonf_http_session *);
+static enum oonf_http_result _cb_file_handler(
+      struct autobuf *out, struct oonf_http_session *);
 
 /* configuration variables */
 static struct cfg_schema_entry _http_entries[] = {
-  CFG_MAP_ACL_V46(oonf_stream_managed_config,
-      acl, "acl", ACL_DEFAULT_ACCEPT, "Access control list for http interface"),
-  CFG_MAP_ACL_V46(oonf_stream_managed_config,
-      bindto, "bindto", "127.0.0.1\0" "::1\0" ACL_DEFAULT_REJECT, "Bind http socket to this address"),
-  CFG_MAP_INT32_MINMAX(oonf_stream_managed_config,
-      port, "port", "1980", "Network port for http interface", 0, false, 1, 65535),
+  CFG_MAP_ACL_V46(_http_config, smc.acl,
+      "acl", ACL_DEFAULT_ACCEPT, "Access control list for http interface"),
+  CFG_MAP_ACL_V46(_http_config, smc.bindto,
+      "bindto", "127.0.0.1\0" "::1\0" ACL_DEFAULT_REJECT, "Bind http socket to this address"),
+  CFG_MAP_INT32_MINMAX(_http_config, smc.port,
+      "port", "1980", "Network port for http interface", 0, false, 1, 65535),
+  CFG_MAP_STRING(_http_config, www_dir, "webserver", "",
+      "Path to map into the /www subdirectory of the HTTP server, empty path"
+      " feature will be disabled"),
 };
 
 static struct cfg_schema_section _http_section = {
@@ -137,10 +157,13 @@ static struct oonf_stream_managed _http_managed_socket = {
     .allowed_sessions = 3,
     .receive_data = _cb_receive_data,
     .create_error = _cb_create_error,
+    .cleanup = _cb_cleanup_session,
   },
 };
 
-/* integrated telnet handler */
+struct _http_config _config;
+
+/* integrated telnet/file handler */
 struct oonf_http_handler _telnet_handler = {
   .site = HTTP_TO_TELNET,
   .content_handler = _cb_telnet_handler,
@@ -149,6 +172,14 @@ struct oonf_http_handler _telnet_handler = {
   },
 };
 
+struct oonf_http_handler _file_handler = {
+  .site = HTTP_FILES,
+  .content_handler = _cb_file_handler,
+  .acl = {
+      .accept_default = true,
+  },
+};
+
 /* subsystem definition */
 static const char *_dependencies[] = {
   OONF_STREAM_SUBSYSTEM,
@@ -175,6 +206,9 @@ _init(void) {
   avl_init(&_http_site_tree, avl_comp_strcasecmp, false);
 
   oonf_http_add(&_telnet_handler);
+  oonf_http_add(&_file_handler);
+
+  _config.www_dir_fd = -1;
   return 0;
 }
 
@@ -183,7 +217,12 @@ _init(void) {
  */
 void
 _cleanup(void) {
+  free (_config.www_dir);
+  if (_config.www_dir_fd != -1) {
+    close(_config.www_dir_fd);
+  }
   oonf_http_remove(&_telnet_handler);
+  oonf_http_remove(&_file_handler);
   oonf_stream_remove_managed(&_http_managed_socket, true);
 }
 
@@ -235,32 +274,6 @@ oonf_http_lookup_value(char **keys, char **values, size_t count, const char *key
   return NULL;
 }
 
-/**
- * Callback for configuration changes
- */
-static void
-_cb_config_changed(void) {
-  struct oonf_stream_managed_config config;
-
-  /* generate binary config */
-  memset(&config, 0, sizeof(config));
-  if (cfg_schema_tobin(&config, _http_section.post,
-      _http_entries, ARRAYSIZE(_http_entries))) {
-    /* error in conversion */
-    OONF_WARN(LOG_HTTP, "Cannot map http config to binary data");
-    goto apply_config_failed;
-  }
-
-  if (oonf_stream_apply_managed(&_http_managed_socket, &config)) {
-    /* error while updating sockets */
-    goto apply_config_failed;
-  }
-
-  /* fall through */
-apply_config_failed:
-  oonf_stream_free_managed_config(&config);
-}
-
 /**
  * Callback for incoming http data
  * @param session pointer to tcp session
@@ -365,10 +378,12 @@ _cb_receive_data(struct oonf_stream_session *session) {
   }
 
   if (handler->content) {
+    /* static content */
     abuf_memcpy(&session->out, handler->content, handler->content_size);
-    _create_http_header(session, HTTP_200_OK, NULL);
+    _create_http_header(session, HTTP_200_OK, NULL, abuf_getlen(&session->out));
   }
   else {
+    /* custom handler */
     enum oonf_http_result result;
     /* check acl */
     if (!netaddr_acl_check_accept(&handler->acl, &session->remote_address)) {
@@ -391,17 +406,37 @@ _cb_receive_data(struct oonf_stream_session *session) {
       result = HTTP_500_INTERNAL_SERVER_ERROR;
     }
 
-    if (result != HTTP_200_OK) {
+    if (result == HTTP_START_FILE_TRANSFER) {
+      session->copy_fd = header.transfer_fd;
+      session->copy_total_size = header.transfer_length;
+      session->copy_bytes_sent = 0;
+
+      _create_http_header(session, HTTP_200_OK,
+          header.content_type, header.transfer_length);
+    }
+    else if (result != HTTP_200_OK) {
       /* create error message */
       _create_http_error(session, result);
     }
     else {
-      _create_http_header(session, HTTP_200_OK, header.content_type);
+      _create_http_header(session, HTTP_200_OK,
+          header.content_type, abuf_getlen(&session->out));
     }
   }
   return STREAM_SESSION_SEND_AND_QUIT;
 }
 
+/**
+ * Close file transfer descriptor during cleanup
+ * @param session
+ */
+static void
+_cb_cleanup_session(struct oonf_stream_session *session) {
+  if (session->copy_fd != -1) {
+    close(session->copy_fd);
+  }
+}
+
 /**
  * Check if an incoming session is authorized to view a http site.
  * @param handler pointer to site handler
@@ -455,7 +490,7 @@ _create_http_error(struct oonf_stream_session *session,
       "<body><h1>HTTP error %d: %s</h1></body></html>",
       oonf_log_get_appdata()->app_name, oonf_log_get_libdata()->version,
       error, _get_headertype_string(error));
-  _create_http_header(session, error, NULL);
+  _create_http_header(session, error, NULL, abuf_getlen(&session->out));
 }
 
 /**
@@ -536,11 +571,12 @@ _get_headertype_string(enum oonf_http_result type) {
  * @param session pointer to tcp session
  * @param code http result code
  * @param content_type explicit content type or NULL for
+ * @param content_length length of content, 0 for no content length
  *   plain html
  */
 static void
 _create_http_header(struct oonf_stream_session *session,
-    enum oonf_http_result code, const char *content_type) {
+    enum oonf_http_result code, const char *content_type, size_t content_length) {
   struct autobuf buf;
   struct timeval currtime;
 
@@ -563,11 +599,11 @@ _create_http_header(struct oonf_stream_session *session,
   if (content_type == NULL) {
     content_type = HTTP_CONTENTTYPE_HTML;
   }
-  abuf_appendf(&buf, "Content-type: %s\r\n", content_type);
+  abuf_appendf(&buf, "%s: %s\r\n", HTTP_CONTENT_TYPE, content_type);
 
   /* Content length */
-  if (abuf_getlen(&session->out) > 0) {
-    abuf_appendf(&buf, "Content-length: %zu\r\n", abuf_getlen(&session->out));
+  if (content_length > 0) {
+    abuf_appendf(&buf, "Content-length: %zu\r\n", content_length);
   }
 
   if (code == HTTP_401_UNAUTHORIZED) {
@@ -824,6 +860,12 @@ _decode_uri(char *src) {
   *dst = 0;
 }
 
+/**
+ * Http to Telnet bridge
+ * @param out output stream
+ * @param session http session
+ * @return http result calculated from telnet result
+ */
 static enum oonf_http_result
 _cb_telnet_handler(struct autobuf *out, struct oonf_http_session *session) {
   static char EOL = 0;
@@ -870,3 +912,86 @@ _cb_telnet_handler(struct autobuf *out, struct oonf_http_session *session) {
   }
   return HTTP_200_OK;
 }
+
+/**
+ * Http File transfer handler
+ * @param out output stream
+ * @param session http session
+ * @return http result
+ */
+static enum oonf_http_result
+_cb_file_handler(struct autobuf *out __attribute__((unused)),
+    struct oonf_http_session *session) {
+  const char *file;
+  struct stat st;
+  int fd;
+
+  if (_config.www_dir_fd == -1) {
+    /* file server not active */
+    return HTTP_404_NOT_FOUND;
+  }
+
+  if (strstr(session->decoded_request_uri, "/../")) {
+    /* directory traversal is not allowed */
+    OONF_INFO(LOG_HTTP, "Blocked directory traversal '%s' uri",
+        session->decoded_request_uri);
+    return HTTP_404_NOT_FOUND;
+  }
+
+  file = &session->decoded_request_uri[sizeof(HTTP_FILES)-1];
+  if (file[0] == '/') {
+    /* directory traversal is not allowed */
+    OONF_INFO(LOG_HTTP, "Blocked directory traversal '%s' uri",
+        session->decoded_request_uri);
+    return HTTP_404_NOT_FOUND;
+  }
+
+  fd = openat(_config.www_dir_fd, file, O_NONBLOCK, O_RDONLY);
+  if (fd == -1) {
+    OONF_INFO(LOG_HTTP, "Could not open file '%s': %s (%d)",
+        file, strerror(errno), errno);
+    return HTTP_404_NOT_FOUND;
+  }
+
+  if (fstat(fd, &st)) {
+    OONF_WARN(LOG_HTTP, "Could not get file statistics of '%s': %s (%d)",
+        file, strerror(errno), errno);
+    return HTTP_404_NOT_FOUND;
+  }
+
+  /* initialize file transfer */
+  session->content_type = oonf_http_lookup_header(session, HTTP_CONTENT_TYPE);
+  session->transfer_fd = fd;
+  session->transfer_length = st.st_size;
+
+  /* start file transfer */
+  return HTTP_START_FILE_TRANSFER;
+}
+
+/**
+ * Callback for configuration changes
+ */
+static void
+_cb_config_changed(void) {
+  /* generate binary config */
+  if (cfg_schema_tobin(&_config, _http_section.post,
+      _http_entries, ARRAYSIZE(_http_entries))) {
+    /* error in conversion */
+    OONF_WARN(LOG_HTTP, "Cannot map http config to binary data");
+    return;
+  }
+
+  oonf_stream_apply_managed(&_http_managed_socket, &_config.smc);
+
+  if (_config.www_dir_fd != -1) {
+    close(_config.www_dir_fd);
+    _config.www_dir_fd = -1;
+  }
+  if (_config.www_dir && _config.www_dir[0]) {
+    _config.www_dir_fd = open(_config.www_dir, O_DIRECTORY, O_RDONLY);
+    if (_config.www_dir_fd == -1) {
+      OONF_WARN(LOG_HTTP, "Could not open file directory '%s': %s (%d)",
+          _config.www_dir, strerror(errno), errno);
+    }
+  }
+}
index 31832d9..24e8d06 100644 (file)
@@ -71,6 +71,9 @@ enum oonf_http_result {
   HTTP_500_INTERNAL_SERVER_ERROR = 500,
   HTTP_501_NOT_IMPLEMENTED = 501,
   HTTP_503_SERVICE_UNAVAILABLE = STREAM_SERVICE_UNAVAILABLE,
+
+  /* special result to signal start of file transfer */
+  HTTP_START_FILE_TRANSFER = 99999,
 };
 
 struct oonf_http_session {
@@ -93,6 +96,10 @@ struct oonf_http_session {
 
   /* content type for answer, NULL means plain/html */
   const char *content_type;
+
+  /* parameters for file transfer */
+  int transfer_fd;
+  size_t transfer_length;
 };
 
 struct oonf_http_handler {
index b443f47..66a4989 100644 (file)
@@ -665,6 +665,8 @@ _create_session(struct oonf_stream_socket *stream_socket,
   session->remote_address = *remote_addr;
   session->remote_socket = *remote_socket;
 
+  session->copy_fd = -1;
+
   if (stream_socket->config.allowed_sessions-- > 0) {
     /* create active session */
     session->state = STREAM_SESSION_ACTIVE;
@@ -821,6 +823,23 @@ _cb_parse_connection(int fd, void *data, bool event_read, bool event_write) {
     }
   }
 
+  /* send file if necessary */
+  if (session->state == STREAM_SESSION_SEND_AND_QUIT
+      && abuf_getlen(&session->out) == 0 && session->copy_fd != 0) {
+    if (event_write) {
+      len = os_socket_sendfile(fd, session->copy_fd, session->copy_bytes_sent,
+          session->copy_total_size - session->copy_bytes_sent);
+      if (len <= 0) {
+        OONF_WARN(LOG_STREAM, "Error while copying file to output stream: %s (%d)",
+            strerror(errno), errno);
+        session->state = STREAM_SESSION_CLEANUP;
+      }
+      else {
+        session->copy_bytes_sent += len;
+      }
+    }
+  }
+
   /* check for buffer underrun */
   if (session->state == STREAM_SESSION_ACTIVE
       && abuf_getlen(&session->out) == 0
@@ -828,7 +847,8 @@ _cb_parse_connection(int fd, void *data, bool event_read, bool event_write) {
     session->state = s_sock->config.buffer_underrun(session);
   }
 
-  if (abuf_getlen(&session->out) == 0) {
+  if (abuf_getlen(&session->out) == 0 &&
+      session->copy_bytes_sent == session->copy_total_size) {
     /* nothing to send anymore */
     OONF_DEBUG(LOG_STREAM, "  deactivating output in scheduler\n");
     oonf_socket_set_write(&session->scheduler_entry, false);
index e8a5b35..d7b5532 100644 (file)
@@ -86,6 +86,16 @@ struct oonf_stream_session {
    * soon as possible */
   struct autobuf out;
 
+  /*
+   * file input descriptor
+   *
+   * will only be used in SEND_AND_QUIT state if out buffer is empty
+   */
+  int copy_fd;
+
+  /* number of bytes already copied and total to be copied */
+  size_t copy_bytes_sent, copy_total_size;
+
   /*
    * internal part of the server
    */
index ba2383d..a056ee7 100644 (file)
@@ -45,6 +45,7 @@
 #include <sys/select.h>
 #include <unistd.h>
 #include <sys/types.h>
+#include <sys/sendfile.h>
 #include <sys/socket.h>
 
 #include "../os_socket.h"
@@ -164,4 +165,20 @@ os_socket_get_loopback_name(void) {
   return "lo";
 }
 
+/**
+ * send data from one filedescriptor to another one. Linux compatible API
+ * structure, might need a bit of work for other OS.
+ * @param outfd
+ * @param infd
+ * @param offset
+ * @param count
+ * @return -1 if an error happened, otherwise the number of bytes that
+ *   were sent to outfd
+ */
+static INLINE ssize_t
+os_socket_sendfile(int outfd, int infd, size_t offset, size_t count) {
+  off_t int_offset = offset;
+  return sendfile(outfd, infd, &int_offset, count);
+}
+
 #endif /* OS_SOCKET_LINUX_H_ */
index d169ed5..9849efb 100644 (file)
@@ -69,6 +69,7 @@ static INLINE ssize_t os_socket_sendto(int fd, const void *buf, size_t length,
 static INLINE ssize_t os_socket_recvfrom(int fd, void *buf, size_t length,
     union netaddr_socket *source, const struct os_interface_data *interf);
 static INLINE const char *os_socket_get_loopback_name(void);
+static INLINE ssize_t os_socket_sendfile(int outfd, int infd, size_t offset, size_t count);
 
 /* include os-specific headers */
 #if defined(__linux__)