diff --git a/spectrum_manager/src/server.cpp b/spectrum_manager/src/server.cpp new file mode 100644 index 0000000000000000000000000000000000000000..94fb17e8408f43219f23961c646e4112e3d02a97 --- /dev/null +++ b/spectrum_manager/src/server.cpp @@ -0,0 +1,365 @@ +#include "server.h" +#include "methods.h" + +#include +#include +#include +#include +#include +#include +#include + +#define SESSION_TTL 120 + +static std::string get_header() { +return "\ + \ + \ + \ + Spectrum 2 web interface\ + \ + \ +

Spectrum 2 web interface

"; +} + + +static void get_qsvar(const struct mg_request_info *request_info, + const char *name, char *dst, size_t dst_len) { + const char *qs = request_info->query_string; + mg_get_var(qs, strlen(qs == NULL ? "" : qs), name, dst, dst_len); +} + +static void my_strlcpy(char *dst, const char *src, size_t len) { + strncpy(dst, src, len); + dst[len - 1] = '\0'; +} + +// Generate session ID. buf must be 33 bytes in size. +// Note that it is easy to steal session cookies by sniffing traffic. +// This is why all communication must be SSL-ed. +static void generate_session_id(char *buf, const char *random, + const char *user) { + mg_md5(buf, random, user, NULL); +} + +Server::Server(ManagerConfig *config) { + srand((unsigned) time(0)); + m_config = config; +} + +Server::~Server() { + mg_stop(ctx); +} + + +static void *_event_handler(enum mg_event event, struct mg_connection *conn) { + const struct mg_request_info *request_info = mg_get_request_info(conn); + return static_cast(request_info->user_data)->event_handler(event, conn); +} + +bool Server::start(int port, const std::string &user, const std::string &password) { + m_user = user; + m_password = password; + const char *options[] = { + "listening_ports", boost::lexical_cast(port).c_str(), + "num_threads", "1", + NULL + }; + + // Setup and start Mongoose + if ((ctx = mg_start(&_event_handler, this, options)) == NULL) { + return false; + } + + return true; +} + +bool Server::check_password(const char *user, const char *password) { + return (m_user == user && m_password == password); +} + +// Allocate new session object +Server::session *Server::new_session(const char *user) { + Server::session *session = new Server::session; + + my_strlcpy(session->user, user, sizeof(session->user)); + snprintf(session->random, sizeof(session->random), "%d", rand()); + generate_session_id(session->session_id, session->random, session->user); + session->expire = time(0) + SESSION_TTL; + + sessions[session->session_id] = session; + return session; +} + +// Get session object for the connection. Caller must hold the lock. +Server::session *Server::get_session(const struct mg_connection *conn) { + time_t now = time(NULL); + char session_id[33]; + mg_get_cookie(conn, "session", session_id, sizeof(session_id)); + + if (sessions.find(session_id) == sessions.end()) { + return NULL; + } + + if (sessions[session_id]->expire != 0 && sessions[session_id]->expire > now) { + return sessions[session_id]; + } + + return NULL; +} + +void Server::authorize(struct mg_connection *conn, const struct mg_request_info *request_info) { + char user[255], password[255]; + Server::session *session; + + // Fetch user name and password. + get_qsvar(request_info, "user", user, sizeof(user)); + get_qsvar(request_info, "password", password, sizeof(password)); + + if (check_password(user, password) && (session = new_session(user)) != NULL) { + std::cout << "User authorized\n"; + // Authentication success: + // 1. create new session + // 2. set session ID token in the cookie + // 3. remove original_url from the cookie - not needed anymore + // 4. redirect client back to the original URL + // + // The most secure way is to stay HTTPS all the time. However, just to + // show the technique, we redirect to HTTP after the successful + // authentication. The danger of doing this is that session cookie can + // be stolen and an attacker may impersonate the user. + // Secure application must use HTTPS all the time. + mg_printf(conn, "HTTP/1.1 302 Found\r\n" + "Set-Cookie: session=%s; max-age=3600; http-only\r\n" // Session ID + "Set-Cookie: user=%s\r\n" // Set user, needed by Javascript code + "Set-Cookie: original_url=/; max-age=0\r\n" // Delete original_url + "Location: /\r\n\r\n", + session->session_id, session->user); + } else { + // Authentication failure, redirect to login. + redirect_to(conn, request_info, "/login"); + } +} + +bool Server::is_authorized(const struct mg_connection *conn, const struct mg_request_info *request_info) { + Server::session *session; + char valid_id[33]; + bool authorized = false; + + // Always authorize accesses to login page and to authorize URI + if (!strcmp(request_info->uri, "/login") || + !strcmp(request_info->uri, "/authorize")) { + return true; + } + +// pthread_rwlock_rdlock(&rwlock); + if ((session = get_session(conn)) != NULL) { + generate_session_id(valid_id, session->random, session->user); + if (strcmp(valid_id, session->session_id) == 0) { + session->expire = time(0) + SESSION_TTL; + authorized = true; + } + } +// pthread_rwlock_unlock(&rwlock); + + return authorized; +} + +void Server::redirect_to(struct mg_connection *conn, const struct mg_request_info *request_info, const char *where) { + mg_printf(conn, "HTTP/1.1 302 Found\r\n" + "Set-Cookie: original_url=%s\r\n" + "Location: %s\r\n\r\n", + request_info->uri, where); +} + +void Server::print_html(struct mg_connection *conn, const struct mg_request_info *request_info, const std::string &html) { + mg_printf(conn, + "HTTP/1.1 200 OK\r\n" + "Content-Type: text/html\r\n" + "Content-Length: %d\r\n" // Always set Content-Length + "\r\n" + "%s", + (int) html.size(), html.c_str()); +} + +void Server::serve_login(struct mg_connection *conn, const struct mg_request_info *request_info) { + std::string html= "\ + \ + \ + \ + Spectrum 2 web interface\ + \ + \ + \ +
\ +

