# SPDX-License-Identifier: AGPL-3.0-or-later """Manage email aliases.""" import contextlib import pwd import sqlite3 from dataclasses import InitVar, dataclass, field from plinth import actions @dataclass class Alias: value: str name: str enabled: bool = field(init=False) status: InitVar[int] def __post_init__(self, status): self.enabled = (status != 0) @contextlib.contextmanager def _get_cursor(): """Return a DB cursor as context manager.""" # Turn ON autocommit mode db_path = '/var/lib/postfix/freedombox-aliases/aliases.sqlite3' connection = sqlite3.connect(db_path, isolation_level=None) connection.row_factory = sqlite3.Row try: cursor = connection.cursor() yield cursor finally: connection.close() def get(username): """Get all aliases of a user.""" query = 'SELECT name, value, status FROM alias WHERE value=?' with _get_cursor() as cursor: rows = cursor.execute(query, (username, )) return [Alias(**row) for row in rows] def exists(name): """Return whether alias is already taken.""" try: pwd.getpwnam(name) return True except KeyError: pass with _get_cursor() as cursor: query = 'SELECT COUNT(*) FROM alias WHERE name=?' cursor.execute(query, (name, )) return cursor.fetchone()[0] != 0 def put(username, name): """Insert if not exists a new alias.""" query = 'INSERT INTO alias (name, value, status) VALUES (?, ?, ?)' with _get_cursor() as cursor: try: cursor.execute(query, (name, username, 1)) except sqlite3.IntegrityError: pass # Alias exists, rare since we are already checking def delete(username, aliases): """Delete a set of aliases.""" query = 'DELETE FROM alias WHERE value=? AND name=?' parameter_seq = ((username, name) for name in aliases) with _get_cursor() as cursor: cursor.execute('BEGIN') cursor.executemany(query, parameter_seq) cursor.execute('COMMIT') def enable(username, aliases): """Enable a list of aliases.""" return _set_status(username, aliases, 1) def disable(username, aliases): """Disable a list of aliases.""" return _set_status(username, aliases, 0) def _set_status(username, aliases, status): """Set the status value of a list of aliases.""" query = 'UPDATE alias SET status=? WHERE value=? AND name=?' parameter_seq = ((status, username, name) for name in aliases) with _get_cursor() as cursor: cursor.execute('BEGIN') cursor.executemany(query, parameter_seq) cursor.execute('COMMIT') def first_setup(): """Create the database file and schema inside it.""" actions.superuser_run('email_server', ['aliases', 'setup']) # Create schema if not exists query = ''' BEGIN; CREATE TABLE IF NOT EXISTS alias ( name TEXT NOT NULL, value TEXT NOT NULL, status INTEGER NOT NULL, PRIMARY KEY (name) ); COMMIT; ''' with _get_cursor() as cursor: cursor.executescript(query)