// 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 // TODO: Fix all the HTTP(s) responses. #include "Net7.h" #include "SSL_Connection.h" #include "ServerManager.h" #include "PacketStructures.h" #include "mysql/mysqlplus.h" SSL_Connection::SSL_Connection() { // Initialize parameters m_SslConnectionThreadRunning = false; m_SslConnectionThreadTerminated = false; m_ConnectionActive = false; m_Socket = INVALID_SOCKET; m_SSL = NULL; m_ServerMgr = 0; m_IpAddress = 0; m_AvatarID = 0; m_LastThreadComms = 0; m_ServerShutdown = false; #ifdef WIN32 UINT uiThreadId = 0; m_RecvThreadHandle = (HANDLE)_beginthreadex(NULL, 1024, SSLRecvThread, this, 0, &uiThreadId); #else pthread_mutex_init(&m_RecvThreadMtx, NULL); pthread_cond_init(&m_RecvThreadCond, NULL); pthread_create(&m_RecvThreadHandle, NULL, SSLRecvThread, this); #endif if (!m_RecvThreadHandle) LogMessage("Unable to create a new SSL receive thread\n"); } SSL_Connection * SSL_Connection::ReSetSSL_Connection(SOCKET s, ServerManager *server_mgr, unsigned long *ip_addr, SSL_CTX *ssl_context) { if (!m_RecvThreadHandle) // Just as a safeguard { LogMessage("!Restarting SSL Connection receive thread\n"); #ifdef WIN32 UINT uiThreadId = 0; m_RecvThreadHandle = (HANDLE)_beginthreadex(NULL, 1024, SSLRecvThread, this, 0, &uiThreadId); #else pthread_mutex_init(&m_RecvThreadMtx, NULL); pthread_cond_init(&m_RecvThreadCond, NULL); pthread_create(&m_RecvThreadHandle, NULL, SSLRecvThread, this); #endif } m_Mutex.Lock(); m_LastThreadComms = GetNet7TickCount(); m_ConnectionActive = true; m_ServerShutdown = false; m_AvatarID = 1; m_Socket = s; m_ServerMgr = server_mgr; if (ip_addr) m_IpAddress = *ip_addr; m_Mutex.Unlock(); // Set SSL Parameters m_SSL = SSL_new(ssl_context); if (!m_SSL) { LogMessage("Unable to reanimate SSL receive thread: SSL_new failed\n"); m_ConnectionActive = false; return this; } SSL_set_fd(m_SSL, m_Socket); LogMessage("Reanimating SSL receive thread.\n"); WakeupThread(); return this; } SSL_Connection::~SSL_Connection() { LogMessage("SSL Connection Destroyed\n"); KillConnection(); // Let thread know it is terminated if (!m_SslConnectionThreadTerminated) { m_SslConnectionThreadTerminated = true; WakeupThread(); m_Mutex.Lock(); Sleep(1); m_Mutex.Unlock(); } } bool SSL_Connection::IsActive() { // Good opportunity to check g_ServerShutdown if (g_ServerShutdown) WakeupThread(); return m_ConnectionActive; } void SSL_Connection::WakeupThread() { m_SslConnectionThreadRunning = true; #ifndef WIN32 pthread_cond_signal(&m_RecvThreadCond); #else ResumeThread(m_RecvThreadHandle); #endif } bool SSL_Connection::OpenSSL_Link() { int ret; int error; ret = SSL_accept(m_SSL); Sleep(1); if ( (ret <= 0) && (m_SSL) ) { error = SSL_get_error(m_SSL, ret); if (error == SSL_ERROR_SYSCALL) error = WSAGetLastError(); LogMessage("SSL_accept failed, error = %d\n", error); return false; } else if (!m_SSL) { //LogMessage("SSL_accept failed. Connection was aborted by ThreadLockCheck().\n"); return false; } return true; } void SSL_Connection::SSL_ConnectionThread() { int bytes; int error; size_t response_length; char * response; while (!g_ServerShutdown && !m_SslConnectionThreadTerminated) { while (m_SslConnectionThreadRunning) { // NOTE: OpenSSL_Link() has been moved here from ReSetSSL_Connection because // otherwise the SSL_ThreadLockCheck never gets called while still in SSL_accept (blocking, remember?) if (!OpenSSL_Link()) { m_SslConnectionThreadRunning = false; break; } bytes = 0; m_LastThreadComms = GetNet7TickCount(); if (m_SSL) { bytes = SSL_read(m_SSL, m_Recv_Buffer, SSL_RECV_BUFFER - 1); } if (bytes < 0) { error = SSL_get_error(m_SSL, bytes); if (error == SSL_ERROR_SYSCALL) error = WSAGetLastError(); if ( (error != SSL_ERROR_ZERO_RETURN) && (error != WSAECONNRESET) && (error != 1) ) { LogMessage("SSL_Connection SSL_read failed, error=%d\n", error); } } else if (bytes > 0) { if (bytes > SSL_RECV_BUFFER - 1) bytes = SSL_RECV_BUFFER - 1; m_Recv_Buffer[bytes] = '\0'; response = GetResponse(m_Recv_Buffer, &response_length); if (response && m_SSL) { SSL_write(m_SSL, response, response_length); delete [] response; if (m_ServerShutdown) { Sleep(200); g_ServerShutdown = true; } } } m_SslConnectionThreadRunning = false; } KillConnection(); // m_SslConnectionThreadRunning set to false. Go to sleep... Zzz... #ifdef WIN32 SuspendThread(m_RecvThreadHandle); #else pthread_cond_wait(&m_RecvThreadCond, &m_RecvThreadMtx); #endif } m_SslConnectionThreadTerminated = true; // Do any special thread cleanup here } 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; char *mime_type = NULL; 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 "); 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, 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: %d\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"); } #ifdef WIN32 UINT WINAPI SSL_Connection::SSLRecvThread(void *param) #else void* SSL_Connection::SSLRecvThread(void *param) #endif { SSL_Connection* p_this = reinterpret_cast( param ); p_this->SSL_ConnectionThread(); #ifdef WIN32 return 1; #else return NULL; #endif } // Kills an active connection on the Socket, if any, and suspends the thread void SSL_Connection::KillConnection(bool AbortSSL) { m_Mutex.Lock(); if ( m_Socket != INVALID_SOCKET ) { LogMessage("Closing SSL Connection.\n"); if (m_SSL) { if (!AbortSSL) SSL_shutdown(m_SSL); // Do a proper shutdown if we're not aborting SSL_clear(m_SSL); } shutdown(m_Socket, 2); // This is a MUST, otherwise SSL_accept just sits there... closesocket(m_Socket); m_Socket = INVALID_SOCKET; } if (m_SSL && m_SSL->version == TLS1_VERSION) { SSL_free(m_SSL); } m_SSL = NULL; // No longer active, not running m_ConnectionActive = false; m_SslConnectionThreadRunning = false; m_AvatarID = 0; m_Mutex.Unlock(); }