Spectrum 2 web interface login

\ +
\ +
\ + Username:
\ + Password:
\ + \ +
\ +
\ + \ +"; + + print_html(conn, request_info, html); +} + +void Server::serve_root(struct mg_connection *conn, const struct mg_request_info *request_info) { + std::vector list = show_list(m_config, false); + std::string html= get_header() + "

List of instances

"; + + BOOST_FOREACH(std::string &instance, list) { + html += ""; + html += ""; + Swift::SimpleEventLoop eventLoop; + Swift::BoostNetworkFactories networkFactories(&eventLoop); + + ask_local_server(m_config, networkFactories, instance, "status"); + eventLoop.runUntilEvents(); + while(get_response().empty()) { + eventLoop.runUntilEvents(); + } + html += ""; + if (get_response().find("Running") == 0) { + html += ""; + } + else { + html += ""; + } + html += ""; + } + + html += "
JIDStatusCommand
" + instance + "" + get_response() + "StopStart
"; + print_html(conn, request_info, html); +} + +void *Server::event_handler(enum mg_event event, struct mg_connection *conn) { + const struct mg_request_info *request_info = mg_get_request_info(conn); + void *processed = (void *) 0x1; + + if (event == MG_NEW_REQUEST) { + if (!is_authorized(conn, request_info)) { + redirect_to(conn, request_info, "/login"); + } else if (strcmp(request_info->uri, "/authorize") == 0) { + authorize(conn, request_info); + } else if (strcmp(request_info->uri, "/login") == 0) { + serve_login(conn, request_info); + } else if (strcmp(request_info->uri, "/") == 0) { + serve_root(conn, request_info); + } else { + // No suitable handler found, mark as not processed. Mongoose will + // try to serve the request. + processed = NULL; + } + } else { + processed = NULL; + } + + return processed; +} + + + +