diff --git a/bin/mastodon-email-blocker.py b/bin/mastodon-email-blocker.py index 8183fdd..70733b0 100755 --- a/bin/mastodon-email-blocker.py +++ b/bin/mastodon-email-blocker.py @@ -4,6 +4,7 @@ import argparse import concurrent.futures import json import os +import pwd import re import subprocess import sys @@ -17,7 +18,7 @@ from pathlib import Path import dns.resolver -VERSION = "6.1" +VERSION = "6.5" SOURCES = [ "https://raw.githubusercontent.com/mehrtat/disposable-email-domain/main/domains.txt", @@ -25,8 +26,6 @@ SOURCES = [ "https://disposable.github.io/disposable-email-domains/domains.txt", ] -RUBY_BIN = "/home/mastodon/.rbenv/versions/3.4.7/bin" - DOMAIN_RE = re.compile( r"^(?=.{1,253}$)" r"(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+" @@ -169,28 +168,82 @@ def save_json_atomic(filename, value): os.unlink(temporary) -def command_environment(): +def mastodon_owner(cwd): + path = Path(cwd).resolve() + stat = path.stat() + account = pwd.getpwuid(stat.st_uid) + + return { + "uid": stat.st_uid, + "gid": stat.st_gid, + "name": account.pw_name, + "home": Path(account.pw_dir), + } + + +def command_environment(cwd): env = os.environ.copy() + owner = mastodon_owner(cwd) + home = owner["home"] - env["PATH"] = ( - f"{RUBY_BIN}:" - "/usr/local/bin:" - "/usr/bin:" - "/bin" - ) + candidates = [ + home / ".rbenv" / "shims", + home / ".rbenv" / "bin", + home / ".asdf" / "shims", + home / ".asdf" / "bin", + home / ".local" / "share" / "mise" / "shims", + home / ".local" / "bin", + ] + path_parts = [] + + for candidate in candidates: + if candidate.is_dir(): + path_parts.append(str(candidate)) + + current_path = env.get("PATH", "") + + if current_path: + path_parts.append(current_path) + + env["PATH"] = os.pathsep.join(path_parts) + env["HOME"] = str(home) + env["USER"] = owner["name"] + env["LOGNAME"] = owner["name"] env["RAILS_ENV"] = "production" + rbenv_root = home / ".rbenv" + + if rbenv_root.is_dir(): + env["RBENV_ROOT"] = str(rbenv_root) + return env +def demote_to_mastodon_owner(cwd): + owner = mastodon_owner(cwd) + + if os.geteuid() != 0: + return None + + if owner["uid"] == 0: + return None + + def demote(): + os.initgroups(owner["name"], owner["gid"]) + os.setgid(owner["gid"]) + os.setuid(owner["uid"]) + + return demote + + def run_command( command, cwd, check=True, extra_env=None, ): - env = command_environment() + env = command_environment(cwd) if extra_env: env.update(extra_env) @@ -202,6 +255,7 @@ def run_command( text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + preexec_fn=demote_to_mastodon_owner(cwd), ) if check and process.returncode != 0: @@ -225,7 +279,6 @@ def run_command( return process - def get_mastodon_blocks(mastodon_dir): ruby = r''' rows = EmailDomainBlock @@ -356,30 +409,40 @@ def cleanup_blocked_pending( dry_run=False, ): - ruby = r''' -dry_run = ENV["BLOCKER_DRY_RUN"] == "1" + ruby = r'''dry_run = ENV["BLOCKER_DRY_RUN"] == "1" checked = 0 matched = 0 +matched_email = 0 +matched_username = 0 deleted = 0 -errors = [] +errors = [] -User - .where(approved: false) - .find_each(batch_size: 100) do |user| - - next if user.email.blank? +User.pending.find_each(batch_size: 100) do |user| next if user.account.nil? checked += 1 begin - next unless EmailDomainBlock.block?(user.email) + blocked_email = + user.email.present? && + EmailDomainBlock.block?(user.email) + + blocked_username = + user.account.username.to_s.match?(/\\Ausr_/i) + + next unless blocked_email || blocked_username matched += 1 + matched_email += 1 if blocked_email + matched_username += 1 if blocked_username + + reasons = [] + reasons << "email" if blocked_email + reasons << "username" if blocked_username if dry_run - puts "__PENDING__#{user.id}\t#{user.email}" + puts "__PENDING__#{user.id}\\t#{user.account.username}\\t#{reasons.join(",")}" next end @@ -394,7 +457,7 @@ User rescue => e errors << { id: user.id, - email: user.email, + username: user.account&.username, error: "#{e.class}: #{e.message}" } end @@ -404,6 +467,8 @@ STDOUT.write( "__CLEANUP__" + JSON.generate({ checked: checked, matched: matched, + matched_email: matched_email, + matched_username: matched_username, deleted: deleted, errors: errors }) @@ -457,7 +522,6 @@ STDOUT.write( ] ) - def resolve_mx( domain, timeout, @@ -498,7 +562,7 @@ def main(): "Disposable domains are hard-blocked; " "MX records are report-only; " "pending registrations using blocked " - "email domains are removed." + "email domains or usr_ usernames are removed." ) ) @@ -573,7 +637,7 @@ def main(): action="store_true", help=( "Do not remove pending registrations " - "whose email is hard-blocked" + "matched by email block or usr_ username" ), ) @@ -583,15 +647,6 @@ def main(): args.mastodon_dir ) - ruby = Path( - RUBY_BIN - ) / "ruby" - - if not ruby.exists(): - raise SystemExit( - f"Ruby non trovato: {ruby}" - ) - if not ( mastodon_dir / "bin/rails" ).exists(): @@ -629,8 +684,23 @@ def main(): f"{mastodon_dir}" ) + owner = mastodon_owner( + mastodon_dir + ) + log( - f"[config] Ruby: {ruby}" + f"[config] Utente Mastodon: " + f"{owner['name']} " + f"(uid={owner['uid']})" + ) + + ruby_version = run_command( + ["ruby", "-v"], + cwd=mastodon_dir, + ).stdout.strip() + + log( + f"[config] Ruby: {ruby_version}" ) log( @@ -822,6 +892,8 @@ def main(): cleanup = { "checked": 0, "matched": 0, + "matched_email": 0, + "matched_username": 0, "deleted": 0, "errors": [], } @@ -848,6 +920,16 @@ def main(): f"{cleanup['matched']:,}" ) + log( + f"[pending] per email : " + f"{cleanup['matched_email']:,}" + ) + + log( + f"[pending] per usr_ : " + f"{cleanup['matched_username']:,}" + ) + if args.dry_run: log( f"[pending] da eliminare: " @@ -873,7 +955,7 @@ def main(): log( " " f"user={error['id']} " - f"{error['email']} " + f"{error.get('username', '')} " f"{error['error']}" ) mx_to_domains = defaultdict(set) @@ -1069,6 +1151,12 @@ def main(): "pending_blocked": cleanup["matched"], + "pending_matched_email": + cleanup["matched_email"], + + "pending_matched_username": + cleanup["matched_username"], + "pending_deleted": cleanup["deleted"], @@ -1180,6 +1268,16 @@ def main(): f"{cleanup['matched']:,}" ) + log( + f" per email : " + f"{cleanup['matched_email']:,}" + ) + + log( + f" per usr_ : " + f"{cleanup['matched_username']:,}" + ) + log( f"Pending eliminati : " f"{cleanup['deleted']:,}" @@ -1232,3 +1330,4 @@ if __name__ == "__main__": log("ERRORE FATALE:") log(str(exc)) raise +