// SSL_Connection.cpp
//
// Test URL: https://local.net-7.org/certificate.html
// Test URL: https://local.net-7.org/shutdown.cgi
// Test URL: https://local.net-7.org/sectorserver.cgi?username=VectoR&port=3500&max_sectors=20&version=0.2
// Test URL: https://local.net-7.org/AuthLogin?username=VectoR&password=go&serviceID=2184&version=2.5
//
// TODO: Add code for subsxml to return XML data from resource files
//
#include "Net7.h"
#include "SSL_Connection.h"
#include "ServerManager.h"
#include "PacketStructures.h"
#include "mysql/mysqlplus.h"
SSL_Connection::SSL_Connection(SOCKET s, ServerManager &server_mgr, long ip_address)
:
m_Socket(s),
m_ServerMgr(server_mgr),
m_IpAddress(ip_address)
{
// Mark this as an active connection
m_ConnectionActive = true;
// Thread is not running (yet)
m_SslConnectionThreadRunning = false;
// Clear the server shutdown flag
m_ServerShutdown = false;
//unsigned char *ip = (unsigned char *)&m_IpAddress;
//printf("Got IP: %u.%u.%u.%u\n", ip[0], ip[1], ip[2], ip[3]);
//Exchange key information
if (OpenSSL_Link())
{
SSL_ConnectionCheck();
}
#ifdef WIN32
//_beginthread(&RunSslConnectionThread, 0, this);
#else
//pthread_create(&m_Thread, NULL, RunSslConnectionThread, (void *) this);
#endif
}
SSL_Connection::~SSL_Connection()
{
m_Mutex.Lock();
if (m_Socket != INVALID_SOCKET)
{
closesocket(m_Socket);
}
m_Mutex.Unlock();
// Allow the thread to die
int limit = 100;
while (m_SslConnectionThreadRunning)
{
Sleep(20);
if (--limit < 0)
{
break;
}
}
}
bool SSL_Connection::IsActive()
{
return m_ConnectionActive;
}
bool SSL_Connection::OpenSSL_Link()
{
SSL_METHOD * ssl_server_method;
//printf("Server accepted incoming SSL connection\n");
SSL_load_error_strings();
SSLeay_add_ssl_algorithms();
ssl_server_method = SSLv23_server_method();
m_ssl_context = SSL_CTX_new(ssl_server_method);
if (!m_ssl_context)
{
printf("SSL_CTX_new failed\n");
m_ConnectionActive = false;
return false;
}
char certf[40];
char keyf[40];
sprintf(certf, "%s.cer", g_DomainName);
sprintf(keyf, "%s.pem", g_DomainName);
if (SSL_CTX_use_certificate_file(m_ssl_context, certf, SSL_FILETYPE_PEM) <= 0)
{
printf("SSL_CTX_use_certificate_file failed\n");
m_ConnectionActive = false;
return false;
}
if (SSL_CTX_use_PrivateKey_file(m_ssl_context, keyf, SSL_FILETYPE_PEM) <= 0)
{
printf("SSL_CTX_use_PrivateKey_file failed\n");
m_ConnectionActive = false;
return false;
}
if (!SSL_CTX_check_private_key(m_ssl_context))
{
printf("Private key does not match the certificate public key\n");
m_ConnectionActive = false;
return false;
}
m_SSL = SSL_new(m_ssl_context);
if (!m_SSL)
{
printf("SSL_new failed\n");
m_ConnectionActive = false;
return false;
}
SSL_set_fd(m_SSL, m_Socket);
int ret = SSL_accept(m_SSL);
if (ret < 0)
{
int error = SSL_get_error(m_SSL, ret);
if (error == SSL_ERROR_SYSCALL)
{
error = WSAGetLastError();
}
if ((error == SSL_ERROR_WANT_READ) ||
(error == SSL_ERROR_WANT_WRITE))
{
// Try again
int ret = SSL_accept(m_SSL);
if (ret < 0)
{
int error = SSL_get_error(m_SSL, ret);
if (error == SSL_ERROR_SYSCALL)
{
error = WSAGetLastError();
}
printf("SSL_accept failed twice, error = %d\n", error);
m_ConnectionActive = false;
return false;
}
}
else
{
printf("SSL_accept failed, error = %d\n", error);
m_ConnectionActive = false;
return false;
}
}
m_SslConnectionThreadRunning = true;
return true;
}
void SSL_Connection::SSL_ConnectionCheck()
{
char recv_buffer[2048];
if (!g_ServerShutdown && m_SslConnectionThreadRunning)
{
//anything there?
if (SSL_peek(m_SSL, recv_buffer, sizeof(recv_buffer) - 1))
{
//printf("SSL_Connection calling SSL_read\n");
int bytes = SSL_read(m_SSL, recv_buffer, sizeof(recv_buffer) - 1);
if (bytes < 0)
{
int error = SSL_get_error(m_SSL, bytes);
if (error == SSL_ERROR_SYSCALL)
{
error = WSAGetLastError();
}
if ((error != SSL_ERROR_ZERO_RETURN) &&
(error != WSAECONNRESET))
{
printf("SSL_Connection SSL_read failed, error=%d\n", error);
}
// terminate the connection
m_SslConnectionThreadRunning = false;
}
else if (bytes == 0)
{
//printf("SSL_Connection closed gracefully\n");
// terminate the connection
m_SslConnectionThreadRunning = false;
}
else if (bytes > 0)
{
//printf("SSL_Connection SSL_read returned %d\n", bytes);
recv_buffer[bytes] = '\0';
size_t response_length;
char * response = GetResponse(recv_buffer, &response_length);
if (response)
{
//printf("SSL_Connection calling SSL_write (%s)\n", response);
int ret = SSL_write(m_SSL, response, response_length);
if (ret < 0)
{
int error = SSL_get_error(m_SSL, ret);
//printf("SSL_Connection SSL_write failed, error=%d\n", error);
// terminate the connection
m_SslConnectionThreadRunning = false;
}
delete [] response;
m_SslConnectionThreadRunning = false;
if (m_ServerShutdown)
{
Sleep(200);
g_ServerShutdown = true;
}
}
}
}
if (m_SslConnectionThreadRunning) //we're still running, just return
{
return;
}
}
/* Clean up. */
//printf("SSL_Connection calling closesocket\n");
closesocket(m_Socket);
//printf("SSL_Connection calling SSL_free\n");
SSL_free(m_SSL);
//printf("SSL_Connection calling SSL_CTX_free\n");
SSL_CTX_free(m_ssl_context);
m_SslConnectionThreadRunning = false;
m_ConnectionActive = false;
}
char *SSL_Connection::GetResponse(char *recv_buffer, size_t *response_length)
{
//printf("%s\n", recv_buffer);
char *response = NULL;
*response_length = 0;
char text[256];
bool UserTaken = false;
if (strstr(recv_buffer, "certificate.html"))
{
sprintf(text,
"\r\n"
"
\r\n"
"\r\n"
"\r\n"
"\r\n"
"%s certificate successfully installed!
\r\n"
"Please close the browser to continue\r\n"
"\r\n"
"\r\n",
g_DomainName);
response = HttpResult(response_length, text);
}
else if (strstr(recv_buffer, "NewPlayer.cgi"))
{
char filename[MAX_PATH];
sprintf(filename, "%sNewPlayer.cgi.txt", SERVER_LOGS_PATH);
FILE *f = fopen(filename, "a");
if (f)
{
fprintf(f, "%s", recv_buffer);
fclose(f);
}
// TODO: Process the form submitted by the player
char *data = strstr(recv_buffer, "\r\n\r\n");
if (data)
{
data += 4;
char *username = strtok(data, "&\r\n ");
char *password = strtok(NULL, "&\r\n ");
char *forumname = strtok(NULL, "&\r\n ");
char *email = strtok(NULL, "&\r\n ");
if (username && password && forumname && email)
{
if ((strncmp(username, USERNAME_TAG, strlen(USERNAME_TAG)) == 0) &&
(strncmp(password, PASSWORD_TAG, strlen(PASSWORD_TAG)) == 0) &&
(strncmp(forumname, FORUM_NAME_TAG, strlen(FORUM_NAME_TAG)) == 0) &&
(strncmp(email, EMAIL_TAG, strlen(EMAIL_TAG)) == 0))
{
username += strlen(USERNAME_TAG);
password += strlen(PASSWORD_TAG);
forumname += strlen(FORUM_NAME_TAG);
email += strlen(EMAIL_TAG);
#ifdef ADD_USERS
m_ServerMgr.m_AccountMgr.AddUser(username, password, "U");
#endif
// TODO: urldecode the strings
// TODO: verify that the fields are not blank
// TODO: verify that the fields do not contain invalid characters
// TODO: verify that the email address is valid format
// TODO: verify that the forum name exists on www.enb-emulator.com
// TODO: Log the IP ADDRESS!
// TODO: take out mutex lock on account manager
// TODO: Verify that the IP ADDRESS is not on the banned list
// TODO: verify that the forum name has no more than 2 accounts
// TODO: see if this username already exists in accounts list or pending list
// TODO: add account name to the pending list with a date stamp
// TODO: release mutex lock on account manager
// TODO: send an email to the user
// TODO: return a valid HTML page telling the player to check their email account
if (UserTaken)
strcpy(recv_buffer, "GET /UsernameTaken.html HTTP/1.1");
else
strcpy(recv_buffer, "GET /PendingAccountEmailSent.html HTTP/1.1");
}
}
}
}
else if (strstr(recv_buffer, "/who.cgi"))
{
response = m_ServerMgr.m_PlayerMgr.WhoHtml(response_length);
}
else if (strstr(recv_buffer, "/AuthLogin"))
{
response = AuthLogin(response_length, recv_buffer);
}
else if (strstr(recv_buffer, "/subsxml/subsxml.jsp"))
{
char filename[MAX_PATH];
sprintf(filename, "%shttps_subsxml.txt", SERVER_LOGS_PATH);
FILE *f = fopen(filename, "w");
if (f)
{
fprintf(f, "%s", recv_buffer);
fclose(f);
}
}
else if (strstr(recv_buffer, "/touchsession.jsp"))
{
response = TouchSession(response_length, recv_buffer);
}
else if (strstr(recv_buffer, "/sectorserver.cgi"))
{
response = SectorServer(response_length, recv_buffer);
}
/*
#ifdef _DEBUG
else if (strstr(recv_buffer, "shutdown.cgi"))
{
printf("Shutting down server!\n");
m_ServerShutdown = true;
char *text =
"\r\n"
"\r\n"
"\r\n"
"\r\n"
"\r\n"
"Server shutdown in progress!
\r\n"
"\r\n"
"\r\n";
response = HttpResult(response_length, text);
}
#endif
*/
if (!response)
{
// Throw away everything after the first line
strtok(recv_buffer, "\r\n");
char *p = strstr(recv_buffer, " HTTP/");
if (p)
{
// throw away the tail end of the line
*p = 0;
// This does not appear to be handled above. See if we can parse the filename
if (strncmp(recv_buffer, "GET /", 5) == 0)
{
p = recv_buffer + 5;
if (p[0] == 0)
{
p = "redirect.html";
}
if ((strlen(recv_buffer) + strlen(SERVER_HTML_PATH)) < MAX_PATH - 1)
{
// "NewPlayer.html" length=14
// ext = p+8
int length = strlen(p);
char *ext3 = p + length - 4;
char *ext4 = p + length - 5;
bool legal = false;
const char *mime_type = NULL;
const char *open_type = NULL;
// This is a list of legal file extensions for our "web server"
if (strcmp(ext3, ".css") == 0)
{
legal = true;
mime_type = "text/plain";
open_type = "r";
}
else if (strcmp(ext4, ".html") == 0)
{
legal = true;
mime_type = "text/html";
open_type = "r";
}
else if (strcmp(ext3, ".gif") == 0)
{
legal = true;
mime_type = "image/gif";
open_type = "rb";
}
if (legal && mime_type && open_type)
{
// Pretend to be a web server and see if a file exists using the specified name
char filename[MAX_PATH];
sprintf(filename, "%s%s", SERVER_HTML_PATH, p);
FILE *f = fopen(filename, open_type);
if (f)
{
fseek(f,0,SEEK_END);
size_t length = ftell(f);
if ((length > 0) && (length < MAX_SERVER_FILE_SIZE))
{
fseek(f,0,SEEK_SET);
size_t actual = fread(m_Buffer, 1, length, f);
if (actual > 0)
{
response = HttpResult(response_length, m_Buffer, mime_type, actual);
}
}
fclose(f);
}
}
}
}
}
// If all else fails, dump the request to a file
if (!response)
{
char filename[MAX_PATH];
sprintf(filename, "%shttps_unrecognized.txt", SERVER_LOGS_PATH);
FILE *f = fopen(filename, "a");
if (f)
{
// Append the request to the log file
fprintf(f, "%s\n", recv_buffer);
fclose(f);
}
}
}
if (!response)
{
// TODO: send 404 response
static const char * error_404 =
"HTTP/1.1 404 File Not Found\r\n"
"Server: AuthServer/2.5\r\n"
"Keep-Alive: timeout=15, max=100\r\n"
"Connection: Keep-Alive\r\n"
"Content-Length: 2\r\n"
"Content-type: text/plain\r\n"
"\r\n"
"\r\n";
response = new char[strlen(error_404) + 1];
strcpy(response, error_404);
}
return response;
}
char *SSL_Connection::TouchSession(size_t *response_length, char *recv_buffer)
{
char * response = NULL;
char * lkey = strstr(recv_buffer, LKEY_TAG);
if (lkey)
{
lkey += strlen(LKEY_TAG);
strtok(lkey, "&\r\n ");
const char *info =
"HTTP/1.1 200 \r\n"
"Server: AuthServer/2.5\r\n"
"Content-Type: text/plain; charset=ISO-8859-1\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"7\r\n"
"Success\r\n"
"0\r\n"
"\r\n";
response = new char[strlen(info) + 1];
strcpy(response, info);
//printf("TouchSession Response:\n%s", response);
*response_length = strlen(response);
}
return (response);
}
char *SSL_Connection::AuthLogin(size_t *response_length, char *recv_buffer)
{
char info[128] = "Valid=False\r\n";
/*
Here is an example of what the client sends to the AuthServer:
GET /AuthLogin?username=VectoR&password=go&serviceID=2184&version=2.5 HTTP/1.1
User-Agent: AuthLogin
Host: local.net-7.org
Connection: Keep-Alive
Cache-Control: no-cache
*/
char * username = strstr(recv_buffer, USERNAME_TAG);
char * password = strstr(recv_buffer, PASSWORD_TAG);
if (username && password)
{
char * ticket = 0;
username += strlen(USERNAME_TAG);
password += strlen(PASSWORD_TAG);
strtok(username, "& ");
strtok(password, "& ");
unsigned char *ip = (unsigned char *)&m_IpAddress;
LogMessage("User '%s' logging in from IP: %u.%u.%u.%u\n", username, ip[0], ip[1], ip[2], ip[3]);
// TODO: This is a good place to check for "banned" IP addresses.
if ((ticket = g_AccountMgr->IssueTicket(username, password)))
{
sprintf(info, "Valid=TRUE\r\nTicket=%s\r\n", ticket);
}
}
return HttpResult(response_length, info, "text/plain");
}
char *SSL_Connection::HttpResult(size_t *response_length, char *data, const char *content_type, size_t length)
{
if (length == 0)
{
length = strlen(data);
}
char header[256];
sprintf(header,
"HTTP/1.1 200 OK\r\n"
"Content-Type: %s\r\n"
"Server: AuthServer/2.5\r\n"
"Content-Length: %ld\r\n"
"\r\n",
content_type,
length);
size_t header_length = strlen(header);
*response_length = header_length + length;
char * response = new char[header_length + length + 1];
if (response)
{
strcpy(response, header);
memcpy(response + header_length, data, length);
response[header_length + length] = 0;
}
return (response);
}
char *SSL_Connection::SectorServer(size_t *response_length, char *recv_buffer)
{
// The Sector Server registers with the Global Server via HTTPS
// The Global Server then turns around and connects to the
// Sector Server via the specified port (usually 3500).
char info[256] = "Success=FALSE\r\n";
/*
Here is an example of what the Sector Server sends to the AuthServer:
GET /sectorserver.cgi?username=VectoR&port=3500&max_sectors=20&version=0.2 HTTP/1.1
User-Agent: AuthLogin
Host: local.net-7.org
Connection: Keep-Alive
Cache-Control: no-cache
*/
//printf("Received SSL request to register a Sector Server\n");
char * username = strstr(recv_buffer, USERNAME_TAG);
char * port = strstr(recv_buffer, PORT_TAG);
char * max_sectors = strstr(recv_buffer, MAX_SECTORS_TAG);
char * version = strstr(recv_buffer, VERSION_TAG);
if (username && port && max_sectors && version)
{
username += strlen(USERNAME_TAG);
port += strlen(PORT_TAG);
max_sectors += strlen(MAX_SECTORS_TAG);
version += strlen(VERSION_TAG);
strtok(username, "& ");
strtok(port, "& ");
strtok(max_sectors, "& ");
strtok(version, "& ");
//printf("username='%s'\n", username);
//printf("port='%s'\n", port);
//printf("version='%s'\n", version);
char expected_version[16];
sprintf(expected_version, "%d.%d", SECTOR_SERVER_MAJOR_VERSION, SECTOR_SERVER_MINOR_VERSION);
// The version number must match to
if (strcmp(version, expected_version) == 0)
{
int port_number = atoi(port);
int num_sectors = atoi(max_sectors);
if ((port_number >= 3500) && (max_sectors > 0))
{
char temp[80];
sprintf(temp, "%d", port_number);
if (strcmp(temp, port) == 0)
{
if (m_ServerMgr.m_SectorServerMgr.RegisterSectorServer(m_IpAddress, port_number, num_sectors, username))
{
strcpy(info, "Success=TRUE\r\n");
}
else
{
strcat(info, "The specified Sector Server IP Address and port are not pre-registered\r\n");
strcat(info, "Visit https://");
strcat(info, g_DomainName);
strcat(info, "/NewSectorServer.html to register\r\n");
}
}
else
{
strcat(info, "Invalid port number\r\n");
}
}
else
{
strcat(info, "Port number must be 3500 or above\r\n");
}
}
else
{
strcat(info, "Expected Sector Server version is ");
strcat(info, expected_version);
strcat(info, "\r\n");
}
}
else
{
strcat(info, "Invalid parameters\r\n");
}
return HttpResult(response_length, info, "text/plain");
}