spectrum2/spectrum_manager/src/server.cpp
2012-10-22 15:05:17 +02:00

465 lines
13 KiB
C++

#include "server.h"
#include "methods.h"
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <time.h>
#include <stdarg.h>
#include <pthread.h>
#define SESSION_TTL 120
static std::string get_header() {
return "\
<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\
\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"> \
<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\" dir=\"ltr\"> \
<head>\
<title>Spectrum 2 web interface</title>\
<meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"/>\
<style type=\"text/css\">\
body{ background-color: #F9F9F9; color: #444444; font: normal normal 14px \"Helvetica\", \"Arial\", Sans-Serif; }\
\
pre, kbd, var, samp, tt{ font-family: \"Courier\", Monospace; }\
\
pre { font-size: 12px; }\
\
h1, h2, h3, h4, h5, h6, pre{ color: #094776; }\
\
h1{ font-size: 28px; }\
\
h2{ font-size: 24px; font-weight: normal; }\
\
h1, h2, h3, h4, h5, h6{ margin-bottom: 20px; }\
\
h2, h3{ border-bottom: 2px solid #EEEEEE; padding: 0 0 3px; } \
\
h3{ border-color: #E5E5E5; border-width: 1px; }\
\
h4{ font-size: 18px; }\
\
h1 a, h2 a{ font-weight: normal; }\
\
h1 a, h2 a, h3 a{ text-decoration: none; }\
\
h3, h5, h6{ font-size: 18px; }\
\
h4, h5, h6{ font-size: 14px; }\
\
p, dl, ul, ol{ margin: 20px 0; }\
\
p, dl, ul, ol, h3, h4, h5, h6{ margin-left: 20px; }\
\
li > ul,\
li > ol{ margin: 0; margin-left: 40px; }\
\
dl > dd{ margin-left: 20px; }\
\
li > p { margin: 0; }\
\
p, li, dd, dt, pre{ line-height: 1.5; }\
\
table {\
border-collapse: collapse;\
margin-bottom: 20px;\
margin-left:20px;\
}\
\
th {\
padding: 0 0.5em;\
text-align: center;\
}\
\
th {\
border: 1px solid #FB7A31;\
background: #FFC;\
}\
\
td {\
border-bottom: 1px solid #CCC;\
border-right: 1px solid #CCC;\
border-left: 1px solid #CCC;\
padding: 0 0.5em;\
}\
\
\
a:link,\
a:visited{ color: #1A5B8D; }\
\
a:hover,\
a:active{ color: #742CAC; }\
\
a.headerlink{ visibility: hidden; }\
\
:hover > a.headerlink { visibility: visible; }\
\
a img{ \
border: 0;\
outline: 0;\
}\
\
img{ display: block; max-width: 100%; }\
\
code {\
border: 1px solid #FB7A31;\
background: #FFC;\
}\
\
pre {\
white-space: pre-wrap;\
white-space: -moz-pre-wrap;\
white-space: -o-pre-wrap;\
border: 1px solid #FB7A31;\
background: #FFC;\
padding:5px;\
padding-left: 15px;\
}\
\
</style>\
</head><body><h1>Spectrum 2 web interface</h1>";
}
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<Server *>(request_info->user_data)->event_handler(event, conn);
}
bool Server::start() {
const char *options[] = {
"listening_ports", boost::lexical_cast<std::string>(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= "\
<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\
\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"> \
<html xmlns=\"http://www.w3.org/1999/xhtml\" lang=\"en\" dir=\"ltr\"> \
<head>\
<title>Spectrum 2 web interface</title>\
<meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"/>\
</head>\
<body>\
<center>\
<h2>Spectrum 2 web interface login</h2>\
<br/>\
<form action=\"/authorize\">\
Username: <input type=\"text\" name=\"user\"></input><br/>\
Password: <input type=\"password\" name=\"password\"></input><br/>\
<input type=\"submit\" value=\"Login\"></input>\
</form>\
</center>\
</body>\
</html>";
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("<h2>") + jid + " online users</h2><table><tr><th>JID<th>Command</th></tr>";
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<std::string> users;
boost::split(users, response, boost::is_any_of("\n"));
BOOST_FOREACH(std::string &user, users) {
html += "<tr><td>" + user + "</td><td></td></tr>";
}
html += "</table><a href=\"/\">Back to main page</a>";
html += "</body></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("<h2>") + jid + " command result</h2>";
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 += "<pre>" + response + "</pre>";
html += "<a href=\"/\">Back to main page</a>";
html += "</body></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 += "<b>" + get_response() + "</b><br/><a href=\"/\">Back to main page</a>";
html += "</body></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 += "<b>" + get_response() + "</b><br/><a href=\"/\">Back to main page</a>";
html += "</body></html>";
print_html(conn, request_info, html);
}
void Server::serve_root(struct mg_connection *conn, const struct mg_request_info *request_info) {
std::vector<std::string> list = show_list(m_config, false);
std::string html= get_header() + "<h2>List of instances</h2><table><tr><th>JID<th>Status</th><th>Command</th><th>Run command</th></tr>";
BOOST_FOREACH(std::string &instance, list) {
html += "<tr>";
html += "<td><a href=\"/onlineusers?jid=" + instance + "\">" + instance + "</a></td>";
Swift::SimpleEventLoop eventLoop;
Swift::BoostNetworkFactories networkFactories(&eventLoop);
ask_local_server(m_config, networkFactories, instance, "status");
eventLoop.runUntilEvents();
while(get_response().empty()) {
eventLoop.runUntilEvents();
}
html += "<td>" + get_response() + "</td>";
if (get_response().find("Running") == 0) {
html += "<td><a href=\"/stop?jid=" + instance + "\">Stop</a></td>";
html += "<td><form action=\"/cmd\">";
html += "<input type=\"hidden\" name=\"jid\" value=\"" + instance + "\"></input>";
html += "<input type=\"text\" name=\"cmd\"></input>";
html += "<input type=\"submit\" value=\"Run\"></input>";
html += "</form></td>";
}
else {
html += "<td><a href=\"/start?jid=" + instance + "\">Start</a></td>";
html += "<td></td>";
}
html += "</tr>";
}
html += "</table></body></html>";
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;
}