Initial commit: VMail Manager Tool mit Argon2id Support
This commit is contained in:
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Python-Interne Dateien
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# Virtuelle Umgebung (Venv)
|
||||||
|
vmail-env/
|
||||||
|
venv/
|
||||||
|
env/
|
||||||
|
|
||||||
|
# Konfiguration (Enthält deine Passwörter!)
|
||||||
|
config.ini
|
||||||
|
|
||||||
|
# System-Dateien
|
||||||
|
.DS_Store
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
7
config.ini.example
Normal file
7
config.ini.example
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
[database]
|
||||||
|
host = localhost
|
||||||
|
user = vmail_admin
|
||||||
|
password = dein_sicheres_passwort
|
||||||
|
database = vmail
|
||||||
|
default_domain = example.com
|
||||||
|
|
||||||
222
vmailtool.py
Executable file
222
vmailtool.py
Executable file
@@ -0,0 +1,222 @@
|
|||||||
|
#!/root/sql/vmail-env/bin/python3
|
||||||
|
import mysql.connector
|
||||||
|
import configparser
|
||||||
|
import getpass
|
||||||
|
import sys
|
||||||
|
import socket
|
||||||
|
from argon2 import PasswordHasher, Type
|
||||||
|
|
||||||
|
# Konfiguration laden
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
config.read('config.ini')
|
||||||
|
hostname = socket.getfqdn()
|
||||||
|
|
||||||
|
DEFAULT_DOM = config.get('database', 'default_domain', fallback="")
|
||||||
|
|
||||||
|
ph = PasswordHasher(
|
||||||
|
time_cost=3,
|
||||||
|
memory_cost=131072,
|
||||||
|
parallelism=4,
|
||||||
|
hash_len=32,
|
||||||
|
salt_len=16,
|
||||||
|
type=Type.ID
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_db_connection():
|
||||||
|
try:
|
||||||
|
return mysql.connector.connect(
|
||||||
|
host=config['database']['host'],
|
||||||
|
user=config['database']['user'],
|
||||||
|
password=config['database']['password'],
|
||||||
|
database=config['database']['database']
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Fehler: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def hash_pw(password):
|
||||||
|
return f"{{ARGON2ID}}{ph.hash(password)}"
|
||||||
|
|
||||||
|
def get_domain_input(prompt="Domain"):
|
||||||
|
if DEFAULT_DOM:
|
||||||
|
val = input(f"{prompt} [{DEFAULT_DOM}]: ").strip()
|
||||||
|
return val if val else DEFAULT_DOM
|
||||||
|
return input(f"{prompt}: ").strip()
|
||||||
|
|
||||||
|
# --- FUNKTIONEN ---
|
||||||
|
|
||||||
|
def show_users():
|
||||||
|
conn = get_db_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("SELECT id, username, domain, enabled, quota FROM accounts ORDER BY id ASC")
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
print(f"\n{'ID':<4} | {'Email Adresse':<35} | {'Status':<10} | {'Quota'}")
|
||||||
|
print("-" * 65)
|
||||||
|
for r in rows:
|
||||||
|
status = "AKTIV" if r[3] else "GESPERRT"
|
||||||
|
print(f"{r[0]:<4} | {r[1]+'@'+r[2]:<35} | {status:<10} | {r[4]} MB")
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def add_user():
|
||||||
|
user = input("Username (vor dem @): ")
|
||||||
|
dom = get_domain_input()
|
||||||
|
pw = getpass.getpass("Passwort: ")
|
||||||
|
hashed = hash_pw(pw)
|
||||||
|
conn = get_db_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
try:
|
||||||
|
cursor.execute("INSERT INTO accounts (username, domain, password, quota, enabled) VALUES (%s, %s, %s, 1024, 1)",
|
||||||
|
(user, dom, hashed))
|
||||||
|
conn.commit()
|
||||||
|
print(f"✔ User {user}@{dom} angelegt.")
|
||||||
|
except Exception as e: print(f"❌ Fehler: {e}")
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def del_user():
|
||||||
|
email = input("E-Mail des zu LÖSCHENDEN Users: ")
|
||||||
|
if '@' not in email: return
|
||||||
|
u, d = email.split('@')
|
||||||
|
confirm = input(f"Account {email} wirklich löschen? (y/N): ")
|
||||||
|
if confirm.lower() != 'y': return
|
||||||
|
conn = get_db_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("DELETE FROM accounts WHERE username = %s AND domain = %s", (u, d))
|
||||||
|
conn.commit()
|
||||||
|
print("✔ User gelöscht.")
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def show_aliases():
|
||||||
|
conn = get_db_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("SELECT id, source_username, source_domain, destination_username, destination_domain FROM aliases ORDER BY id ASC")
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
print(f"\n{'ID':<4} | {'Alias (Source)':<30} | {'Ziel (Destination)':<30}")
|
||||||
|
print("-" * 70)
|
||||||
|
for r in rows:
|
||||||
|
print(f"{r[0]:<4} | {r[1]+'@'+r[2]:<30} | {r[3]+'@'+r[4]:<30}")
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def add_alias():
|
||||||
|
s_user = input("Alias Name (Source): ")
|
||||||
|
s_dom = get_domain_input("Alias Domain")
|
||||||
|
d_user = input("Ziel Username (Dest): ")
|
||||||
|
d_dom = get_domain_input("Ziel Domain")
|
||||||
|
conn = get_db_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
try:
|
||||||
|
cursor.execute("INSERT INTO aliases (source_username, source_domain, destination_username, destination_domain, enabled) VALUES (%s, %s, %s, %s, 1)",
|
||||||
|
(s_user, s_dom, d_user, d_dom))
|
||||||
|
conn.commit()
|
||||||
|
print("✔ Alias angelegt.")
|
||||||
|
except Exception as e: print(f"❌ Fehler: {e}")
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def del_alias():
|
||||||
|
alias_id = input("ID des zu löschenden Alias: ")
|
||||||
|
conn = get_db_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("DELETE FROM aliases WHERE id = %s", (alias_id,))
|
||||||
|
conn.commit()
|
||||||
|
print(f"✔ Alias ID {alias_id} gelöscht.")
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def show_domains():
|
||||||
|
conn = get_db_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("SELECT id, domain FROM domains ORDER BY id ASC")
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
print(f"\n{'ID':<4} | {'Domain'}")
|
||||||
|
print("-" * 30)
|
||||||
|
for r in rows:
|
||||||
|
print(f"{r[0]:<4} | {r[1]}")
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def add_domain():
|
||||||
|
dom = input("Neue Domain: ").strip()
|
||||||
|
if not dom: return
|
||||||
|
conn = get_db_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
try:
|
||||||
|
cursor.execute("INSERT INTO domains (domain) VALUES (%s)", (dom,))
|
||||||
|
conn.commit()
|
||||||
|
print(f"✔ Domain {dom} registriert.")
|
||||||
|
except Exception as e: print(f"❌ Fehler: {e}")
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def del_domain():
|
||||||
|
dom = input("Zu löschende Domain (Name eingeben): ").strip()
|
||||||
|
if not dom: return
|
||||||
|
confirm = input(f"Soll die Domain '{dom}' wirklich gelöscht werden? (y/N): ")
|
||||||
|
if confirm.lower() != 'y': return
|
||||||
|
|
||||||
|
conn = get_db_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
try:
|
||||||
|
cursor.execute("DELETE FROM domains WHERE domain = %s", (dom,))
|
||||||
|
conn.commit()
|
||||||
|
if cursor.rowcount > 0:
|
||||||
|
print(f"✔ Domain '{dom}' wurde gelöscht.")
|
||||||
|
else:
|
||||||
|
print("❌ Domain nicht gefunden.")
|
||||||
|
except mysql.connector.Error as e:
|
||||||
|
if e.errno == 1451:
|
||||||
|
print("❌ Fehler: Domain kann nicht gelöscht werden, da noch User oder Aliase damit verknüpft sind!")
|
||||||
|
else:
|
||||||
|
print(f"❌ Fehler: {e}")
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def change_pw():
|
||||||
|
email = input("E-Mail für PW-Wechsel: ")
|
||||||
|
if '@' not in email: return
|
||||||
|
u, d = email.split('@')
|
||||||
|
pw = getpass.getpass("Neues Passwort: ")
|
||||||
|
hashed = hash_pw(pw)
|
||||||
|
conn = get_db_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("UPDATE accounts SET password = %s WHERE username = %s AND domain = %s", (hashed, u, d))
|
||||||
|
conn.commit()
|
||||||
|
print("✔ Passwort aktualisiert.")
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def toggle_status():
|
||||||
|
email = input("E-Mail zum (De)aktivieren: ")
|
||||||
|
if '@' not in email: return
|
||||||
|
u, d = email.split('@')
|
||||||
|
conn = get_db_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("UPDATE accounts SET enabled = NOT enabled WHERE username = %s AND domain = %s", (u, d))
|
||||||
|
conn.commit()
|
||||||
|
print("✔ Status geändert.")
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
# --- MAIN ---
|
||||||
|
|
||||||
|
def menu():
|
||||||
|
while True:
|
||||||
|
print("\n" + "="*60)
|
||||||
|
print(f" VMAIL MANAGER ({hostname})")
|
||||||
|
print("="*60)
|
||||||
|
print("1) Show Users 6) Del Alias")
|
||||||
|
print("2) Add User (1024MB) 7) Domains (Show/Add/Del)")
|
||||||
|
print("3) Del User 8) Change Password")
|
||||||
|
print("4) Show Aliases 9) Toggle Status (An/Aus)")
|
||||||
|
print("5) Add Alias 0) Beenden")
|
||||||
|
|
||||||
|
cmd = input("\nAuswahl > ")
|
||||||
|
if cmd == '1': show_users()
|
||||||
|
elif cmd == '2': add_user()
|
||||||
|
elif cmd == '3': del_user()
|
||||||
|
elif cmd == '4': show_aliases()
|
||||||
|
elif cmd == '5': add_alias()
|
||||||
|
elif cmd == '6': del_alias()
|
||||||
|
elif cmd == '7':
|
||||||
|
show_domains()
|
||||||
|
sub_cmd = input("\n[A]dd Domain, [D]el Domain oder [Enter] zurück: ").lower()
|
||||||
|
if sub_cmd == 'a': add_domain()
|
||||||
|
elif sub_cmd == 'd': del_domain()
|
||||||
|
elif cmd == '8': change_pw()
|
||||||
|
elif cmd == '9': toggle_status()
|
||||||
|
elif cmd == '0': break
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
menu()
|
||||||
Reference in New Issue
Block a user