Ver Fonte

less swearing and first public push

Kablersalat há 8 meses atrás
commit
427ef022bb

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+.DS_Store

+ 76 - 0
README.md

@@ -0,0 +1,76 @@
+# Helper-EmailProxy
+
+Welcome to Emailproxy-UI! The extra helping hand of scuffed code that hopefully makes your EmailProxy experience less of a dumpster fire.
+
+For now only able to do Office 365 Accounts and that uhm just barely but feel free to have a go at making it do other 2FA terribleness E-Mail services work too.
+
+## Project "Structure"
+(I've included this section because that's what "professionals" do, but we both know this is barely structured chaos)
+
+### The Core Confusion
+- `emailproxy-ui.py`: A Python script that pretends to know what it's doing with authentication processes
+- `emailproxy-ui.php`: The PHP frontend because I love making myself suffer with PHP 
+- `config/`: Where configuration files go to hide from responsibility
+- `logs/`: Divided into three categories of depression:
+  - `python/`: Where Python screams into the void
+  - `php/`: PHP's personal diary of failures
+  - `nginx/`: Nginx complaining about both of the above
+
+### Helpers
+- `helpers/`: Contains useful scripts that will probably break something
+  - `update-paths.sh`: Updates paths so they all point to the wrong places, but consistently
+
+## Getting Started
+Cool, I don't care. What can it do and how do I use it?
+
+This abomination helps you authenticate with Microsoft for EmailProxy without manually going through the hell that is copying/pasting authentication URLs. It gives you a web UI that works most of the time.
+
+### TL;DR
+
+You can use any old crappy email client again with your companies terrible E-Mail provider (Office 365 / Outlook)
+
+### What do you need to get started?
+
+- Install Python and PHP on whatever potato you have lying around
+- Install EmailProxy (how? uhm will make a guide someday TM)
+- Basic understanding of how EmailProxy works (if you want to use something else than MS email stuff)
+
+### Run test instance of it in 69 easy steps (actually just 5)
+1. Update your paths with `cd helpers && ./update-paths.sh` and edit and server IP's etc. `nano config/config.env`
+2. Install emailproxy with pipx `pipx install emailproxy`
+3. Deploy the nginx config somehow and run the backend `./emailproxy-ui.py`
+3. Access the web interface at: `http://your-server:8080/`
+
+And voilà! You now have a test instance to poke arround with.
+
+## Features
+- **Web UI**: ... Do i need to say more ?
+- **MS Pissing off**: Gain ability to use unencrypted mail protocols for the true ms pissing off experience
+- **Log "Management"**: Totally dont rely on them for the code to work
+- **Path Configuration**: Automatically fixes (or breaks) all your paths in one go!
+
+## How it Works (If it does)
+1. User enters their email and password in the web UI
+2. The Python script launches a temporary EmailProxy instance
+3. The PHP code pretends to know what it's doing
+4. Magic happens (or errors occur)
+5. Authentication URL is magically extracted and displayed to the user to authenticate
+6. User gets authenticated after pasting his not spoofed and repurposed Thunderbird return URL and gets displayed the correct configs for mail server
+
+## Contributing
+You can contribute if you want... but I doubt anyone would willingly touch this.
+
+bahahashbdasjhdashjdhj
+
+## License
+This project is licensed under the "I Can't Believe It Actually Runs" License.
+
+Basically:
+- No warranty.
+- No responsibility.
+- I barely know what I'm doing.
+- If this breaks your email, that's on you (in honesty it shouldn't though, most likely will just piss of your E-Mail adminier).
+
+Made with tolerance for Python, love for PHP, and pure disgust for OAuth by T.B ❤️ 🚀
+
+PS: There's minimal commit history because of using questianble language in it which ive ommited. You're welcome.

+ 20 - 0
config/config.env

@@ -0,0 +1,20 @@
+EMAILPROXY_EXECUTABLE=/home/crt/.local/bin/emailproxy
+EMAILPROXY_LOG_FILE=/home/crt/helper-emailproxy/logs/python/proxy.log
+EMAILPROXY_CONFIG_FILE=/home/crt/helper-emailproxy/config/emailproxy.config
+EMAILPROXY_AUTH_CONFIG=/home/crt/helper-emailproxy/config/emailproxy-auth.config
+COMMAND_PORT=8765
+ALLOWED_DOMAINS=example.com
+DEBUG_WEB=true
+DEBUG_WEB_LOG_FILE=/home/crt/helper-emailproxy/logs/php/webui.log
+
+# Mail server settings for client configuration
+MAIL_SERVER_NAME=172.16.57.101
+MAIL_IMAP_PORT=1993
+MAIL_SMTP_PORT=1465
+MAIL_IMAP_SSL=false
+MAIL_SMTP_SSL=false
+MAIL_IMAP_PROTOCOL=IMAP
+MAIL_SMTP_PROTOCOL=SMTP
+
+# Admin contact
+SYSADMIN_EMAIL=hostmaster@example.com

+ 22 - 0
config/emailproxy-auth.config

@@ -0,0 +1,22 @@
+[IMAP-2993]
+server_address = outlook.office365.com
+server_port = 993
+local_address = 0.0.0.0
+
+[SMTP-2465]
+server_address = smtp.office365.com
+server_port = 465
+local_address = 0.0.0.0
+
+[@rafisa.ch]
+permission_url = https://login.microsoftonline.com/common/oauth2/v2.0/authorize
+token_url = https://login.microsoftonline.com/common/oauth2/v2.0/token
+oauth2_scope = https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/POP.AccessAsUser.All https://outlook.office.com/SMTP.Send offline_access
+redirect_uri = https://login.microsoftonline.com/common/oauth2/nativeclient
+client_id = 9e5f94bc-e8a4-4e73-b8be-63364c29d753
+client_secret = 
+
+[emailproxy]
+delete_account_token_on_password_error = False
+use_login_password_as_client_credentials_secret = True
+allow_catch_all_accounts = True

+ 22 - 0
config/emailproxy.config

@@ -0,0 +1,22 @@
+[IMAP-1993]
+server_address = outlook.office365.com
+server_port = 993
+local_address = 0.0.0.0
+
+[SMTP-1465]
+server_address = smtp.office365.com
+server_port = 465
+local_address = 0.0.0.0
+
+[@example.com]
+permission_url = https://login.microsoftonline.com/common/oauth2/v2.0/authorize
+token_url = https://login.microsoftonline.com/common/oauth2/v2.0/token
+oauth2_scope = https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/POP.AccessAsUser.All https://outlook.office.com/SMTP.Send offline_access
+redirect_uri = https://login.microsoftonline.com/common/oauth2/nativeclient
+client_id = 9e5f94bc-e8a4-4e73-b8be-63364c29d753
+client_secret = 
+
+[emailproxy]
+delete_account_token_on_password_error = False
+use_login_password_as_client_credentials_secret = True
+allow_catch_all_accounts = True

+ 24 - 0
config/nginx-emailproxy.conf

@@ -0,0 +1,24 @@
+crt@tc-rafisa-eproxy ~/helper-emailproxy (main)> cat /etc/nginx/conf.d/emailproxy-ui.conf 
+server {
+    listen 8080;
+    server_name _;
+
+    root /var/www/emailproxy-ui;
+    index index.php;
+
+    access_log /home/crt/helper-emailproxy/logs/nginx/nginx-access.log;
+    error_log /home/crt/helper-emailproxy/logs/nginx/nginx-error.log;
+
+    location / {
+        try_files $uri $uri/ =404;
+    }
+
+    location ~ \.php$ {
+        include snippets/fastcgi-php.conf;
+        fastcgi_pass unix:/run/php/php8.2-fpm.sock; # maybe ajust this if u got older version yk
+    }
+
+    location ~ /\.ht {
+        deny all;
+    }
+}

