#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; m_user = CONFIG_STRING(m_config, "service.admin_username"); m_password = CONFIG_STRING(m_config, "service.admin_password"); } Server::~Server() { if (ctx) { 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() { const char *options[] = { "listening_ports", boost::lexical_cast(CONFIG_INT(m_config, "service.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_onlineusers(struct mg_connection *conn, const struct mg_request_info *request_info) { std::string html = get_header(); char jid[255]; get_qsvar(request_info, "jid", jid, sizeof(jid)); html += std::string("

") + jid + " online users

"; Swift::SimpleEventLoop eventLoop; Swift::BoostNetworkFactories networkFactories(&eventLoop); ask_local_server(m_config, networkFactories, jid, "online_users"); eventLoop.runUntilEvents(); while(get_response().empty()) { eventLoop.runUntilEvents(); } std::string response = get_response(); std::vector users; boost::split(users, response, boost::is_any_of("\n")); BOOST_FOREACH(std::string &user, users) { html += ""; } html += "
JIDCommand
" + user + "
Back to main page"; html += ""; print_html(conn, request_info, html); } void Server::serve_cmd(struct mg_connection *conn, const struct mg_request_info *request_info) { std::string html = get_header(); char jid[255]; get_qsvar(request_info, "jid", jid, sizeof(jid)); char cmd[4096]; get_qsvar(request_info, "cmd", cmd, sizeof(cmd)); html += std::string("

") + jid + " command result

"; Swift::SimpleEventLoop eventLoop; Swift::BoostNetworkFactories networkFactories(&eventLoop); ask_local_server(m_config, networkFactories, jid, cmd); while(get_response().empty()) { eventLoop.runUntilEvents(); } std::string response = get_response(); html += "
" + response + "
"; html += "Back to main page"; html += ""; print_html(conn, request_info, html); } void Server::serve_start(struct mg_connection *conn, const struct mg_request_info *request_info) { std::string html= get_header() ; char jid[255]; get_qsvar(request_info, "jid", jid, sizeof(jid)); start_instances(m_config, jid); html += "" + get_response() + "
Back to main page"; html += ""; print_html(conn, request_info, html); } void Server::serve_stop(struct mg_connection *conn, const struct mg_request_info *request_info) { std::string html= get_header(); char jid[255]; get_qsvar(request_info, "jid", jid, sizeof(jid)); stop_instances(m_config, jid); html += "" + get_response() + "
Back to main page"; html += ""; 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 += ""; html += ""; } else { html += ""; html += ""; } html += ""; } html += "
JIDStatusCommandRun command
" + instance + "" + get_response() + "Stop
"; html += ""; html += ""; html += ""; html += "
Start
"; 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 if (strcmp(request_info->uri, "/onlineusers") == 0) { serve_onlineusers(conn, request_info); } else if (strcmp(request_info->uri, "/cmd") == 0) { serve_cmd(conn, request_info); } else if (strcmp(request_info->uri, "/start") == 0) { serve_start(conn, request_info); } else if (strcmp(request_info->uri, "/stop") == 0) { serve_stop(conn, request_info); } else { // No suitable handler found, mark as not processed. Mongoose will // try to serve the request. processed = NULL; } } else if (event == MG_EVENT_LOG) { // Called by Mongoose's cry() std::cerr << "Mongoose error: " << request_info->log_message << "\n"; } else { processed = NULL; } return processed; }