diff options
| author | Gab <24553253+gabrix73@users.noreply.github.com> | 2025-10-25 02:41:04 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-10-25 02:41:04 +0200 |
| commit | e2c8b1fbd6b9e066975278cd8c291a4ded1d3818 (patch) | |
| tree | fd020ff8c5e8ea6c09e97c0e29afc37fe431e17e | |
| parent | 99111c334ab3623e99c18c86ef56261b2148aa8c (diff) | |
| download | m2usenet-and-mail2news-e2c8b1fbd6b9e066975278cd8c291a4ded1d3818.tar.gz m2usenet-and-mail2news-e2c8b1fbd6b9e066975278cd8c291a4ded1d3818.tar.xz m2usenet-and-mail2news-e2c8b1fbd6b9e066975278cd8c291a4ded1d3818.zip | |
Update print statement from 'Hello' to 'Goodbye'
| -rw-r--r-- | index.php | 1190 |
1 files changed, 961 insertions, 229 deletions
@@ -1,12 +1,27 @@ <?php // /var/www/m2usenet/index.php +// m2usenet v2.0.0 - Hardened Gateway Interface + +// Start session for CSRF token +if (session_status() === PHP_SESSION_NONE) { + ini_set('session.cookie_httponly', 1); + ini_set('session.cookie_secure', 1); + ini_set('session.use_strict_mode', 1); + session_start(); +} + +// Generate CSRF token +if (!isset($_SESSION['csrf_token'])) { + $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); +} +$csrfToken = $_SESSION['csrf_token']; ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>Mail2Usenet Gateway</title> + <title>m2usenet Gateway v2.0</title> <style> :root { --background: #f9f9f9; @@ -19,8 +34,11 @@ --input-bg: #fff; --input-readonly: #eee; --progress-bg: #ddd; + --success: #28a745; + --warning: #ffc107; + --error: #dc3545; } - + .dark-theme { --background: #1a1a1a; --card-bg: #2c2c2c; @@ -33,34 +51,35 @@ --input-readonly: #2a2a2a; --progress-bg: #444; } - - body { - background: var(--background); - color: var(--text); - font-family: sans-serif; - margin: 0; - padding: 0; + + body { + background: var(--background); + color: var(--text); + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + margin: 0; + padding: 0; transition: background 0.3s ease; } - - .container { - max-width: 800px; - margin: auto; - padding: 20px; + + .container { + max-width: 800px; + margin: auto; + padding: 20px; } - + header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; } - + .theme-toggle { display: flex; align-items: center; + gap: 8px; } - + .toggle { position: relative; width: 60px; @@ -71,7 +90,7 @@ cursor: pointer; transition: 0.3s; } - + .toggle:before { content: ''; position: absolute; @@ -83,19 +102,81 @@ left: 2px; transition: 0.3s; } - + input[type="checkbox"]:checked + .toggle { background: #4caf50; } - + input[type="checkbox"]:checked + .toggle:before { transform: translateX(30px); } - + input[type="checkbox"] { display: none; } - + + /* Notification System */ + .notification { + position: fixed; + top: 20px; + right: 20px; + max-width: 400px; + padding: 15px 20px; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0,0,0,0.3); + z-index: 10000; + animation: slideIn 0.3s ease; + display: flex; + align-items: center; + gap: 10px; + } + + .notification.success { + background: var(--success); + color: white; + } + + .notification.error { + background: var(--error); + color: white; + } + + .notification.warning { + background: var(--warning); + color: #333; + } + + .notification.info { + background: #007bff; + color: white; + } + + @keyframes slideIn { + from { + transform: translateX(400px); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } + } + + .notification-icon { + font-size: 1.5em; + } + + .notification-close { + margin-left: auto; + cursor: pointer; + font-size: 1.2em; + opacity: 0.8; + } + + .notification-close:hover { + opacity: 1; + } + .section-info { background: var(--card-bg); border-left: 4px solid var(--primary); @@ -104,108 +185,145 @@ border-radius: 0 4px 4px 0; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } - + .tabs { display: flex; flex-direction: column; gap: 8px; margin-bottom: 15px; } - - .tabs button { - display: block; - width: 100%; - padding: 12px; - border: none; - background: var(--tab-bg); + + .tabs button { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + padding: 12px 15px; + border: none; + background: var(--tab-bg); color: var(--text); - cursor: pointer; + cursor: pointer; border-radius: 4px; font-weight: bold; transition: background 0.2s, transform 0.1s; + position: relative; } - + .tabs button:hover { background: var(--primary); color: white; } - + .tabs button:active { transform: scale(0.98); } - - .tab-content { - display: none; - padding: 20px; - background: var(--card-bg); - border: 1px solid var(--border); + + .tabs button.completed::after { + content: 'โ'; + position: absolute; + right: 15px; + background: var(--success); + color: white; + width: 24px; + height: 24px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 0.9em; + } + + .tab-content { + display: none; + padding: 20px; + background: var(--card-bg); + border: 1px solid var(--border); border-radius: 4px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); margin-bottom: 20px; } - - .tab-content.active { - display: block; + + .tab-content.active { + display: block; } - - label { - display: block; - margin-top: 15px; - font-weight: bold; + + label { + display: block; + margin-top: 15px; + font-weight: bold; margin-bottom: 5px; } - - input, textarea, select { - width: 100%; - padding: 10px; - margin-top: 5px; - box-sizing: border-box; + + input, textarea, select { + width: 100%; + padding: 10px; + margin-top: 5px; + box-sizing: border-box; border: 1px solid var(--border); border-radius: 4px; background: var(--input-bg); color: var(--text); + font-family: inherit; } - - input[readonly] { - background: var(--input-readonly); + + input:focus, textarea:focus, select:focus { + outline: none; + border-color: var(--primary); + box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.2); + } + + input.error, textarea.error { + border-color: var(--error); + } + + input[readonly], textarea[readonly] { + background: var(--input-readonly); cursor: not-allowed; + border: 2px solid #999; } - - button { - margin-top: 15px; - padding: 12px 20px; + + button { + margin-top: 15px; + padding: 12px 20px; background: var(--primary); color: white; border: none; border-radius: 4px; cursor: pointer; font-weight: bold; - transition: background 0.2s; + transition: background 0.2s, opacity 0.2s; + font-family: inherit; } - - button:hover { + + button:hover:not(:disabled) { background: var(--primary-hover); } - - .progress-bar { - width: 100%; - background: var(--progress-bg); - border-radius: 5px; - overflow: hidden; - height: 20px; - margin-top: 10px; - } - - .progress-bar-inner { - height: 100%; - width: 0; - background: var(--primary); - text-align: center; - color: white; - line-height: 20px; + + button:disabled { + opacity: 0.6; + cursor: not-allowed; + } + + .progress-bar { + width: 100%; + background: var(--progress-bg); + border-radius: 5px; + overflow: hidden; + height: 20px; + margin-top: 10px; + } + + .progress-bar-inner { + height: 100%; + width: 0; + background: var(--primary); + text-align: center; + color: white; + line-height: 20px; transition: width 0.3s; + font-size: 0.85em; } - + .output-field { margin-top: 15px; padding: 10px; @@ -213,9 +331,65 @@ border: 1px solid var(--border); border-radius: 4px; word-break: break-all; - min-height: 20px; + min-height: 30px; + font-family: monospace; + font-size: 0.9em; + } + + .output-field.empty { + color: #999; + font-style: italic; + } + + .gateway-info { + background: var(--card-bg); + border: 1px solid var(--border); + padding: 15px; + margin: 15px 0; + border-radius: 4px; + border-left: 4px solid #007bff; + } + + .gateway-item { + display: flex; + align-items: center; + padding: 8px 0; + border-bottom: 1px solid var(--border); + } + + .gateway-item:last-child { + border-bottom: none; + } + + .gateway-priority { + background: #007bff; + color: white; + padding: 2px 8px; + border-radius: 12px; + font-size: 0.8em; + margin-right: 10px; + min-width: 70px; + text-align: center; + } + + .gateway-priority.secondary { + background: #6c757d; + } + + .gateway-priority.fallback { + background: #ffc107; + color: #333; } - + + .gateway-address { + font-family: monospace; + background: var(--input-readonly); + padding: 4px 8px; + border-radius: 4px; + flex: 1; + font-size: 0.85em; + } + footer { text-align: center; margin-top: 40px; @@ -224,25 +398,31 @@ font-size: 0.9em; color: #888; } - + .footer-links { display: flex; justify-content: center; gap: 20px; margin-top: 10px; } - + .footer-links a { color: var(--primary); text-decoration: none; transition: color 0.2s; } - + .footer-links a:hover { color: var(--primary-hover); text-decoration: underline; } - + + .step-indicator { + font-size: 0.85em; + color: #666; + margin-bottom: 10px; + } + @media (max-width: 600px) { .container { padding: 10px; @@ -254,16 +434,29 @@ flex-direction: column; gap: 10px; } + .gateway-item { + flex-direction: column; + align-items: flex-start; + gap: 5px; + } + .gateway-address { + width: 100%; + } + .notification { + right: 10px; + left: 10px; + max-width: none; + } } </style> <!-- Include both TweetNaCl libraries --> -<script src="https://cdn.jsdelivr.net/npm/tweetnacl@1.0.3/nacl.min.js"></script> -<script src="https://cdn.jsdelivr.net/npm/tweetnacl-util@0.15.1/nacl-util.min.js"></script> + <script src="https://cdn.jsdelivr.net/npm/tweetnacl@1.0.3/nacl.min.js"></script> + <script src="https://cdn.jsdelivr.net/npm/tweetnacl-util@0.15.1/nacl-util.min.js"></script> </head> <body> <div class="container"> <header> - <h1>Mail2Usenet Gateway</h1> + <h1>m2usenet Gateway v2.0</h1> <div class="theme-toggle"> <span>๐</span> <input type="checkbox" id="themeToggle"> @@ -273,81 +466,129 @@ </header> <div class="tabs"> - <button onclick="showTab('pow')">1. Generate Hashcash Token</button> - <button onclick="showTab('sign')">2. Sign Message</button> - <button onclick="showTab('send')">3. Send Message</button> + <button id="tabBtn1" onclick="showTab('pow')"> + <span>1. Generate Hashcash Token</span> + </button> + <button id="tabBtn2" onclick="showTab('sign')"> + <span>2. Sign Message</span> + </button> + <button id="tabBtn3" onclick="showTab('send')"> + <span>3. Send Message</span> + </button> </div> <div id="pow" class="tab-content active"> <h2>Proof-of-Work Token</h2> - + <div class="section-info"> + <p><strong>About m2usenet:</strong> This application is a privacy-focused gateway that sends your messages to Usenet newsgroups via our mail2news gateways on the onion network. No access logs are collected, and your messages are routed through secure gateways.</p> <p><strong>What is this?</strong> This step generates a "proof-of-work" token (hashcash) that prevents spam by requiring your computer to perform some calculations. This is similar to how cryptocurrencies work - you need to "mine" a valid token before sending a message.</p> <p><strong>How to use:</strong> Enter your email address, select the difficulty level (higher bits = longer processing time), and click "Generate Token". Your browser will mine a valid token that will be required in the next steps.</p> </div> - - <label>Email (resource): <input type="email" id="hcEmail" placeholder="your@email.com"></label> + + <label>Email (resource): <input type="email" id="hcEmail" placeholder="your@email.com" required></label> <label>Difficulty (bits): <select id="hcBits"> - <option value="24">24 bits (faster, suitable for most devices)</option> - <option value="28">28 bits (medium difficulty)</option> - <option value="32">32 bits (slower, more secure)</option> + <option value="16">16 bits (very fast, ~instant - recommended for Tor Browser)</option> + <option value="20" selected>20 bits (fast, ~few seconds)</option> + <option value="24">24 bits (medium, ~30-60 seconds)</option> + <option value="28">28 bits (slow, ~several minutes)</option> </select> </label> <button id="genTokenBtn">Generate Token</button> - <div class="progress-bar"><div id="tokenProgress" class="progress-bar-inner">0</div></div> + <div class="progress-bar"><div id="tokenProgress" class="progress-bar-inner">Ready</div></div> <label>Generated Token:</label> - <div id="tokenOutput" class="output-field"></div> + <div id="tokenOutput" class="output-field empty">Token will appear here after generation</div> </div> <div id="sign" class="tab-content"> <h2>Ed25519 Digital Signature</h2> - + <div class="section-info"> <p><strong>What is this?</strong> This step creates a digital signature for your message using the Ed25519 cryptographic algorithm. This signature helps verify that the message was sent by you and hasn't been tampered with.</p> <p><strong>How to use:</strong> First, generate a key pair (this creates a public and private key). Then, write your message and click "Sign Message". This will create a digital signature that will be attached to your post.</p> </div> - + <label>Email used for PoW:</label> <input type="text" id="readonlyEmailSign" readonly> <label>Message to Sign:</label> - <textarea id="messageToSign" rows="6" placeholder="Write your message here..."></textarea> + <textarea id="messageToSign" rows="6" placeholder="Write your message here..." required></textarea> <button id="genKeyBtn">Generate Key Pair</button> - <button id="signMsgBtn">Sign Message</button> - + <button id="signMsgBtn" disabled>Sign Message</button> + <label>Generated Public Key:</label> - <div id="pubKeyOutput" class="output-field"></div> + <div id="pubKeyOutput" class="output-field empty">Public key will appear here</div> <label>Generated Signature:</label> - <div id="signatureOutput" class="output-field"></div> + <div id="signatureOutput" class="output-field empty">Signature will appear here</div> </div> <div id="send" class="tab-content"> <h2>Send Message</h2> - + <div class="section-info"> - <p><strong>What is this?</strong> This final step sends your signed message to the Usenet network through our NNTP server running on the Tor network at <code>peannyjkqwqfynd24p6dszvtchkq7hfkwymi5by5y332wmosy5dwfaqd.onion</code>.</p> - <p><strong>How to use:</strong> Complete the form below with your name, the newsgroups you want to post to (max 3), subject, and verify that your message and authentication details are correct. Then click "Send" to post your message.</p> + <p><strong>What is this?</strong> This final step sends your signed message to the Usenet network via mail2news gateways. m2usenet v2.0.0 uses Tor for all connections with automatic fallback.</p> + + <div class="gateway-info"> + <h4>๐ Full Onion Network Path</h4> + <p style="margin-bottom: 15px; font-size: 0.95em;">Your message travels entirely within the Tor network through these three components:</p> + + <div class="gateway-item"> + <span class="gateway-priority">Step 1</span> + <div style="flex: 1;"> + <strong>SMTP Relay: <a href="https://github.com/gabrix73/pluto2" target="_blank" style="color: #007bff; text-decoration: none;">Pluto2</a></strong> + <p style="font-size: 0.85em; margin: 5px 0 0 0; color: #666;">Specialized SMTP relay operating exclusively over Tor, providing robust protection against traffic analysis, timing attacks, and metadata correlation.</p> + </div> + </div> + + <div class="gateway-item"> + <span class="gateway-priority">Step 2</span> + <div style="flex: 1;"> + <strong>Mail2News Gateway</strong> + <div class="gateway-address" style="margin-top: 5px;">mail2news@xilb7y4kj6u6qfo45o3yk2kilfv54ffukzei3puonuqlncy7cn2afwyd.onion</div> + <p style="font-size: 0.85em; margin: 5px 0 0 0; color: #666;">Converts email to Usenet posts while maintaining full anonymity within the onion network.</p> + </div> + </div> + + <div class="gateway-item"> + <span class="gateway-priority">Step 3</span> + <div style="flex: 1;"> + <strong>NNTP Server</strong> + <div class="gateway-address" style="margin-top: 5px;">nntp://peannyjkqwqfynd24p6dszvtchkq7hfkwymi5by5y332wmosy5dwfaqd.onion</div> + <p style="font-size: 0.85em; margin: 5px 0 0 0; color: #666;">Final destination for Usenet Tor hidden service.</p> + </div> + </div> + + <p style="margin-top: 15px; padding: 10px; background: rgba(0,123,255,0.1); border-radius: 4px; font-size: 0.9em;"> + <strong>๐ Privacy Guarantee:</strong> Your message never leaves the Tor network until it reaches the NNTP server. All three components (Pluto2 โ Mail2News โ NNTP) operate as Tor hidden services, providing end-to-end anonymity with protection against traffic analysis, timing attacks, and metadata correlation. + </p> + </div> + + <p><strong>How to use:</strong> Complete the form below with your name, newsgroups (max 3), subject, and verify that your message and authentication details are correct. Then click "Send" to post your message via the gateway system.</p> </div> - + <label>Email used for PoW:</label> <input type="text" id="readonlyEmailSend" readonly> + <form id="sendForm" method="POST" action="send.php"> + <input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($csrfToken); ?>"> <label>From (Name): <input type="text" id="fromName" required placeholder="Your Name"></label> <input type="hidden" name="from" id="fromFull"> - <label>Newsgroups (max 3, comma separated): <input type="text" name="newsgroups" required placeholder="e.g. alt.privacy, comp.security"></label> - <label>Subject: <input type="text" name="subject" required placeholder="Message Subject"></label> - <label>References (optional): <input type="text" name="references" placeholder="Message-ID of post you're replying to"></label> + <label>Newsgroups (max 3, comma separated): + <input type="text" name="newsgroups" id="newsgroups" required placeholder="e.g. alt.privacy, comp.security"> + </label> + <label>Subject: <input type="text" name="subject" id="subject" required placeholder="Message Subject"></label> + <label>References (optional): <input type="text" name="references" id="references" placeholder="Message-ID of post you're replying to"></label> <label>X-Hashcash Token: <input type="text" name="xhashcash" id="hcToken" required readonly></label> <label>Message:</label> - <textarea name="message" id="messageContent" rows="8" required placeholder="Your message content will appear here after signing..."></textarea> + <textarea name="message" id="messageContent" rows="8" required placeholder="Your message will appear here after signing..."></textarea> <input type="hidden" name="x-ed25519-pub" id="x-ed25519-pub"> <input type="hidden" name="x-ed25519-sig" id="x-ed25519-sig"> - <button type="submit">Send Message</button> + <button type="submit" id="sendBtn">Send via m2usenet Gateway</button> </form> </div> - + <footer> - <div>Mail2Usenet Gateway ยฉ 2025 - Privacy-focused communication tool</div> + <div>m2usenet Gateway v2.0.0 ยฉ 2025 - Privacy-focused Usenet posting tool</div> <div class="footer-links"> <a href="https://yamn.virebent.art">Home</a> <a href="mailto:%69%6E%66%6F%40%76%69%72%65%62%65%6E%74%2E%61%72%74">Contact</a> @@ -356,7 +597,55 @@ </footer> </div> +<!-- SCRIPT 1: Core Functions (Theme, Notifications, POW, Signing) --> <script> +// Notification system +function showNotification(message, type = 'info', duration = 5000) { + const icons = { + success: 'โ', + error: 'โ', + warning: 'โ ', + info: 'โน' + }; + + const notification = document.createElement('div'); + notification.className = `notification ${type}`; + notification.innerHTML = ` + <span class="notification-icon">${icons[type] || icons.info}</span> + <span class="notification-message">${message}</span> + <span class="notification-close" onclick="this.parentElement.remove()">ร</span> + `; + + document.body.appendChild(notification); + + if (duration > 0) { + setTimeout(() => { + if (notification.parentElement) { + notification.remove(); + } + }, duration); + } +} + +// State management +const appState = { + step1Complete: false, + step2Complete: false, + step3Complete: false +}; + +function updateTabIndicators() { + if (appState.step1Complete) { + document.getElementById('tabBtn1').classList.add('completed'); + } + if (appState.step2Complete) { + document.getElementById('tabBtn2').classList.add('completed'); + } + if (appState.step3Complete) { + document.getElementById('tabBtn3').classList.add('completed'); + } +} + // Theme toggle functionality document.getElementById('themeToggle').addEventListener('change', function() { document.body.classList.toggle('dark-theme', this.checked); @@ -368,12 +657,13 @@ document.addEventListener('DOMContentLoaded', function() { const darkTheme = localStorage.getItem('darkTheme') === 'true'; document.getElementById('themeToggle').checked = darkTheme; document.body.classList.toggle('dark-theme', darkTheme); - + // Check if nacl is properly loaded if (typeof nacl === 'undefined') { - console.error("TweetNaCl library not loaded properly!"); + showNotification('Cryptography library failed to load. Please refresh the page.', 'error', 0); + return; } - + // Initialize nacl.util if it doesn't exist if (typeof nacl.util === 'undefined') { nacl.util = { @@ -398,6 +688,8 @@ document.addEventListener('DOMContentLoaded', function() { } }; } + + showNotification('m2usenet Gateway v2.0 ready', 'success', 3000); }); function showTab(id) { @@ -405,156 +697,516 @@ function showTab(id) { document.getElementById(id).classList.add('active'); } +// Step 1: Generate Hashcash Token let keyPair = null; +let workersSupported = true; + +// Check if Web Workers are supported (may be blocked in Tor Browser) +try { + if (typeof Worker === 'undefined') { + workersSupported = false; + console.warn('[WARN] Web Workers not supported in this browser'); + } +} catch (e) { + workersSupported = false; + console.warn('[WARN] Web Workers check failed:', e); +} + +// SHA-1 implementation for fallback mode +function sha1(str) { + function rotate_left(n, s) { + return (n << s) | (n >>> (32 - s)); + } + + function cvt_hex(val) { + let str = ''; + for (let i = 7; i >= 0; i--) { + const v = (val >>> (i * 4)) & 0x0f; + str += v.toString(16); + } + return str; + } + + function utf8Encode(str) { + return unescape(encodeURIComponent(str)); + } + + let blockstart; + let i, j; + const W = new Array(80); + let H0 = 0x67452301; + let H1 = 0xEFCDAB89; + let H2 = 0x98BADCFE; + let H3 = 0x10325476; + let H4 = 0xC3D2E1F0; + let A, B, C, D, E; + let temp; + + str = utf8Encode(str); + const str_len = str.length; + + const word_array = []; + for (i = 0; i < str_len - 3; i += 4) { + j = str.charCodeAt(i) << 24 | str.charCodeAt(i + 1) << 16 | str.charCodeAt(i + 2) << 8 | str.charCodeAt(i + 3); + word_array.push(j); + } + + switch (str_len % 4) { + case 0: + i = 0x080000000; + break; + case 1: + i = str.charCodeAt(str_len - 1) << 24 | 0x0800000; + break; + case 2: + i = str.charCodeAt(str_len - 2) << 24 | str.charCodeAt(str_len - 1) << 16 | 0x08000; + break; + case 3: + i = str.charCodeAt(str_len - 3) << 24 | str.charCodeAt(str_len - 2) << 16 | str.charCodeAt(str_len - 1) << 8 | 0x80; + break; + } + + word_array.push(i); + + while ((word_array.length % 16) != 14) word_array.push(0); + + word_array.push(str_len >>> 29); + word_array.push((str_len << 3) & 0x0ffffffff); + + for (blockstart = 0; blockstart < word_array.length; blockstart += 16) { + for (i = 0; i < 16; i++) W[i] = word_array[blockstart + i]; + for (i = 16; i <= 79; i++) W[i] = rotate_left(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16], 1); + + A = H0; + B = H1; + C = H2; + D = H3; + E = H4; + + for (i = 0; i <= 19; i++) { + temp = (rotate_left(A, 5) + ((B & C) | (~B & D)) + E + W[i] + 0x5A827999) & 0x0ffffffff; + E = D; + D = C; + C = rotate_left(B, 30); + B = A; + A = temp; + } + + for (i = 20; i <= 39; i++) { + temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0x6ED9EBA1) & 0x0ffffffff; + E = D; + D = C; + C = rotate_left(B, 30); + B = A; + A = temp; + } + + for (i = 40; i <= 59; i++) { + temp = (rotate_left(A, 5) + ((B & C) | (B & D) | (C & D)) + E + W[i] + 0x8F1BBCDC) & 0x0ffffffff; + E = D; + D = C; + C = rotate_left(B, 30); + B = A; + A = temp; + } + + for (i = 60; i <= 79; i++) { + temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0xCA62C1D6) & 0x0ffffffff; + E = D; + D = C; + C = rotate_left(B, 30); + B = A; + A = temp; + } + + H0 = (H0 + A) & 0x0ffffffff; + H1 = (H1 + B) & 0x0ffffffff; + H2 = (H2 + C) & 0x0ffffffff; + H3 = (H3 + D) & 0x0ffffffff; + H4 = (H4 + E) & 0x0ffffffff; + } + + return cvt_hex(H0) + cvt_hex(H1) + cvt_hex(H2) + cvt_hex(H3) + cvt_hex(H4); +} + +// Single-threaded mining fallback +function mineSingleThread(prefix, targetZeros, progressCallback, foundCallback) { + console.log('[INFO] Using single-threaded mining (Web Workers not available)'); + const target = '0'.repeat(targetZeros); + let nonce = 0; + let checked = 0; + const batchSize = 100; // Check 100 hashes before updating UI + + function mineNextBatch() { + const endNonce = nonce + batchSize; + + while (nonce < endNonce) { + const token = prefix + nonce; + const hash = sha1(token); + checked++; + + if (hash.startsWith(target)) { + foundCallback(nonce, checked); + return; + } + + nonce++; + } + + // Update progress + progressCallback(checked); + + // Continue mining in next tick to avoid blocking UI + setTimeout(mineNextBatch, 0); + } + + mineNextBatch(); +} + +document.getElementById('genTokenBtn').onclick = () => { + const email = document.getElementById('hcEmail').value.trim(); + const bits = parseInt(document.getElementById('hcBits').value); + + // Validation + if (!email) { + showNotification('โ Please enter your email address first!', 'warning'); + document.getElementById('hcEmail').classList.add('error'); + document.getElementById('hcEmail').focus(); + return; + } + + // Email format validation + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(email)) { + showNotification('โ Please enter a valid email address!', 'warning'); + document.getElementById('hcEmail').classList.add('error'); + document.getElementById('hcEmail').focus(); + return; + } + + document.getElementById('hcEmail').classList.remove('error'); + + // Start generation + showNotification(`๐จ Starting token generation (${bits} bits)... This may take a while.`, 'info'); + + // Update UI to show processing + const btn = document.getElementById('genTokenBtn'); + btn.disabled = true; + btn.textContent = 'Mining Token...'; + + const now = new Date(); + const timestamp = now.getUTCFullYear().toString().slice(2) + + ("0"+(now.getUTCMonth()+1)).slice(-2) + + ("0"+now.getUTCDate()).slice(-2) + + ("0"+now.getUTCHours()).slice(-2) + + ("0"+now.getUTCMinutes()).slice(-2) + + ("0"+now.getUTCSeconds()).slice(-2); + const ext = ""; + const rand = Math.floor(Math.random()*1e6); + const prefix = `1:${bits}:${timestamp}:${email}:${ext}:${rand}:`; + const zeros = bits/4; + + const progBar = document.getElementById('tokenProgress'); + progBar.style.width = '0%'; + progBar.innerText = 'Mining...'; + + let totalChecked = 0; + let startTime = Date.now(); + + function updateProgress(checked) { + totalChecked += checked; + const elapsed = (Date.now() - startTime) / 1000; + const hashRate = Math.round(totalChecked / elapsed); + progBar.style.width = Math.min(95, (totalChecked / 10000)) + '%'; + progBar.innerText = `${totalChecked} hashes (${hashRate}/s)`; + } + + function onFound(nonce, totalHashes) { + const token = prefix + nonce; + + // Update UI + const outputField = document.getElementById('tokenOutput'); + outputField.innerText = token; + outputField.classList.remove('empty'); + document.getElementById('hcToken').value = token; + + const fromName = document.getElementById('fromName').value || 'Anonymous'; + document.getElementById('fromFull').value = `${fromName} <${email}>`; + document.getElementById('readonlyEmailSign').value = email; + document.getElementById('readonlyEmailSend').value = email; + + // Restore button + btn.disabled = false; + btn.textContent = 'Generate Token'; + progBar.style.width = '100%'; + progBar.innerText = `Complete! (${totalHashes} hashes)`; + + // Mark step as complete + appState.step1Complete = true; + updateTabIndicators(); + + const elapsed = ((Date.now() - startTime) / 1000).toFixed(1); + showNotification(`โ Token generated in ${elapsed}s! You can now proceed to sign your message.`, 'success', 8000); + + console.log(`[INFO] Token mined: ${totalHashes} hashes in ${elapsed}s`); + + // Auto-switch to next tab after a delay + setTimeout(() => { + showTab('sign'); + }, 2000); + } + + // Try Web Workers first, fallback to single-thread + if (workersSupported) { + console.log('[INFO] Attempting multi-threaded mining with Web Workers'); + + try { + const cores = navigator.hardwareConcurrency || 2; + console.log(`[INFO] Using ${cores} worker threads`); + + let found = false; + let coreChecked = Array(cores).fill(0); + const workers = []; + + function updateCombinedProgress() { + const sum = coreChecked.reduce((a,b)=>a+b,0); + updateProgress(sum - totalChecked); + } + + for (let i = 0; i < cores; i++) { + try { + const w = new Worker('powWorker.js'); + + w.postMessage({ prefix, targetZeros: zeros, startNonce: i, step: cores }); + + w.onmessage = e => { + if (found) return; + + if (e.data.type === 'found') { + found = true; + workers.forEach(x => x.terminate()); + onFound(e.data.nonce, e.data.checked); + } + + if (e.data.type === 'progress') { + coreChecked[i] = e.data.checked; + updateCombinedProgress(); + } + }; + + w.onerror = (err) => { + console.error(`[ERROR] Worker ${i} error:`, err); + if (i === 0) { + // First worker failed, fallback to single-thread + console.warn('[WARN] Worker failed, falling back to single-thread'); + workers.forEach(x => x.terminate()); + mineSingleThread(prefix, zeros, updateProgress, onFound); + } + }; + + workers.push(w); + } catch (err) { + console.error(`[ERROR] Failed to create worker ${i}:`, err); + if (i === 0) { + // Fallback to single-thread + mineSingleThread(prefix, zeros, updateProgress, onFound); + return; + } + } + } + + // Timeout safety (10 minutes for 24 bits) + setTimeout(() => { + if (!found) { + console.warn('[WARN] Mining timeout, this is taking too long'); + showNotification('โ Mining is taking longer than expected. Consider using a lower difficulty.', 'warning', 10000); + } + }, 600000); + + } catch (err) { + console.error('[ERROR] Worker mining failed:', err); + showNotification('โ Multi-threaded mining failed, using fallback mode', 'warning'); + mineSingleThread(prefix, zeros, updateProgress, onFound); + } + } else { + // Single-threaded fallback + mineSingleThread(prefix, zeros, updateProgress, onFound); + } +}; + +// Step 2: Generate Key Pair document.getElementById('genKeyBtn').onclick = function() { try { - // Make sure nacl is loaded and the sign function exists - if (typeof nacl === 'undefined') { - throw new Error("TweetNaCl library is not loaded"); - } - - if (typeof nacl.sign === 'undefined' || typeof nacl.sign.keyPair !== 'function') { - throw new Error("TweetNaCl's sign functionality is not available"); + // Make sure nacl is loaded + if (typeof nacl === 'undefined' || typeof nacl.sign === 'undefined') { + throw new Error("Cryptography library not loaded"); } - + + showNotification('๐ Generating Ed25519 key pair...', 'info'); + keyPair = nacl.sign.keyPair(); const pubKey = keyPair.publicKey; - - // Use our nacl.util implementation to encode the public key - const pubB64 = typeof nacl.util.encodeBase64 === 'function' + + const pubB64 = typeof nacl.util.encodeBase64 === 'function' ? nacl.util.encodeBase64(pubKey) : btoa(Array.from(new Uint8Array(pubKey)).map(byte => String.fromCharCode(byte)).join('')); - - document.getElementById('pubKeyOutput').innerText = pubB64; + + const pubOutput = document.getElementById('pubKeyOutput'); + pubOutput.innerText = pubB64; + pubOutput.classList.remove('empty'); document.getElementById('x-ed25519-pub').value = pubB64; - alert("Key pair generated successfully! You can now sign your message."); + + // Enable sign button + document.getElementById('signMsgBtn').disabled = false; + + showNotification('โ Key pair generated successfully! Now write your message and click "Sign Message".', 'success', 6000); } catch (error) { console.error("Error generating key pair:", error); - alert("Error generating key pair: " + error.message); + showNotification('โ Error generating key pair: ' + error.message, 'error'); } }; +// Step 2: Sign Message document.getElementById('signMsgBtn').onclick = function() { try { if (!keyPair) { - return alert("Please generate a key pair first."); + showNotification('โ Please generate a key pair first!', 'warning'); + return; } - - const msg = document.getElementById('messageToSign').value; + + const msg = document.getElementById('messageToSign').value.trim(); if (!msg) { - return alert("Please enter a message to sign."); + showNotification('โ Please write a message to sign!', 'warning'); + document.getElementById('messageToSign').classList.add('error'); + document.getElementById('messageToSign').focus(); + return; + } + + if (msg.length < 10) { + showNotification('โ Message is too short. Please write at least 10 characters.', 'warning'); + return; } - + + document.getElementById('messageToSign').classList.remove('error'); + + showNotification('โ Signing message...', 'info'); + const msgBytes = typeof nacl.util.decodeUTF8 === 'function' ? nacl.util.decodeUTF8(msg) : new TextEncoder().encode(msg); - + const sig = nacl.sign.detached(msgBytes, keyPair.secretKey); - + const sigB64 = typeof nacl.util.encodeBase64 === 'function' ? nacl.util.encodeBase64(sig) : btoa(Array.from(new Uint8Array(sig)).map(byte => String.fromCharCode(byte)).join('')); - - document.getElementById('signatureOutput').innerText = sigB64; + + const sigOutput = document.getElementById('signatureOutput'); + sigOutput.innerText = sigB64; + sigOutput.classList.remove('empty'); document.getElementById('x-ed25519-sig').value = sigB64; + + // EXACTLY like v1.0: message + "\n\n--- Digital Signature ---\n" + signature + // Public key goes ONLY in X-Ed25519-Pub header, NOT in message body document.getElementById('messageContent').value = msg + "\n\n--- Digital Signature ---\n" + sigB64; - alert("Message signed successfully! You can now proceed to the Send Message tab."); + + console.log('[DEBUG] Message signed like v1.0, total length:', (msg + "\n\n--- Digital Signature ---\n" + sigB64).length); + + // Mark step as complete + appState.step2Complete = true; + updateTabIndicators(); + + showNotification('โ Message signed successfully! You can now send your message to Usenet.', 'success', 8000); + + // Auto-switch to send tab + setTimeout(() => { + showTab('send'); + }, 2000); } catch (error) { console.error("Error signing message:", error); - alert("Error signing message: " + error.message); + showNotification('โ Error signing message: ' + error.message, 'error'); } }; -document.getElementById('genTokenBtn').onclick = () => { - const email = document.getElementById('hcEmail').value; - const bits = parseInt(document.getElementById('hcBits').value); - if (!email) return alert("Please enter an email address."); - - // Update UI to show processing - document.getElementById('genTokenBtn').disabled = true; - document.getElementById('genTokenBtn').textContent = 'Processing...'; - - const now = new Date(); - const timestamp = now.getUTCFullYear().toString().slice(2) + - ("0"+(now.getUTCMonth()+1)).slice(-2) + - ("0"+now.getUTCDate()).slice(-2) + - ("0"+now.getUTCHours()).slice(-2) + - ("0"+now.getUTCMinutes()).slice(-2) + - ("0"+now.getUTCSeconds()).slice(-2); - const ext = ""; - const rand = Math.floor(Math.random()*1e6); - const prefix = `1:${bits}:${timestamp}:${email}:${ext}:${rand}:`; - const zeros = bits/4; - const cores = navigator.hardwareConcurrency || 2; - let found = false; - let totalChecked = Array(cores).fill(0); - const progBar = document.getElementById('tokenProgress'); - progBar.style.width = '0%'; progBar.innerText = '0'; - const workers = []; - - function updateProgress() { - const sum = totalChecked.reduce((a,b)=>a+b,0); - const pct = Math.min(100,(sum%(cores*1000))/(cores*10)); - progBar.style.width = pct + '%'; - progBar.innerText = sum; +// Step 3: Send Form +document.getElementById('sendForm').addEventListener('submit', function(e) { + // Form validation + const requiredFields = [ + {id: 'fromName', name: 'Name'}, + {id: 'newsgroups', name: 'Newsgroups'}, + {id: 'subject', name: 'Subject'}, + {id: 'hcToken', name: 'Hashcash Token'}, + {id: 'messageContent', name: 'Message'}, + {id: 'x-ed25519-pub', name: 'Public Key'}, + {id: 'x-ed25519-sig', name: 'Signature'} + ]; + + const missing = []; + requiredFields.forEach(field => { + const el = document.getElementById(field.id); + if (!el.value.trim()) { + missing.push(field.name); + el.classList.add('error'); + } else { + el.classList.remove('error'); + } + }); + + if (missing.length > 0) { + e.preventDefault(); + showNotification(`โ Missing required fields: ${missing.join(', ')}. Please complete all previous steps.`, 'warning', 8000); + return false; } - for (let i = 0; i < cores; i++) { - const w = new Worker('powWorker.js'); - w.postMessage({ prefix, targetZeros: zeros, startNonce: i, step: cores }); - w.onmessage = e => { - if (found) return; - if (e.data.type === 'found') { - found = true; - const token = prefix + e.data.nonce; - document.getElementById('tokenOutput').innerText = token; - document.getElementById('hcToken').value = token; - const fromName = document.getElementById('fromName').value; - document.getElementById('fromFull').value = `${fromName} <${email}>`; - document.getElementById('readonlyEmailSign').value = email; - document.getElementById('readonlyEmailSend').value = email; - workers.forEach(x => x.terminate()); - - // Restore button - document.getElementById('genTokenBtn').disabled = false; - document.getElementById('genTokenBtn').textContent = 'Generate Token'; - - // Show completion message - alert("Token generation complete! You can now proceed to the Sign Message tab."); - } - if (e.data.type === 'progress') { - totalChecked[i] = e.data.checked; - updateProgress(); - } - }; - workers.push(w); + // Debug: log message length + const messageLen = document.getElementById('messageContent').value.length; + console.log('[DEBUG] Final message length being sent:', messageLen); + console.log('[DEBUG] Message does NOT include signature in body - signature is in headers only'); + + if (messageLen < 10) { + e.preventDefault(); + showNotification('โ Message is too short (minimum 10 characters)', 'warning'); + return false; } -}; -document.getElementById('sendForm').addEventListener('submit', function(e) { - // Form validation - const requiredFields = ['fromName', 'hcToken', 'messageContent', 'x-ed25519-pub', 'x-ed25519-sig']; - const missing = requiredFields.filter(id => !document.getElementById(id).value.trim()); - - if (missing.length > 0) { + // Validate newsgroups + const newsgroups = document.getElementById('newsgroups').value.split(',').map(g => g.trim()).filter(g => g); + if (newsgroups.length === 0) { e.preventDefault(); - alert("Please complete all previous steps before sending your message."); + showNotification('โ Please enter at least one newsgroup!', 'warning'); return false; } - - // Log for debugging - console.log('[DEBUG] x-ed25519-pub:', document.getElementById('x-ed25519-pub').value); - console.log('[DEBUG] x-ed25519-sig:', document.getElementById('x-ed25519-sig').value); - console.log('[DEBUG] From (full):', document.getElementById('fromFull').value); - console.log('[DEBUG] Token:', document.getElementById('hcToken').value); - console.log('[DEBUG] Message:', document.getElementById('messageContent').value); - - if (confirm("Are you sure you want to send this message to the Usenet network?")) { - return true; - } else { + + if (newsgroups.length > 3) { e.preventDefault(); + showNotification('โ Maximum 3 newsgroups allowed!', 'warning'); return false; } + + // Confirm submission + const confirmMsg = `Are you ready to send your message to Usenet?\n\nNewsgroups: ${newsgroups.join(', ')}\nSubject: ${document.getElementById('subject').value}\nMessage length: ${messageLen} chars\n\nThis action cannot be undone.`; + + if (!confirm(confirmMsg)) { + e.preventDefault(); + return false; + } + + // Log for debugging + console.log('[DEBUG] Submitting message:'); + console.log('[DEBUG] From:', document.getElementById('fromFull').value); + console.log('[DEBUG] Newsgroups:', document.getElementById('newsgroups').value); + console.log('[DEBUG] Subject:', document.getElementById('subject').value); + console.log('[DEBUG] Message length:', messageLen); + console.log('[DEBUG] Hashcash:', document.getElementById('hcToken').value.substring(0, 50) + '...'); + console.log('[DEBUG] Ed25519-Pub:', document.getElementById('x-ed25519-pub').value.substring(0, 30) + '...'); + console.log('[DEBUG] Ed25519-Sig:', document.getElementById('x-ed25519-sig').value.substring(0, 30) + '...'); + + // Show sending notification + showNotification('๐ค Sending your message via m2usenet gateway...', 'info', 0); + document.getElementById('sendBtn').disabled = true; + document.getElementById('sendBtn').textContent = 'Sending...'; + + return true; }); // Handle form field updates for "from" field @@ -565,5 +1217,85 @@ document.getElementById('fromName').addEventListener('input', function() { } }); </script> + +<!-- SCRIPT 2: Newsreader Integration (Autofill from URL parameters) --> +<script> +(function() { + const u = new URLSearchParams(window.location.search); + const a = u.get('action'); + if (!a) return; + + function fill(id, v, readonly = false) { + const f = document.getElementById(id); + if (f && v) { + f.value = decodeURIComponent(v); + if (readonly) { + f.readOnly = true; + f.style.background = '#e8e8e8'; + f.style.border = '2px solid #999'; + f.style.cursor = 'not-allowed'; + } + } + } + + // NEW POST: campo newsgroups immutabile + if (a === 'new') { + const ng = u.get('newsgroups'); + if (ng) { + fill('newsgroups', ng, true); // READONLY - non puรฒ essere modificato + if (typeof showNotification === 'function') { + showNotification('๐ NEW POST: Posting to ' + decodeURIComponent(ng), 'success'); + } + } + setTimeout(() => showTab('pow'), 500); + } + + // REPLY TO POST: 4 campi immutabili (references, newsgroups, subject) + body editabile con quote + if (a === 'reply') { + const ng = u.get('newsgroups'); + const subj = u.get('subject'); + const refs = u.get('references'); + const qbody = u.get('quoted-body'); + + // Riempi i campi IMMUTABILI (readonly) + if (ng) fill('newsgroups', ng, true); // READONLY + if (subj) fill('subject', subj, true); // READONLY (con Re: prefix) + if (refs) fill('references', refs, true); // READONLY + + // Riempi il body con quote Usenet (EDITABILE) + if (qbody) { + const origFrom = u.get('original-from'); + const origDate = u.get('original-date'); + + // Formato quote tipico Usenet + let quotedText = ''; + if (origFrom && origDate) { + quotedText = 'On ' + decodeURIComponent(origDate) + ', ' + decodeURIComponent(origFrom) + ' wrote:\n\n'; + } + + // Aggiungi '> ' davanti ad ogni linea del messaggio originale + const bodyLines = decodeURIComponent(qbody).split('\n'); + for (let i = 0; i < bodyLines.length; i++) { + quotedText += '> ' + bodyLines[i] + '\n'; + } + + quotedText += '\n\n'; // Spazio per la risposta + + // Riempi il campo messageToSign (questo รจ EDITABILE) + const msgField = document.getElementById('messageToSign'); + if (msgField) { + msgField.value = quotedText; + } + } + + if (typeof showNotification === 'function') { + showNotification('๐ฌ REPLY: References and metadata are locked', 'info'); + } + + setTimeout(() => showTab('pow'), 500); + } +})(); +</script> + </body> -</html> +</html> |