+ 768 - 0
emailproxy-ui.php

@@ -0,0 +1,768 @@
+<?php
+session_start();
+ini_set('session.cookie_lifetime', 0);  // till browser closes
+ini_set('session.gc_maxlifetime', 600);  // 10 minutes
+ini_set('display_errors', 1);
+ini_set('display_startup_errors', 1);
+error_reporting(E_ALL);
+
+if (!isset($_SESSION['form_token'])) {
+    $_SESSION['form_token'] = bin2hex(random_bytes(32));
+}
+
+if (isset($_SESSION['form_success']) && $_SESSION['form_success']) {
+    $_SESSION['form_success'] = false;
+}
+
+// dont ask why i have this here
+if (isset($_SESSION['success_message'])) {
+    $success = $_SESSION['success_message'];
+    unset($_SESSION['success_message']);
+}
+
+// load the config (edit this to your needs)
+$config = parse_ini_file('/home/crt/helper-emailproxy/config/config.env');
+
+$configPath = $config['EMAILPROXY_CONFIG_FILE'];
+$authBaseConfigPath = $config['EMAILPROXY_AUTH_CONFIG'] ?? '/home/crt/helper-emailproxy/config/emailproxy-auth.config'; // base config path plus fallback to my default service user
+$logPath = $config['EMAILPROXY_LOG_FILE'];
+$allowedDomains = array_map('trim', explode(',', $config['ALLOWED_DOMAINS']));
+$debugWeb = isset($config['DEBUG_WEB']) && in_array(strtolower($config['DEBUG_WEB']), ['1', 'true', 'yes'], true);
+$debugLogFile = $config['DEBUG_WEB_LOG_FILE'] ?? '/tmp/web-debug.log';
+$authTimeout = 600; // if le auth session is not done in le 10 minutes welp too bad
+$publicMode = isset($config['PUBLIC']) && in_array(strtolower($config['PUBLIC']), ['1', 'true', 'yes'], true);
+$emailproxyExec = $config['EMAILPROXY_EXECUTABLE'];
+$commandPort = 8765; // little port to talk back to the auth-injector, should probably secure this more
+
+$mailServerName = $config['MAIL_SERVER_NAME'] ?? 'localhost';
+$mailImapPort = $config['MAIL_IMAP_PORT'] ?? '993';
+$mailSmtpPort = $config['MAIL_SMTP_PORT'] ?? '587';
+$mailImapSsl = isset($config['MAIL_IMAP_SSL']) && in_array(strtolower($config['MAIL_IMAP_SSL']), ['1', 'true', 'yes'], true); // is this a shit way to handle this ? yes very much so, does it make it more foolproof ? also yes
+$mailSmtpSsl = isset($config['MAIL_SMTP_SSL']) && in_array(strtolower($config['MAIL_SMTP_SSL']), ['1', 'true', 'yes'], true);
+$mailImapProtocol = $config['MAIL_IMAP_PROTOCOL'] ?? 'IMAP';
+$mailSmtpProtocol = $config['MAIL_SMTP_PROTOCOL'] ?? 'SMTP';
+
+// our super duper shit captcha that can be bypassed in seconds
+if (!isset($_SESSION['captcha']) || empty($_SESSION['captcha'])) {
+    $_SESSION['captcha'] = rand(1000, 9999);
+}
+
+// i dont know if my code breaks without debuging enabled yet :harold:.exe
+function debugLog($text, $debug, $file) {
+    if (!$debug) return;
+    $entry = "[" . date("Y-m-d H:i:s") . "] " . $text . "\n";
+    file_put_contents($file, $entry, FILE_APPEND);
+}
+
+function isEmailAllowed($email, $allowedDomains) {
+    $parts = explode('@', $email);
+    if (count($parts) !== 2) return false;
+    return in_array($parts[1], $allowedDomains);
+}
+
+function sendCommandToAuthInjector($command, $debug, $file) {
+    global $commandPort;
+    
+    debugLog("Sending command to auth-injector: " . json_encode($command), $debug, $file);
+    
+    $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
+    if ($socket === false) {
+        debugLog("socket_create() failed: " . socket_strerror(socket_last_error()), $debug, $file);
+        return ['success' => false, 'error' => "Failed to create socket"];
+    }
+    
+    $result = socket_connect($socket, 'localhost', $commandPort);
+    if ($result === false) {
+        debugLog("socket_connect() failed: " . socket_strerror(socket_last_error($socket)), $debug, $file);
+        socket_close($socket);
+        return ['success' => false, 'error' => "Failed to connect to auth-injector"];
+    }
+    
+    $data = json_encode($command);
+    socket_write($socket, $data, strlen($data));
+    
+    $response = socket_read($socket, 4096);
+    socket_close($socket);
+    
+    $response = json_decode($response, true);
+    debugLog("Received response from auth-injector: " . json_encode($response), $debug, $file);
+    
+    return $response;
+}
+
+function getAuthURL($logFile, $email) {
+    global $debugWeb, $debugLogFile;
+    
+    if (!file_exists($logFile)) {
+        debugLog("Log file not found: $logFile", $debugWeb, $debugLogFile);
+        return null;
+    }
+    
+    debugLog("Searching for auth URL in $logFile for email $email", $debugWeb, $debugLogFile);
+    $lines = array_reverse(file($logFile));
+    
+    foreach ($lines as $line) {
+        if (strpos($line, "Please visit the following URL") !== false) {
+            debugLog("Found 'Please visit' line: $line", $debugWeb, $debugLogFile);
+            
+            if (strpos($line, $email) !== false) {
+                preg_match('/https:\/\/login\\.microsoftonline\\.com\\/[^ ]+/', $line, $matches);
+                $url = $matches[0] ?? null;
+                debugLog("Extracted URL: $url", $debugWeb, $debugLogFile);
+                return $url;
+            }
+        }
+    }
+    
+    debugLog("No auth URL found for $email after checking " . count($lines) . " lines", $debugWeb, $debugLogFile);
+    return null;
+}
+
+// add "temporary" debug function for session ... 
+function debugSession($message) {
+    file_put_contents('/tmp/session_debug.log', 
+        date('Y-m-d H:i:s') . " - " . $message . ": " . 
+        json_encode($_SESSION) . "\n", 
+        FILE_APPEND);
+}
+
+// more debug functions for IMAP
+function debugImap($message, $debug = true, $debugFile = null) {
+    if (!$debug) return;
+    $timestamp = date('Y-m-d H:i:s');
+    $logMessage = "[$timestamp] [IMAP] $message\n";
+    file_put_contents($debugFile ?? '/tmp/imap-debug.log', $logMessage, FILE_APPEND);
+    error_log($logMessage); // Also log to PHP error log
+}
+
+// part of the user checking yk
+function userExistsInConfig($configPath, $email) {
+    if (!file_exists($configPath)) return false;
+    $content = file_get_contents($configPath);
+    return strpos($content, "[$email]") !== false;
+}
+
+function verifyUserPassword($email, $password) {
+    global $debugWeb, $debugLogFile;
+    
+    // imagine using the correct port
+    $imapPort = getMainImapPort();
+    if (!$imapPort) {
+        debugLog("Could not determine IMAP port", $debugWeb, $debugLogFile);
+        return false;
+    }
+    
+    debugLog("Attempting to verify password for $email using IMAP port $imapPort", $debugWeb, $debugLogFile);
+    
+    // try and connect, if it fails, it fails lol
+    $fp = @fsockopen('127.0.0.1', $imapPort, $errno, $errstr, 5);
+    if (!$fp) {
+        debugLog("Could not connect to IMAP server: $errstr ($errno)", $debugWeb, $debugLogFile);
+        return false;
+    }
+    
+    // we need timeout, python isnt that fast
+    stream_set_timeout($fp, 10);
+    
+    // get le hello from das server
+    $greeting = fgets($fp, 1024);
+    debugLog("IMAP greeting: $greeting", $debugWeb, $debugLogFile);
+    
+    // check if its a valid greeting and not an insulting one lol
+    if (!$greeting || strpos($greeting, '* OK') === false) {
+        debugLog("Invalid IMAP greeting, closing connection", $debugWeb, $debugLogFile);
+        fclose($fp);
+        return false;
+    }
+    
+    // totally untested function to escape the email and password custom characters
+    $safeEmail = str_replace(array('\\', '"'), array('\\\\', '\\"'), $email);
+    $safePassword = str_replace(array('\\', '"'), array('\\\\', '\\"'), $password);
+    
+    // send the login command obviously
+    $loginCommand = "a001 LOGIN \"$safeEmail\" \"$safePassword\"\r\n";
+    debugLog("Sending login command...", $debugWeb, $debugLogFile);
+    fwrite($fp, $loginCommand);
+    
+    // wait for python to piss its pants and respond
+    $response = '';
+    $timeout = time() + 10; // 10 seconds for pissing time
+    
+    while (!feof($fp) && time() < $timeout) {
+        $line = fgets($fp, 1024);
+        if (!$line) break;
+        
+        $response .= $line;
+        debugLog("IMAP response line: " . trim($line), $debugWeb, $debugLogFile);
+        
+        // is ok? then good
+        if (strpos($line, 'a001 OK') === 0) {
+            fwrite($fp, "a002 LOGOUT\r\n");
+            fclose($fp);
+            debugLog("Authentication succeeded", $debugWeb, $debugLogFile);
+            return true;
+        }
+        // is no? then bad
+        if (strpos($line, 'a001 NO') === 0 || strpos($line, 'a001 BAD') === 0) {
+            fclose($fp);
+            debugLog("Authentication failed: " . trim($line), $debugWeb, $debugLogFile);
+            return false;
+        }
+    }
+    
+    // if we here then we failed and we should go and cry
+    fclose($fp);
+    debugLog("Authentication timed out or had other issue", $debugWeb, $debugLogFile);
+    return false;
+}
+
+function getMainImapPort() {
+    global $configPath;
+    
+    if (!file_exists($configPath)) {
+        return null;
+    }
+    
+    $content = file_get_contents($configPath);
+    if (preg_match('/\[IMAP-(\d+)\]/', $content, $matches)) {
+        return (int)$matches[1];
+    }
+    
+    return null;
+}
+
+function removeUserFromConfig($configPath, $email) {
+    if (!file_exists($configPath)) return false;
+    
+    $content = file_get_contents($configPath);
+    
+    // danger zone ahead we are using regex to remove the user from the config in a terrible way
+    $pattern = '/\[' . preg_quote($email, '/') . '\].*?(?=\n\[|\Z)/s';
+    $contentAfterRemoval = preg_replace($pattern, '', $content);
+    
+    // i have ocd
+    $contentAfterRemoval = preg_replace("/\n\n\n+/", "\n\n", $contentAfterRemoval);
+    
+    // update prem file
+    return file_put_contents($configPath, $contentAfterRemoval) !== false;
+}
+
+// state ? AMERICA !!! RAHHHHHH 
+$step = $_SESSION['step'] ?? 1;
+$authUrl = '';
+$email = $_SESSION['email'] ?? '';
+$success = '';
+$error = '';
+
+// mmmm posting
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+    // check if the token is valid if is not it has no rights
+    $valid_token = false;
+    
+    if (isset($_POST['form_token']) && isset($_SESSION['form_token']) && 
+        $_POST['form_token'] === $_SESSION['form_token']) {
+        $valid_token = true;
+    } 
+    // i wont even pretend i know what this does
+    elseif (isset($_POST['form_token']) && isset($_SESSION['previous_form_token']) && 
+        $_POST['form_token'] === $_SESSION['previous_form_token']) {
+        $valid_token = true;
+        // stolen code yay !!! 
+        unset($_SESSION['previous_form_token']);
+    }
+    
+    if (!$valid_token) {
+        // le user has pressed refresh and submiteed the form again most likely so uhm nuh uh
+        $error = "Form submission error. Please try again.";
+    } else {
+        // MORE STOLEN CODE !!! YIPPIE
+        $_SESSION['form_token'] = bin2hex(random_bytes(32));
+        
+        if ($step === 1 && isset($_POST['captcha'])) {
+            debugSession("Before CAPTCHA validation");
+            // Step nummero eins: nutzloses gschiss catptcha
+            $captcha = trim($_POST['captcha']);
+            if ($captcha !== (string)$_SESSION['captcha']) {
+                $error = "Invalid CAPTCHA. Please try again.";
+                // kei ahnig wiso mer ned üsih function benützed aber okay hets problem gfixxed
+                $_SESSION['captcha'] = rand(1000, 9999);
+            } else {
+                // isch guet denn bye bye kaptcha damit "sicherheit und so"
+                unset($_SESSION['captcha']);
+                
+                // ich verlühre mini hoffnig das das jemals sicher wird sih aber mer chans ja probiere (ich han depressioneh)
+                $imapPort = 2993 + rand(1, 100);
+                $smtpPort = 2465 + rand(1, 100);
+                
+                // session ID, isch so fürs authentifizierig dingens
+                $sessionId = time() . '_' . rand(1000, 9999);
+                
+                // merken sonst wiso haben wir es gemacht ???
+                $_SESSION['imapPort'] = $imapPort;
+                $_SESSION['smtpPort'] = $smtpPort;
+                $_SESSION['sessionId'] = $sessionId;
+                
+                // yk vlt bruchid mer zersch emol d'email adresse deswege mal zu steppo 2 gah
+                $_SESSION['step'] = 2;
+                $_SESSION['form_success'] = true;
+                
+                // save session and redirect to the same page ... to update the content kinda terribly
+                session_write_close();
+                header('Location: ' . $_SERVER['PHP_SELF']);
+                exit;
+            }
+            debugSession("After CAPTCHA validation");
+        } elseif ($step === 2 && isset($_POST['email']) && isset($_POST['password'])) {
+            debugSession("Before email/password processing");
+            // Schritt numbero due ... email und passwort ihtöggele
+            $email = trim($_POST['email']);
+            $password = trim($_POST['password']);
+            
+            if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
+                $error = "Invalid email address.";
+            } elseif (!isEmailAllowed($email, $allowedDomains)) {
+                $error = "Email domain not allowed.";
+            } else {
+                // mmm mal güxle ob de user scho existiert im config
+                if (userExistsInConfig($configPath, $email)) {
+                    // sehr guet denn chömer mal gugge ob das passwort au stimmt
+                    if (verifyUserPassword($email, $password)) {
+                        // das isch save ned sicher aber yoa lets fucking go oder so
+                        $_SESSION['added_email'] = $email;
+                        $_SESSION['verified_password'] = $password; // mmm yes very secure password storage right here so you can steal it from the session :harold:
+                        $_SESSION['step'] = 4; // go to SEX panel ... to show the user config page
+                        
+                        // redirect to the same page to update the content
+                        session_write_close();
+                        header('Location: ' . $_SERVER['PHP_SELF']);
+                        exit;
+                    } else {
+                        // GOOD BYE MY NI... back to the lobby !!!
+                        $error = "Incorrect password for existing account. If you're having trouble, contact " . 
+                                 htmlspecialchars($config['SYSADMIN_EMAIL'] ?? 'your system administrator');
+                    }
+                } else {
+                    // user existiert ned also lets go und mache das authentifizierig dingens zum ms nerve
+                    $_SESSION['email'] = $email;
+                    $imapPort = $_SESSION['imapPort'];
+                    $smtpPort = $_SESSION['smtpPort'];
+                    $sessionId = $_SESSION['sessionId'];
+                    
+                    // now launch emailproxy temp session (look at me so sekurity)
+                    $command = [
+                        'type' => 'new_user',
+                        'email' => $email,
+                        'imap_port' => $imapPort,
+                        'smtp_port' => $smtpPort,
+                        'session_id' => $sessionId
+                    ];
+                    
+                    // note to self : following line has been fixed by an LLM (because i was lazy and i have no oversight of my code at this point its late and i want to sleep)
+                    $response = sendCommandToAuthInjector($command, $debugWeb, $debugLogFile);
+
+                    if (!$response['success']) {
+                        $error = "Failed to start authentication process. Please try again.";
+                    } else {
+                        sleep(3); // more spaghetti timeouts to ensure my slow server can handle it
+                        
+                        // connect to the IMAP server and send creds (this fails sometimes but we ignore it)
+                        $fp = fsockopen("127.0.0.1", $imapPort, $errno, $errstr, 5);
+                        if (!$fp) {
+                            $error = "Failed to connect to the temporary emailproxy (IMAP port $imapPort).";
+                        } else {
+                            // Send login command to IMAP
+                            fwrite($fp, "a001 LOGIN $email $password\r\n");
+                            sleep(5); // demonstration of ignoring it
+                            fclose($fp);
+                            
+                            $maxAttempts = 10; // how many times we try to get the url before deciding le server is le fucked
+                            $authUrl = null;
+                            
+                            for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {
+                                $authUrl = getAuthURL($logPath, $email);
+                                if ($authUrl) {
+                                    break;
+                                }
+                                sleep(3); // more demonstration of ignoring the problem
+                            }
+                            
+                            if ($authUrl) {
+                                $_SESSION['authUrl'] = $authUrl;
+                                $_SESSION['step'] = 3; // move to step numbero tres ... tres bien oder so wenn mer das url hend
+                                $_SESSION['form_success'] = true;
+                                
+                                // Save session and redirect
+                                session_write_close();
+                                header('Location: ' . $_SERVER['PHP_SELF']);
+                                exit;
+                            } else {
+                                $error = "Failed to retrieve the authentication URL. Please try again.";
+                                debugLog("Authentication URL not found after $maxAttempts attempts", $debugWeb, $debugLogFile);
+                            }
+                        }
+                    }
+                }
+            }
+        } elseif ($step === 3 && isset($_POST['auth_redirect'])) {
+            debugSession("Processing redirect URL");
+            // number tres ... redirect url processing
+            $authRedirect = trim($_POST['auth_redirect']);
+            $email = $_SESSION['email'];
+            $sessionId = $_SESSION['sessionId'];
+            
+            // send the redirect URL to the auth-injector (or by this point known as emailproxy-ui.py backend)
+            $command = [
+                'type' => 'redirect_url',
+                'session_id' => $sessionId,
+                'email' => $email,
+                'redirect_url' => $authRedirect
+            ];
+            
+            $response = sendCommandToAuthInjector($command, $debugWeb, $debugLogFile);
+            
+            if ($response['success']) {
+                $command = [
+                    'type' => 'merge_config',
+                    'session_id' => $sessionId
+                ];
+                $mergeResponse = sendCommandToAuthInjector($command, $debugWeb, $debugLogFile);
+                if ($mergeResponse['success']) {
+                    // checking if the user exists in the config ... again ... because stupid
+                    $is_existing = userExistsInConfig($configPath, $email);
+                    
+                    $_SESSION['added_email'] = $email;
+                    $_SESSION['auth_success'] = true;
+                    $_SESSION['form_success'] = true;
+                    $_SESSION['is_existing_account'] = $is_existing; // attempt at trying to make it display a different message upon creating or editing an account but didnt work lol
+                    $_SESSION['step'] = 4; // numbero quatro oder so
+                    
+                    // save sessino and redirect to the same page ... yk updates yap yap
+                    session_write_close();
+                    header('Location: ' . $_SERVER['PHP_SELF']);
+                    exit;
+                } else {
+                    $error = "Failed to merge configuration. Please try again.";
+                }
+            } else {
+                $error = "Authentication failed. The URL you provided may be invalid or the authentication timed out.";
+                $command = [
+                    'type' => 'cleanup',
+                    'session_id' => $sessionId
+                ];
+                sendCommandToAuthInjector($command, $debugWeb, $debugLogFile);
+            }
+        } 
+        // Idk what the following mystery is but its AI's spin on fixing and securing my user delete process
+        // Add this after your existing POST handlers, before the closing } of the if ($_SERVER['REQUEST_METHOD'] === 'POST') block
+        elseif (isset($_POST['remove_email']) && isset($_POST['confirm_password'])) {
+            $email = trim($_POST['remove_email']);
+            
+            // uhm we uhm please actually want to delete an email and not accidentally match all config entries
+            if (empty($email) && isset($_SESSION['email_for_removal'])) {
+                $email = $_SESSION['email_for_removal'];
+                unset($_SESSION['email_for_removal']);
+            }
+            
+            $password = trim($_POST['confirm_password']);
+            
+            if (empty($email)) {
+                $_SESSION['success_message'] = "Error: No email address specified for removal.";
+            } else {
+                // you very very sure the password is le correcto amigo ?
+                if (verifyUserPassword($email, $password)) {
+                    // tell the auth-injector to brutally cut out the user from running config
+                    $command = [
+                        'type' => 'remove_user',
+                        'email' => $email
+                    ];
+                    
+                    $response = sendCommandToAuthInjector($command, $debugWeb, $debugLogFile);
+                    
+                    if ($response['success']) {
+                        $_SESSION['success_message'] = "Account $email has been successfully removed from the email proxy.";
+                    } else {
+                        $_SESSION['success_message'] = "Failed to remove account. Error: " . ($response['error'] ?? 'Unknown error');
+                    }
+                } else {
+                    $_SESSION['success_message'] = "Password verification failed. If you need assistance, please contact " . 
+                                 ($config['SYSADMIN_EMAIL'] ?? 'your system administrator');
+                }
+            }
+            
+            // redir to first page (maybe clear session ? idk yet)
+            header('Location: ' . $_SERVER['PHP_SELF']);
+            exit;
+        }
+    }
+}
+?>
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Emailproxy Auth Panel</title>
+    <style>
+        body {
+            font-family: Arial, sans-serif;
+            max-width: 800px;
+            margin: 0 auto;
+            padding: 20px;
+        }
+        .error {
+            color: red;
+            font-weight: bold;
+        }
+        .success {
+            color: green;
+            font-weight: bold;
+        }
+        .form-group {
+            margin-bottom: 15px;
+        }
+        label {
+            display: block;
+            margin-bottom: 5px;
+        }
+        input[type="text"],
+        input[type="email"],
+        input[type="password"] {
+            width: 100%;
+            padding: 8px;
+            box-sizing: border-box;
+        }
+        button {
+            padding: 10px 15px;
+            background-color: #4CAF50;
+            color: white;
+            border: none;
+            cursor: pointer;
+        }
+        button:hover {
+            background-color: #45a049;
+        }
+        .auth-url {
+            word-break: break-all;
+            background-color: #f5f5f5;
+            padding: 10px;
+            border: 1px solid #ddd;
+        }
+        .success-container {
+            max-width: 700px;
+            margin: 0 auto;
+        }
+
+        .mail-settings {
+            background-color: #f9f9f9;
+            border: 1px solid #ddd;
+            border-radius: 5px;
+            padding: 20px;
+            margin: 20px 0;
+        }
+
+        .settings-container {
+            display: flex;
+            justify-content: space-between;
+            flex-wrap: wrap;
+        }
+
+        .settings-box {
+            flex: 1;
+            min-width: 250px;
+            padding: 10px;
+            margin: 5px;
+            background: white;
+            border: 1px solid #eee;
+            border-radius: 5px;
+        }
+
+        .settings-box h5 {
+            margin-top: 0;
+            color: #333;
+            border-bottom: 1px solid #eee;
+            padding-bottom: 10px;
+        }
+
+        .settings-box ul {
+            list-style: none;
+            padding-left: 0;
+        }
+
+        .settings-box li {
+            margin-bottom: 8px;
+        }
+
+        .note {
+            font-style: italic;
+            color: #666;
+            margin-top: 20px;
+        }
+
+        .button {
+            display: inline-block;
+            padding: 10px 15px;
+            background-color: #4CAF50;
+            color: white;
+            text-decoration: none;
+            border-radius: 4px;
+            margin-top: 10px;
+        }
+
+        .button:hover {
+            background-color: #45a049;
+        }
+
+        // Add this to your existing <style> section
+        .actions-container {
+            margin-top: 30px;
+            border-top: 1px solid #eee;
+            padding-top: 20px;
+        }
+
+        .remove-account {
+            margin-top: 30px;
+            padding: 15px;
+            background-color: #fff4f4;
+            border: 1px solid #ffdddd;
+            border-radius: 5px;
+        }
+
+        .remove-account h4 {
+            color: #cc0000;
+            margin-top: 0;
+        }
+
+        .button.danger {
+            background-color: #d9534f;
+        }
+
+        .button.danger:hover {
+            background-color: #c9302c;
+        }
+    </style>
+</head>
+<body>
+    <h2>OAutsch Sucks Authorization Panel</h2>
+    <p>for people who hate 2FA, Microsoft and overcomplicated E-Mail.</p>
+    <p>Made with love for PHP <br> and PURE RAGE AND HATRED FOR AZURE, TEAMS, EXCHANGE AND MICRO$SOFT <br> by UMTS at <a href="https://teleco.ch">teleco</a></p>
+    <hr>
+
+    <?php if ($error): ?>
+        <p class="error"><?= htmlspecialchars($error) ?></p>
+    <?php endif; ?>
+    
+    <?php if ($success): ?>
+        <p class="success"><?= htmlspecialchars($success) ?></p>
+    <?php endif; ?>
+    
+    <?php if ($step === 1): ?>
+        <form method="POST">
+            <div class="form-group">
+                <label>CAPTCHA: <?= isset($_SESSION['captcha']) ? $_SESSION['captcha'] : rand(1000, 9999) ?></label>
+                <input type="text" name="captcha" required placeholder="Enter the CAPTCHA code">
+            </div>
+            <input type="hidden" name="form_token" value="<?= $_SESSION['form_token'] ?>">
+            <button type="submit">Start Authentication</button>
+        </form>
+    <?php elseif ($step === 2): ?>
+        <form method="POST">
+            <div class="form-group">
+                <label>Email Address:</label>
+                <input type="email" name="email" required placeholder="Enter your email address">
+            </div>
+            <div class="form-group">
+                <label>Password:</label>
+                <input type="password" name="password" required placeholder="Enter your password">
+            </div>
+            <input type="hidden" name="form_token" value="<?= $_SESSION['form_token'] ?>">
+            <button type="submit">Submit</button>
+        </form>
+    <?php elseif ($step === 3): ?>
+        <p><strong>Please visit the following URL to complete authentication:</strong></p>
+        <div class="auth-url">
+            <a href="<?= htmlspecialchars($_SESSION['authUrl']) ?>" target="_blank"><?= htmlspecialchars($_SESSION['authUrl']) ?></a>
+        </div>
+        <p>After completing the authentication process, you will be redirected to a page with a final URL. Copy that URL and paste it below:</p>
+        
+        <form method="POST">
+            <div class="form-group">
+                <label>Paste the redirected URL here:</label>
+                <input type="text" name="auth_redirect" required placeholder="Paste the final URL here">
+            </div>
+            <input type="hidden" name="form_token" value="<?= $_SESSION['form_token'] ?>">
+            <button type="submit">Complete Authentication</button>
+        </form>
+    <?php elseif ($step === 4): ?>
+    <div class="success-container">
+        <h3>Success!</h3>
+        <p class="success">Your account <strong><?= htmlspecialchars($_SESSION['added_email'] ?? '') ?></strong> exists on the email proxy.</p>
+        
+        <div class="mail-settings">
+            <h4>Mail Client Configuration</h4>
+            
+            <div class="settings-container">
+                <div class="settings-box">
+                    <h5>Incoming Mail Server (<?= htmlspecialchars($mailImapProtocol) ?>)</h5>
+                    <ul>
+                        <li><strong>Server:</strong> <?= htmlspecialchars($mailServerName) ?></li>
+                        <li><strong>Port:</strong> <?= htmlspecialchars($mailImapPort) ?></li>
+                        <li><strong>Security:</strong> <?= $mailImapSsl ? 'SSL/TLS' : 'None' ?></li>
+                        <li><strong>Username:</strong> <?= htmlspecialchars($_SESSION['added_email'] ?? 'Your full email address') ?></li>
+                        <li><strong>Authentication:</strong> Normal Password</li>
+                    </ul>
+                </div>
+                
+                <div class="settings-box">
+                    <h5>Outgoing Mail Server (<?= htmlspecialchars($mailSmtpProtocol) ?>)</h5>
+                    <ul>
+                        <li><strong>Server:</strong> <?= htmlspecialchars($mailServerName) ?></li>
+                        <li><strong>Port:</strong> <?= htmlspecialchars($mailSmtpPort) ?></li>
+                        <li><strong>Security:</strong> <?= $mailSmtpSsl ? 'SSL/TLS' : 'None' ?></li>
+                        <li><strong>Username:</strong> <?= htmlspecialchars($_SESSION['added_email'] ?? 'Your full email address') ?></li>
+                        <li><strong>Authentication:</strong> Normal Password</li>
+                    </ul>
+                </div>
+            </div>
+            
+            <p class="note">Use your defined password to sign into the proxy server (can differ from OAuth Providers account password).</p>
+        </div>
+        
+        <div class="actions-container">
+            <p><a href="<?= $_SERVER['PHP_SELF'] ?>" class="button">Add Another Account</a></p>
+            
+            <div class="remove-account">
+                <h4>Remove Account</h4>
+                <p>If you wish to remove this account from the email proxy, please confirm your password below.</p>
+                
+                <form method="POST" onsubmit="return confirm('Are you sure?')">
+                    <div class="form-group">
+                        <label>Confirm Password:</label>
+                        <input type="password" name="confirm_password" required placeholder="Enter your password">
+                    </div>
+                    <input type="hidden" name="remove_email" value="<?= htmlspecialchars($_SESSION['added_email'] ?? '') ?>">
+                    <?php $form_token = $_SESSION['form_token']; ?>
+                    <input type="hidden" name="form_token" value="<?= $form_token ?>">
+                    <button type="submit" class="button danger">Remove Account from Proxy</button>
+                </form>
+            </div>
+        </div>
+    </div>
+    
+    <?php
+    $email_for_removal = $_SESSION['added_email'] ?? '';
+    
+    // Make new token before bye bye old one
+    $new_token = bin2hex(random_bytes(32));
+    
+    // clean up le session after super premio success in hating ms
+    $_SESSION = [];
+    $_SESSION['captcha'] = rand(1000, 9999);
+    $_SESSION['form_token'] = $new_token;
+    // keep token for the next form just in case doe
+    $_SESSION['previous_form_token'] = $form_token;
+    $_SESSION['email_for_removal'] = $email_for_removal;
+    $step = 1;
+    ?>
+<?php endif; ?>
+</body>
+</html>

