| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490 |
- #!/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()
|