const WTVClientSessionData = require("../../../classes/WTVClientSessionData"); const minisrv_service_file = true; if (!session_data) { session_data = new WTVClientSessionData(minisrv_config, (socket.ssid || null)) } // Sorry Zef :kek // https://git.computernewb.com/yellows111/msnp-wiki/src/branch/master/docs/services/rst.md // the RST_ cookie stuff was code that was temp until we had proper token authentication const NS = { SOAP: "http://schemas.xmlsoap.org/soap/envelope/", WSSE: "http://schemas.xmlsoap.org/ws/2003/06/secext", WSP: "http://schemas.xmlsoap.org/ws/2002/12/policy", WSU: "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd", WSA: "http://schemas.xmlsoap.org/ws/2004/03/addressing", WST: "http://schemas.xmlsoap.org/ws/2004/04/trust", PSF: "http://schemas.microsoft.com/Passport/SoapServices/SOAPFault", ENC: "http://www.w3.org/2001/04/xmlenc#", DS: "http://www.w3.org/2000/09/xmldsig#" }; function getCookie(cookieString, name) { if (!cookieString) return null; const match = cookieString.match(new RegExp(`(?:^|;\\s*)${name}=([^;]*)`)); return match ? decodeURIComponent(match[1]) : null; } function setCookie(name, value, options = {}) { const cookie = `${name}=${encodeURIComponent(value)}`; const path = options.path || '/'; const expires = options.expires || ''; return `${cookie}; path=${path}${expires ? `; expires=${expires}` : ''}`; } function formatDateTime(dt) { return dt.toISOString().replace(/\.\d{3}Z$/, 'Z'); } function getClientIP() { const forwarded = request_headers['x-forwarded-for']; if (forwarded) { const ips = forwarded.split(','); return ips[0].trim(); } return request_headers['x-real-ip'] || '127.0.0.1'; } function generateRandomToken(userId, appliesTo, isLegacy = false) { const timestamp = Date.now(); const randomPart = crypto.randomBytes(32).toString('hex'); if (isLegacy) { const tokenData = `${userId}|${appliesTo}|${timestamp}|${randomPart}`; return crypto.createHash('sha256').update(tokenData).digest('hex'); } else { const tokenData = { uid: userId, app: appliesTo, ts: timestamp, rand: randomPart, ver: '1.0' }; return Buffer.from(JSON.stringify(tokenData)).toString('base64'); } } function extractXmlValue(xml, elementName) { if (!xml) return null; const patterns = [ new RegExp(`<${elementName}>([\\s\\S]*?)`, 'i'), new RegExp(`([\\s\\S]*?)`, 'i'), new RegExp(`([\\s\\S]*?)`, 'i'), new RegExp(`([\\s\\S]*?)`, 'i') ]; for (const regex of patterns) { const match = xml.match(regex); if (match && match[1]) { let value = match[1].trim(); value = value.replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&'); return value; } } return null; } function extractTokenFromCipherValue(xml) { if (!xml) return null; const cipherRegex = /([\s\S]*?)<\/CipherValue>/gi; let match; let token = null; while ((match = cipherRegex.exec(xml)) !== null) { let cipherValue = match[1].trim(); if (cipherValue && cipherValue.length > 0) { token = cipherValue; debug("Found CipherValue token:", token.substring(0, 50) + "..."); break; } } return token; } function validateTokenAndGetUser(token) { try { let userId = null; let email = null; if (request_headers.cookie) { userId = getCookie(request_headers.cookie, 'RST_Auth'); email = getCookie(request_headers.cookie, 'RST_Email'); if (!email) email = getCookie(request_headers.cookie, 'rst_email'); if (!email) email = getCookie(request_headers.cookie, 'rst_username'); } if (!userId) { userId = crypto.createHash('md5').update(token).digest('hex'); email = `user_${userId.substring(0, 8)}@example.com`; } debug(`Token validated - UserId: ${userId}, Email: ${email}`); return { success: true, userId, email }; } catch (error) { console.error("Token validation error:", error); return { success: false, userId: null, email: null }; } } function generateErrorResponse(errorCode, errorText) { const now = formatDateTime(new Date()); headers = `Status: 200 OK Content-type: text/xml; charset=utf-8`; return ` 1 0x80048800 ${errorCode} NOBELLIUM 16.0.30846.6 S:Sender wst:FailedAuthentication Authentication Failure ${errorCode} 0x80041012 ${errorText} `; } function generateSuccessResponse(requestBody, userId, email, firstName, lastName) { const now = new Date(); const tomorrow = new Date(now.getTime() + 24 * 60 * 60 * 1000); const createdTime = formatDateTime(now); const expiresTime = formatDateTime(tomorrow); const puid = crypto.randomBytes(16).toString('hex').toUpperCase(); const cid = crypto.randomBytes(8).toString('hex').toUpperCase(); const safeFirstName = firstName || email.split('@')[0] || "User"; const safeLastName = lastName || "User"; const clientIp = getClientIP(); const rstRegex = //gi; const responses = []; let match; let foundRst = false; let rstIndex = 0; while ((match = rstRegex.exec(requestBody)) !== null) { foundRst = true; const rstBlock = match[0]; const addressMatch = rstBlock.match(/(.*?)<\/wsa:Address>/i); let appliesTo = addressMatch ? addressMatch[1] : "urn:passport:compact"; const policyMatch = rstBlock.match(/ http://Passport.NET/STS ${token} `; } else { let tokenValue = `t=${token}`; if (needsProofToken) { tokenValue += `&p=profile`; } requestedSecurityToken = ` ${tokenValue} `; } let responseXml = ` ${tokenType} ${appliesTo} ${createdTime} ${expiresTime} ${requestedSecurityToken} `; if (needsProofToken || isLegacy) { responseXml += ` ${binarySecret} `; } responseXml += ` `; responses.push(responseXml); rstIndex++; } if (!foundRst) { const defaultToken = generateRandomToken(userId, "urn:passport:compact", false); responses.push(` urn:passport:compact t=${defaultToken} ${createdTime} ${expiresTime} `); } headers = `Status: 200 OK Content-type: text/xml; charset=utf-8 Set-Cookie: RST_Auth=${userId}; path=/; HttpOnly Set-Cookie: RST_Email=${email}; path=/`; return ` 1 ${puid} 16.000.26889.00 3.100.2179.0 16.000.26208.0 0x48803 0x0 NOBELLIUM 16.0.30846.6 MSFT; path=/; domain=.msn.com; expires=Wed, 30-Dec-2037 16:00:00 GMT MSFT; path=/; domain=.live.com; expires=Wed, 30-Dec-2037 16:00:00 GMT MSFT true ${cid} ${email} US 1033 ${safeFirstName} ${safeLastName} 40100643 ${clientIp} ${cid} ${responses.join('\n ')} `; } function rstHandler() { try { // Get POST data let requestBody = ''; if (request_headers.post_data) { if (Buffer.isBuffer(request_headers.post_data)) { requestBody = request_headers.post_data.toString('utf8'); } else if (typeof request_headers.post_data === 'string') { requestBody = request_headers.post_data; } else if (typeof request_headers.post_data === 'object') { requestBody = JSON.stringify(request_headers.post_data); } } else { debug("No post_data found. Available keys:", Object.keys(request_headers)); return generateErrorResponse("0x80048820", "No POST data received"); } if (!requestBody || requestBody.trim() === '') { debug("Empty request body"); return generateErrorResponse("0x80048820", "Empty request body"); } // Authentication let email = extractXmlValue(requestBody, 'Username'); let password = extractXmlValue(requestBody, 'Password'); let userId = null; let userEmail = null; let firstName = "User"; let lastName = "User"; if ((!email || !password) && requestBody.includes('CipherValue')) { debug("No username/password found, trying token authentication..."); const token = extractTokenFromCipherValue(requestBody); if (token) { const tokenValidation = validateTokenAndGetUser(token); if (tokenValidation.success) { userId = tokenValidation.userId; userEmail = tokenValidation.email; debug(`Token authentication successful for: ${userEmail} (${userId})`); if (request_headers.cookie) { const cookieEmail = getCookie(request_headers.cookie, 'RST_Email'); const cookieUsername = getCookie(request_headers.cookie, 'rst_username'); if (cookieEmail) userEmail = cookieEmail; if (cookieUsername) firstName = cookieUsername; } } else { debug("Token validation failed"); return generateErrorResponse("0x80048821", "Invalid token"); } } else { debug("No token found in CipherValue"); return generateErrorResponse("0x80048820", "Missing credentials/token"); } } else if (email && password) { debug(`Extracted - Email: ${email}, Password: ${password ? '***' : 'empty'}`); if (email && email.indexOf('@') < 0) { const domain = minisrv_config.config.domain_name || 'minisrv.local'; email = `${email}@${domain}`; } userEmail = email; firstName = email.split('@')[0]; userId = crypto.createHash('md5').update(email).digest('hex'); const validAuth = validateCredentials(email, password); if (!validAuth) { debug("Invalid credentials"); return generateErrorResponse("0x80048821", "Invalid credentials"); } else { debug(`Authentication successful for: ${userEmail} (${userId})`); } } else { debug("Missing both credentials and token"); return generateErrorResponse("0x80048820", "Missing credentials/token"); } if (!userId || !userEmail) { debug("Failed to get user identity"); return generateErrorResponse("0x80048821", "User identity not found"); } const cookieHeaders = [ setCookie('rst_email', userEmail, { path: '/' }), setCookie('rst_username', firstName, { path: '/' }), setCookie('rst_authenticated', 'true', { path: '/', expires: 'Wed, 30-Dec-2037 16:00:00 GMT' }) ]; const response = generateSuccessResponse(requestBody, userId, userEmail, firstName, lastName); for (const cookie of cookieHeaders) { headers += `\nSet-Cookie: ${cookie}`; } return response; } catch (error) { console.error("RST Handler Error:", error); console.error("Error stack:", error.stack); return generateErrorResponse("0x80048820", `Internal error: ${error.message}`); } } function validateCredentials(email, password) { username = email.split('@')[0]; result_ary = session_data.findAccountByUsername(username); if (result_ary[0]) { if (!socket.ssid) { socket.ssid = result_ary[1]; // second arg should handle secondary users session_data.setSSID(socket.ssid, result_ary[2]); } return session_data.validateUserPassword(password); } return false; } let result = rstHandler(); if (result) { data = result; }