+ 490 - 0
emailproxy-ui.py

@@ -0,0 +1,490 @@
+#!/usr/bin/env python3
+import os
+import sys
+import subprocess
+import tempfile
+import shutil
+import time
+import socket
+import json
+import re
+
+# load le config file (edit this to your needs)
+CONFIG_FILE = "/home/crt/helper-emailproxy/config/config.env"
+
+def load_config():
+    """Load configuration from the environment file."""
+    config = {}
+    with open(CONFIG_FILE, "r") as f:
+        for line in f:
+            # dont read comments or empty lines perhaps
+            if line.strip().startswith("#") or "=" not in line:
+                continue
+            key, value = line.strip().split("=", 1)
+            config[key.strip()] = value.strip()
+    return config
+
+def create_temp_config(auth_base_config, imap_port, smtp_port):
+    """Create a temporary configuration file for the session."""
+    temp_dir = tempfile.mkdtemp()
+    temp_config_path = os.path.join(temp_dir, "emailproxy-auth-temp.config")
+    
+    with open(auth_base_config, "r") as base_config, open(temp_config_path, "w") as temp_config:
+        for line in base_config:
+            temp_config.write(
+                line.replace("IMAP-2993", f"IMAP-{imap_port}")
+                    .replace("SMTP-2465", f"SMTP-{smtp_port}")
+            )
+    
+    return temp_config_path, temp_dir
+
+def launch_emailproxy(temp_config_path, emailproxy_exec):
+    """Launch the emailproxy with the temporary configuration."""
+    # launch a temporary emailproxy process for getting 2fa piss token
+    config = load_config()
+    log_file = config.get("EMAILPROXY_LOG_FILE", "/tmp/emailproxy.log")
+    
+    print(f"[+] Starting temporary emailproxy, writing output to {log_file}")
+    
+    working_dir = os.path.dirname(emailproxy_exec) if "/" in emailproxy_exec else os.getcwd()
+    cmd = f"stdbuf -oL {emailproxy_exec} --config-file '{temp_config_path}' --no-gui --external-auth"
+    
+    # this is a security risk, but we need to write the piss token to one file for php to read it because i am dumb
+    with open(log_file, "a") as lf:
+        proc = subprocess.Popen(
+            cmd, 
+            shell=True, 
+            cwd=working_dir,
+            stdin=subprocess.PIPE, 
+            stdout=lf,  # thanks ai for fixing my crap
+            stderr=lf,  # same here
+            text=True
+        )
+    
+    # return the process
+    return proc
+
+def validate_new_user(temp_config_path, email):
+    """Check if a new user was successfully added to the temporary config."""
+    if not os.path.exists(temp_config_path):
+        return False
+        
+    with open(temp_config_path, "r") as temp_config:
+        return f"[{email}]" in temp_config.read()
+
+def merge_configs(main_config_path, temp_config_path):
+    """Merge the new user configuration into the main config."""
+    with open(main_config_path, "r") as main_config:
+        main_config_content = main_config.read()
+
+    # ocd newline check
+    ensure_newline = "" if main_config_content.endswith("\n") else "\n"
+    
+    with open(temp_config_path, "r") as temp_config:
+        temp_config_content = temp_config.read()
+
+    # get new successful users from temp config
+    user_sections = []
+    current_section = []
+    in_user_section = False
+    
+    for line in temp_config_content.splitlines():
+        if line.startswith("[") and "@" in line:
+            if current_section:  # i am bad at python
+                user_sections.append("\n".join(current_section))
+                current_section = []
+            in_user_section = True
+            current_section.append(line)
+        elif line.startswith("["):
+            in_user_section = False
+            current_section = []
+        elif in_user_section:
+            current_section.append(line)
+    
+    # add the last user section because thats 99% of the time what we want
+    if current_section and in_user_section:
+        user_sections.append("\n".join(current_section))
+
+    # maybe dont create conflicts yk (send help)
+    merged_any = False
+    for user_section in user_sections:
+        user_email = user_section.split("[", 1)[1].split("]", 1)[0]
+        if f"[{user_email}]" in main_config_content:
+            print(f"[!] User {user_email} already exists in the main config. Skipping merge.")
+            continue
+        
+        # append new user and ENSURE NEW LINE I HATE GRUSSIGI CONFIG !!!
+        with open(main_config_path, "a") as main_config:
+            main_config.write(f"{ensure_newline}\n{user_section}\n")
+            
+            # mmm emailproxy deletes config and replaces with its cached one when stopped so uhm yeah this is here for that
+            main_config.flush()
+            os.fsync(main_config.fileno())
+            
+            print(f"[+] Successfully added {user_email} to the main config.")
+            merged_any = True
+
+    return merged_any
+
+def restart_main_proxy(emailproxy_exec, emailproxy_config, working_dir, log_file, debug):
+    """Restart the main emailproxy process."""
+    print("[+] Stopping the current emailproxy process...")
+    
+    # kill it with fire
+    kill_cmd = f"pkill -f '{emailproxy_exec} --config-file {emailproxy_config}'"
+    subprocess.run(kill_cmd, shell=True)
+    
+    # make sure the process burnt alive and died
+    time.sleep(2)
+    
+    # return before starting the new one
+    return working_dir, log_file, debug
+
+# thenks ai for fixing !
+def handle_new_user(email, imap_port, smtp_port, emailproxy_exec, auth_base_config):
+    """Handle a new user authentication request."""
+    temp_config_path, temp_dir = create_temp_config(auth_base_config, imap_port, smtp_port)
+    
+    try:
+        # launch temp emailproxy
+        print(f"[+] Launching temporary emailproxy for {email} on ports IMAP:{imap_port}, SMTP:{smtp_port}")
+        temp_proxy_proc = launch_emailproxy(temp_config_path, emailproxy_exec)
+        
+        # gibs returned values for php
+        return temp_config_path, temp_dir, temp_proxy_proc
+        
+    except Exception as e:
+        print(f"[!] Error launching temporary emailproxy: {e}")
+        shutil.rmtree(temp_dir)
+        return None, None, None
+
+def start_command_server():
+    """Start a socket server to receive commands from PHP."""
+    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+    server_socket.bind(('localhost', COMMAND_PORT))
+    server_socket.listen(5)
+    return server_socket
+
+# security risk most likely <3 i love it
+def remove_user_from_config(config_file, email):
+    """Remove a user section from the config file."""
+    if not os.path.exists(config_file):
+        return False
+    
+    with open(config_file, "r") as f:
+        content = f.read()
+    
+    # thanks ai for the regex
+    pattern = r'\[' + re.escape(email) + r'\].*?(?=\n\[|\Z)'
+    new_content = re.sub(pattern, '', content, flags=re.DOTALL)
+    
+    # ICH HASSE GRUSSIGI CONFIG
+    new_content = re.sub(r'\n\n\n+', '\n\n', new_content)
+    
+    # updaten das filet
+    with open(config_file, "w") as f:
+        f.write(new_content)
+        f.flush()
+        os.fsync(f.fileno())
+    
+    print(f"[+] Successfully removed user {email} from the config.")
+    return True
+
+def main():
+    """Main function to handle the authentication process."""
+    config = load_config()
+    
+    # load em vars yehea boi
+    emailproxy_exec = config.get("EMAILPROXY_EXECUTABLE", "emailproxy")
+    main_config = config.get("EMAILPROXY_CONFIG_FILE", "/home/crt/helper-emailproxy/config/emailproxy.config")
+    auth_base_config = config.get("EMAILPROXY_AUTH_CONFIG", "/home/crt/helper-emailproxy/config/emailproxy-auth.config")
+    log_file = config.get("EMAILPROXY_LOG_FILE", "/tmp/emailproxy.log")
+    command_port = int(config.get("COMMAND_PORT", "8765"))
+    
+    # get da working dir
+    working_dir = os.path.dirname(emailproxy_exec) if "/" in emailproxy_exec else os.getcwd()
+    
+    # launch main proxii
+    print("[+] Launching the main emailproxy process...")
+    cmd = f"stdbuf -oL {emailproxy_exec} --config-file '{main_config}' --no-gui"
+    
+    # commadn line debug (sorry for the mess below emailproxy was acting weird)
+    debug = "--debug" in sys.argv
+    
+    if debug:
+        main_proxy_proc = subprocess.Popen(
+            cmd, 
+            shell=True, 
+            cwd=working_dir,
+            stdin=subprocess.PIPE, 
+            stdout=sys.stdout, 
+            stderr=sys.stderr, 
+            text=True
+        )
+    else:
+        with open(log_file, "a") as lf:
+            main_proxy_proc = subprocess.Popen(
+                cmd, 
+                shell=True, 
+                cwd=working_dir,
+                stdin=subprocess.PIPE, 
+                stdout=lf, 
+                stderr=lf, 
+                text=True
+            )
+    
+    print(f"[+] emailproxy started with PID {main_proxy_proc.pid}")
+    
+    # maybe benutzi machi das richtigi porti
+    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+    server_socket.bind(('localhost', command_port))
+    server_socket.listen(5)
+    print(f"[+] Command server started on port {command_port}, waiting for commands...")
+
+    active_sessions = {}  # store active sessions for cleanup so no mess and stinky
+    
+    try:
+        while True:
+            # accept le connections from PHP <3
+            client_socket, address = server_socket.accept()
+            data = client_socket.recv(4096).decode('utf-8')
+            
+            try:
+                command = json.loads(data)
+                cmd_type = command.get("type")
+                
+                if cmd_type == "new_user":
+                    # thanks ai for fixing my crap
+                    email = command.get("email", "")
+                    imap_port = int(command.get("imap_port", 2993))
+                    smtp_port = int(command.get("smtp_port", 2465))
+                    session_id = command.get("session_id")
+                    
+                    temp_config_path, temp_dir, temp_proc = handle_new_user(
+                        email, imap_port, smtp_port, emailproxy_exec, auth_base_config
+                    )
+                    
+                    if temp_config_path:
+                        active_sessions[session_id] = (temp_config_path, temp_dir, temp_proc)
+                        client_socket.send(json.dumps({"success": True}).encode('utf-8'))
+                    else:
+                        client_socket.send(json.dumps({"success": False, "error": "Failed to start temporary proxy"}).encode('utf-8'))
+                
+                elif cmd_type == "check_auth":
+                    # check if the user was successfully added to the temporary config
+                    session_id = command.get("session_id")
+                    email = command.get("email")
+                    
+                    if session_id in active_sessions:
+                        temp_config_path, _, _ = active_sessions[session_id]
+                        success = validate_new_user(temp_config_path, email)
+                        client_socket.send(json.dumps({"success": success}).encode('utf-8'))
+                    else:
+                        client_socket.send(json.dumps({"success": False, "error": "Invalid session"}).encode('utf-8'))
+                
+                elif cmd_type == "merge_config":
+                    # merge the config and brutally restart the proxy (bad bad bad but works so so yeah uhm idk)
+                    session_id = command.get("session_id")
+                    
+                    if session_id in active_sessions:
+                        temp_config_path, temp_dir, temp_proc = active_sessions[session_id]
+                        
+                        # murder temp
+                        if temp_proc and temp_proc.poll() is None:
+                            temp_proc.terminate()
+                            temp_proc.wait(timeout=5)
+                        
+                        # stop main proxy
+                        working_dir, log_file, debug = restart_main_proxy(emailproxy_exec, main_config, working_dir, log_file, debug)
+                        
+                        # merge while stopped
+                        print("[+] Merging configuration files...")
+                        merge_success = merge_configs(main_config, temp_config_path)
+                        
+                        # revive again
+                        print("[+] Starting a new emailproxy process...")
+                        cmd = f"stdbuf -oL {emailproxy_exec} --config-file '{main_config}' --no-gui"
+                        
+                        if debug:
+                            main_proxy_proc = subprocess.Popen(
+                                cmd, 
+                                shell=True, 
+                                cwd=working_dir,
+                                stdin=subprocess.PIPE, 
+                                stdout=sys.stdout, 
+                                stderr=sys.stderr, 
+                                text=True
+                            )
+                        else:
+                            with open(log_file, "a") as lf:
+                                main_proxy_proc = subprocess.Popen(
+                                    cmd, 
+                                    shell=True, 
+                                    cwd=working_dir,
+                                    stdin=subprocess.PIPE, 
+                                    stdout=lf, 
+                                    stderr=lf, 
+                                    text=True
+                                )
+                        
+                        print(f"[+] New emailproxy process started with PID {main_proxy_proc.pid}")
+                        
+                        # no more temp
+                        shutil.rmtree(temp_dir)
+                        del active_sessions[session_id]
+                        
+                        client_socket.send(json.dumps({"success": merge_success}).encode('utf-8'))
+                    else:
+                        client_socket.send(json.dumps({"success": False, "error": "Invalid session"}).encode('utf-8'))
+                
+                elif cmd_type == "cleanup":
+                    # clean up abandoned fatherless session (sad violin)
+                    session_id = command.get("session_id")
+                    
+                    if session_id in active_sessions:
+                        _, temp_dir, temp_proc = active_sessions[session_id]
+                        
+                        # more murdering of the orphans
+                        if temp_proc and temp_proc.poll() is None:
+                            temp_proc.terminate()
+                            temp_proc.wait(timeout=5)
+                        
+                        # hide the bodies
+                        shutil.rmtree(temp_dir)
+                        del active_sessions[session_id]
+                        
+                        client_socket.send(json.dumps({"success": True}).encode('utf-8'))
+                    else:
+                        client_socket.send(json.dumps({"success": False, "error": "Invalid session"}).encode('utf-8'))
+                
+                elif cmd_type == "redirect_url":
+                    # process the actual redirect url
+                    session_id = command.get("session_id")
+                    email = command.get("email")
+                    redirect_url = command.get("redirect_url")
+                    
+                    if session_id in active_sessions:
+                        temp_config_path, temp_dir, temp_proc = active_sessions[session_id]
+                        print(f"[+] Received redirect URL for {email}")
+                        
+                        if temp_proc and temp_proc.poll() is None:
+                            try:
+                                # canz we haz writez?
+                                if temp_proc.stdin:
+                                    temp_proc.stdin.write(redirect_url + "\n")
+                                    temp_proc.stdin.flush()
+                                    time.sleep(5)  # oop wait a bit for the process
+                                    
+                                    # ignoring the problems just like irl and trying again
+                                    success = False
+                                    for _ in range(3):
+                                        if validate_new_user(temp_config_path, email):
+                                            success = True
+                                            break
+                                        time.sleep(2)
+                                    
+                                    client_socket.send(json.dumps({"success": success}).encode('utf-8'))
+                                else:
+                                    client_socket.send(json.dumps({"success": False, "error": "Process stdin not available"}).encode('utf-8'))
+                            except Exception as e:
+                                print(f"[!] Error sending redirect URL: {e}")
+                                client_socket.send(json.dumps({"success": False, "error": str(e)}).encode('utf-8'))
+                        else:
+                            client_socket.send(json.dumps({"success": False, "error": "Temporary process not running"}).encode('utf-8'))
+                    else:
+                        client_socket.send(json.dumps({"success": False, "error": "Invalid session"}).encode('utf-8'))
+                
+                elif cmd_type == "remove_user":
+                    # remove a user from the main config
+                    email = command.get("email")
+                    
+                    if not email:
+                        client_socket.send(json.dumps({"success": False, "error": "Email address required"}).encode('utf-8'))
+                        continue
+                    
+                    # stop the main proxy before removing the user
+                    working_dir, log_file, debug = restart_main_proxy(emailproxy_exec, main_config, working_dir, log_file, debug)
+                    
+                    # tschau tschau user
+                    print(f"[+] Removing user {email} from configuration...")
+                    remove_success = remove_user_from_config(main_config, email)
+                    
+                    # prxi is back
+                    print("[+] Starting a new emailproxy process...")
+                    cmd = f"stdbuf -oL {emailproxy_exec} --config-file '{main_config}' --no-gui"
+                    
+                    if debug:
+                        main_proxy_proc = subprocess.Popen(
+                            cmd, 
+                            shell=True, 
+                            cwd=working_dir,
+                            stdin=subprocess.PIPE, 
+                            stdout=sys.stdout, 
+                            stderr=sys.stderr, 
+                            text=True
+                        )
+                    else:
+                        with open(log_file, "a") as lf:
+                            main_proxy_proc = subprocess.Popen(
+                                cmd, 
+                                shell=True, 
+                                cwd=working_dir,
+                                stdin=subprocess.PIPE, 
+                                stdout=lf, 
+                                stderr=lf, 
+                                text=True
+                            )
+                    
+                    print(f"[+] New emailproxy process started with PID {main_proxy_proc.pid}")
+                    client_socket.send(json.dumps({"success": remove_success}).encode('utf-8'))
+                
+                else:
+                    client_socket.send(json.dumps({"success": False, "error": "Unknown command"}).encode('utf-8'))
+            
+            except json.JSONDecodeError:
+                client_socket.send(json.dumps({"success": False, "error": "Invalid JSON"}).encode('utf-8'))
+            except Exception as e:
+                client_socket.send(json.dumps({"success": False, "error": str(e)}).encode('utf-8'))
+            finally:
+                client_socket.close()
+                
+            # apparently we need to clean up the mess (thanks ai)
+            current_time = time.time()
+            timed_out_sessions = []
+            
+            for session_id, (_, temp_dir, temp_proc) in active_sessions.items():
+                session_time = int(session_id.split('_')[0])
+                if current_time - session_time > 300:  # 5 mins
+                    if temp_proc and temp_proc.poll() is None:
+                        temp_proc.terminate()
+                        temp_proc.wait(timeout=5)
+                    
+                    shutil.rmtree(temp_dir)
+                    timed_out_sessions.append(session_id)
+            
+            for session_id in timed_out_sessions:
+                del active_sessions[session_id]
+                print(f"[!] Session {session_id} timed out and was cleaned up.")
+            
+    except KeyboardInterrupt:
+        print("[!] Shutting down...")
+    finally:
+        # bye bye sessions
+        for session_id, (_, temp_dir, temp_proc) in active_sessions.items():
+            if temp_proc and temp_proc.poll() is None:
+                temp_proc.terminate()
+                temp_proc.wait(timeout=5)
+            
+            shutil.rmtree(temp_dir)
+        
+        # bye bye main proxy
+        if main_proxy_proc.poll() is None:
+            main_proxy_proc.terminate()
+            main_proxy_proc.wait(timeout=5)
+        
+        print("[+] All processes terminated and temporary files probably cleaned up.")
+
+if __name__ == "__main__":
+    main()

