// PORT: in-client account registration. 40250 has no counterpart: its accounts were made on the web. // // net.RegisterAccount(host, port, login, password, socialId) starts one HTTP POST /register to the // server's registration service (tools/server/register_server.py) on a worker thread; // net.GetRegisterAccountResult() polls it from the login window's OnUpdate. The reply is a single // line, "OK" or "ERR ", returned to Python as-is. #include "StdAfx.h" #include #include #include #include #include #include #include #ifdef MT_HAS_MESHPORT #include "transport.h" // PORT: registration shares the same LAN/mesh policy as every game socket. #endif #include #include #include #include #include #include #include namespace { enum ERegisterState { REGISTER_IDLE, REGISTER_PENDING, REGISTER_DONE, }; const int c_iRegisterTimeoutMs = 8000; std::mutex gs_kRegisterMutex; std::atomic gs_iRegisterState(REGISTER_IDLE); std::string gs_strRegisterResult; // PORT: registration must stop on dialog cancellation and before the mesh runtime is torn down. std::thread gs_registerWorker; std::atomic gs_registerCancelled(false); std::mutex gs_registerSocketMutex; int gs_registerSocket = -1; std::string __UrlEncode(const std::string& c_rstText) { static const char c_szHex[] = "0123456789ABCDEF"; std::string stOut; for (unsigned char c : c_rstText) { if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '_' || c == '.' || c == '~') { stOut += char(c); } else { stOut += '%'; stOut += c_szHex[c >> 4]; stOut += c_szHex[c & 15]; } } return stOut; } bool __WaitFor(int iSocket, short sEvents) { const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(c_iRegisterTimeoutMs); while (!gs_registerCancelled && std::chrono::steady_clock::now() < deadline) { pollfd kPoll = {iSocket,sEvents,0}; int result = poll(&kPoll,1,100); if (result > 0) return (kPoll.revents & sEvents) != 0; if (result < 0 && errno != EINTR) return false; } return false; } std::string __Post(const std::string& c_rstHost, int iPort, const std::string& c_rstBody) { addrinfo kHints; memset(&kHints, 0, sizeof(kHints)); kHints.ai_family = AF_INET; kHints.ai_socktype = SOCK_STREAM; addrinfo* pkAddr = NULL; if (getaddrinfo(c_rstHost.c_str(), std::to_string(iPort).c_str(), &kHints, &pkAddr) != 0 || !pkAddr) return "ERR CONNECT"; if (gs_registerCancelled) { freeaddrinfo(pkAddr); return "ERR CANCELLED"; } const int iSocket = socket(pkAddr->ai_family, pkAddr->ai_socktype, pkAddr->ai_protocol); if (iSocket < 0) { freeaddrinfo(pkAddr); return "ERR CONNECT"; } #ifdef SO_NOSIGPIPE const int iOne = 1; setsockopt(iSocket, SOL_SOCKET, SO_NOSIGPIPE, &iOne, sizeof(iOne)); #endif { std::lock_guard lock(gs_registerSocketMutex); gs_registerSocket = iSocket; } fcntl(iSocket, F_SETFL, fcntl(iSocket, F_GETFL, 0) | O_NONBLOCK); std::string stResult = "ERR CONNECT"; int iError = 0; socklen_t iErrorLen = sizeof(iError); #ifdef MT_HAS_MESHPORT const int connected = mt::meshport::connect_socket(iSocket, *reinterpret_cast(pkAddr->ai_addr)); const bool throughMesh = mt::meshport::pending(iSocket); bool meshReady = true; if (mt::meshport::pending(iSocket)) { int state = 0; while (state == 0 && !gs_registerCancelled) { state = mt::meshport::poll_socket(iSocket); if (!state) std::this_thread::sleep_for(std::chrono::milliseconds(2)); } meshReady = state == 1; } if (!gs_registerCancelled && meshReady && (throughMesh || connected == 0 || #else if (connect(iSocket, pkAddr->ai_addr, pkAddr->ai_addrlen) == 0 || #endif (errno == EINPROGRESS && __WaitFor(iSocket, POLLOUT) && getsockopt(iSocket, SOL_SOCKET, SO_ERROR, &iError, &iErrorLen) == 0 && iError == 0) #ifdef MT_HAS_MESHPORT ) #endif ) { const std::string stRequest = "POST /register HTTP/1.0\r\n" "Host: " + c_rstHost + "\r\n" "Content-Type: application/x-www-form-urlencoded\r\n" "Content-Length: " + std::to_string(c_rstBody.size()) + "\r\n" "Connection: close\r\n\r\n" + c_rstBody; size_t uSent = 0; while (uSent < stRequest.size() && __WaitFor(iSocket, POLLOUT)) { #ifdef MSG_NOSIGNAL const ssize_t n = send(iSocket, stRequest.data() + uSent, stRequest.size() - uSent, MSG_NOSIGNAL); #else const ssize_t n = send(iSocket, stRequest.data() + uSent, stRequest.size() - uSent, 0); #endif if (n <= 0) break; uSent += size_t(n); } std::string stResponse; if (uSent == stRequest.size()) { char acBuf[512]; while (stResponse.size() < 4096 && __WaitFor(iSocket, POLLIN)) { const ssize_t n = recv(iSocket, acBuf, sizeof(acBuf), 0); if (n <= 0) break; stResponse.append(acBuf, size_t(n)); } } const size_t uBody = stResponse.find("\r\n\r\n"); if (uBody == std::string::npos) { stResult = uSent == stRequest.size() ? "ERR TIMEOUT" : "ERR CONNECT"; } else { stResult = stResponse.substr(uBody + 4); const size_t uEnd = stResult.find_first_of("\r\n"); if (uEnd != std::string::npos) stResult.resize(uEnd); if (stResult.empty()) stResult = "ERR SERVER"; } } #ifdef MT_HAS_MESHPORT mt::meshport::forget_socket(iSocket); #endif { std::lock_guard lock(gs_registerSocketMutex); gs_registerSocket = -1; close(iSocket); } freeaddrinfo(pkAddr); return gs_registerCancelled ? "ERR CANCELLED" : stResult; } } PyObject* netRegisterAccount(PyObject* poSelf, PyObject* poArgs) { char* szHost; int iPort; char* szLogin; char* szPassword; char* szSocialID; if (!PyTuple_GetString(poArgs, 0, &szHost)) return Py_BuildException(); if (!PyTuple_GetInteger(poArgs, 1, &iPort)) return Py_BuildException(); if (!PyTuple_GetString(poArgs, 2, &szLogin)) return Py_BuildException(); if (!PyTuple_GetString(poArgs, 3, &szPassword)) return Py_BuildException(); if (!PyTuple_GetString(poArgs, 4, &szSocialID)) return Py_BuildException(); int iExpected = REGISTER_IDLE; if (!gs_iRegisterState.compare_exchange_strong(iExpected, REGISTER_PENDING)) { iExpected = REGISTER_DONE; if (!gs_iRegisterState.compare_exchange_strong(iExpected, REGISTER_PENDING)) return Py_BuildValue("i", 0); // one request at a time } if (gs_registerWorker.joinable()) gs_registerWorker.join(); gs_registerCancelled = false; const std::string stHost = szHost; const std::string stBody = "login=" + __UrlEncode(szLogin) + "&password=" + __UrlEncode(szPassword) + "&social_id=" + __UrlEncode(szSocialID); gs_registerWorker = std::thread([stHost, iPort, stBody]() { const std::string stResult = __Post(stHost, iPort, stBody); { std::lock_guard kLock(gs_kRegisterMutex); gs_strRegisterResult = stResult; } gs_iRegisterState = REGISTER_DONE; }); return Py_BuildValue("i", 1); } // Returns "" while the request runs (or none was started), else its result once. PyObject* netGetRegisterAccountResult(PyObject* poSelf, PyObject* poArgs) { int iExpected = REGISTER_DONE; if (!gs_iRegisterState.compare_exchange_strong(iExpected, REGISTER_IDLE)) return Py_BuildValue("s", ""); std::lock_guard kLock(gs_kRegisterMutex); return Py_BuildValue("s", gs_strRegisterResult.c_str()); } // PORT: cancellation interrupts both route preparation and HTTP I/O; the worker owns close(). void CancelAccountRegistration() { gs_registerCancelled = true; std::lock_guard lock(gs_registerSocketMutex); if (gs_registerSocket >= 0) ::shutdown(gs_registerSocket,SHUT_RDWR); } void StopAccountRegistration() { CancelAccountRegistration(); if (gs_registerWorker.joinable()) gs_registerWorker.join(); gs_iRegisterState = REGISTER_IDLE; } PyObject* netCancelRegisterAccount(PyObject*,PyObject*) { CancelAccountRegistration(); return Py_BuildNone(); }