+ 58 - 0
helpers/update-paths.sh

@@ -0,0 +1,58 @@
+#!/bin/bash
+
+SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+BASE_DIR="$(dirname "$SCRIPT_DIR")"
+echo "Base directory detected as: $BASE_DIR"
+
+mkdir -p "$BASE_DIR/config"
+mkdir -p "$BASE_DIR/logs/python"
+mkdir -p "$BASE_DIR/logs/php"
+mkdir -p "$BASE_DIR/logs/nginx"
+
+touch "$BASE_DIR/logs/python/proxy.log"
+touch "$BASE_DIR/logs/php/webui.log"
+touch "$BASE_DIR/logs/nginx/nginx-access.log"
+touch "$BASE_DIR/logs/nginx/nginx-error.log"
+
+chmod -R 775 "$BASE_DIR/logs"
+chmod 664 "$BASE_DIR/logs/python/proxy.log"
+chmod 664 "$BASE_DIR/logs/php/webui.log"
+chmod 664 "$BASE_DIR/logs/nginx/nginx-access.log"
+chmod 664 "$BASE_DIR/logs/nginx/nginx-error.log"
+
+if [ $(id -u) -eq 0 ]; then
+    # Get the current user who is running sudo
+    ACTUAL_USER=$(logname || echo "$SUDO_USER")
+    echo "Setting ownership to $ACTUAL_USER:www-data"
+    chown -R $ACTUAL_USER:www-data "$BASE_DIR/logs"
+else
+    echo "Warning: Not running as root, ownership not changed. May need to manually adjust file ownership."
+    echo "Consider running: sudo chown -R $(whoami):www-data $BASE_DIR/logs"
+fi
+
+CONFIG_FILE="$BASE_DIR/config/config.env"
+echo "Updating paths in $CONFIG_FILE"
+
+sed -i "s|EMAILPROXY_LOG_FILE=.*|EMAILPROXY_LOG_FILE=$BASE_DIR/logs/python/proxy.log|" "$CONFIG_FILE"
+sed -i "s|EMAILPROXY_CONFIG_FILE=.*|EMAILPROXY_CONFIG_FILE=$BASE_DIR/config/emailproxy.config|" "$CONFIG_FILE"
+sed -i "s|EMAILPROXY_AUTH_CONFIG=.*|EMAILPROXY_AUTH_CONFIG=$BASE_DIR/config/emailproxy-auth.config|" "$CONFIG_FILE"
+sed -i "s|DEBUG_WEB_LOG_FILE=.*|DEBUG_WEB_LOG_FILE=$BASE_DIR/logs/php/webui.log|" "$CONFIG_FILE"
+
+PY_FILE="$BASE_DIR/emailproxy-ui.py"
+echo "Updating path in $PY_FILE"
+sed -i "s|CONFIG_FILE = .*|CONFIG_FILE = \"$BASE_DIR/config/config.env\"|" "$PY_FILE"
+
+UI_PHP="$BASE_DIR/emailproxy-ui.php"
+echo "Updating path in $UI_PHP"
+sed -i "s|\$config = parse_ini_file(.*|\$config = parse_ini_file('$BASE_DIR/config/config.env');|" "$UI_PHP"
+
+NGINX_CONF="$BASE_DIR/config/nginx-emailproxy.conf"
+echo "Updating paths in $NGINX_CONF"
+sed -i "s|access_log .*|access_log $BASE_DIR/logs/nginx/nginx-access.log;|" "$NGINX_CONF"
+sed -i "s|error_log .*|error_log $BASE_DIR/logs/nginx/nginx-error.log;|" "$NGINX_CONF"
+
+echo "updated the paths lol"
+echo "Log directories:"
+echo "  - Python logs: $BASE_DIR/logs/python"
+echo "  - PHP logs:    $BASE_DIR/logs/php"
+echo "  - Nginx logs:  $BASE_DIR/logs/nginx"

+ 0 - 0
logs/nginx/nginx-access.log


+ 0 - 0
logs/nginx/nginx-error.log


+ 0 - 0
logs/php/webui.log


+ 0 - 0
logs/python/proxy.log