fix: block usr_ signups and improve runtime portability

This commit is contained in:
zand committed 2026-10-03 18:24:56 +02:00
1 parent 8d42548c54
commit 7cc965ce52
1 file changed
+136 -37
+136 -37
View File
@@ -4,6 +4,7 @@ import argparse
import concurrent.futures import concurrent.futures
import json import json
import os import os
import pwd
import re import re
import subprocess import subprocess
import sys import sys
@@ -17,7 +18,7 @@ from pathlib import Path
import dns.resolver import dns.resolver
VERSION = "6.1" VERSION = "6.5"
SOURCES = [ SOURCES = [
"https://raw.githubusercontent.com/mehrtat/disposable-email-domain/main/domains.txt", "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", "https://disposable.github.io/disposable-email-domains/domains.txt",
] ]
RUBY_BIN = "/home/mastodon/.rbenv/versions/3.4.7/bin"
DOMAIN_RE = re.compile( DOMAIN_RE = re.compile(
r"^(?=.{1,253}$)" r"^(?=.{1,253}$)"
r"(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+" 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) 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() env = os.environ.copy()
owner = mastodon_owner(cwd)
home = owner["home"]
env["PATH"] = ( candidates = [
f"{RUBY_BIN}:" home / ".rbenv" / "shims",
"/usr/local/bin:" home / ".rbenv" / "bin",
"/usr/bin:" home / ".asdf" / "shims",
"/bin" 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" env["RAILS_ENV"] = "production"
rbenv_root = home / ".rbenv"
if rbenv_root.is_dir():
env["RBENV_ROOT"] = str(rbenv_root)
return env 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( def run_command(
command, command,
cwd, cwd,
check=True, check=True,
extra_env=None, extra_env=None,
): ):
env = command_environment() env = command_environment(cwd)
if extra_env: if extra_env:
env.update(extra_env) env.update(extra_env)
@@ -202,6 +255,7 @@ def run_command(
text=True, text=True,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, stderr=subprocess.PIPE,
preexec_fn=demote_to_mastodon_owner(cwd),
) )
if check and process.returncode != 0: if check and process.returncode != 0:
@@ -225,7 +279,6 @@ def run_command(
return process return process
def get_mastodon_blocks(mastodon_dir): def get_mastodon_blocks(mastodon_dir):
ruby = r''' ruby = r'''
rows = EmailDomainBlock rows = EmailDomainBlock
@@ -356,30 +409,40 @@ def cleanup_blocked_pending(
dry_run=False, dry_run=False,
): ):
ruby = r''' ruby = r'''dry_run = ENV["BLOCKER_DRY_RUN"] == "1"
dry_run = ENV["BLOCKER_DRY_RUN"] == "1"
checked = 0 checked = 0
matched = 0 matched = 0
matched_email = 0
matched_username = 0
deleted = 0 deleted = 0
errors = [] errors = []
User User.pending.find_each(batch_size: 100) do |user|
.where(approved: false)
.find_each(batch_size: 100) do |user|
next if user.email.blank?
next if user.account.nil? next if user.account.nil?
checked += 1 checked += 1
begin 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 += 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 if dry_run
puts "__PENDING__#{user.id}\t#{user.email}" puts "__PENDING__#{user.id}\\t#{user.account.username}\\t#{reasons.join(",")}"
next next
end end
@@ -394,7 +457,7 @@ User
rescue => e rescue => e
errors << { errors << {
id: user.id, id: user.id,
email: user.email, username: user.account&.username,
error: "#{e.class}: #{e.message}" error: "#{e.class}: #{e.message}"
} }
end end
@@ -404,6 +467,8 @@ STDOUT.write(
"__CLEANUP__" + JSON.generate({ "__CLEANUP__" + JSON.generate({
checked: checked, checked: checked,
matched: matched, matched: matched,
matched_email: matched_email,
matched_username: matched_username,
deleted: deleted, deleted: deleted,
errors: errors errors: errors
}) })
@@ -457,7 +522,6 @@ STDOUT.write(
] ]
) )
def resolve_mx( def resolve_mx(
domain, domain,
timeout, timeout,
@@ -498,7 +562,7 @@ def main():
"Disposable domains are hard-blocked; " "Disposable domains are hard-blocked; "
"MX records are report-only; " "MX records are report-only; "
"pending registrations using blocked " "pending registrations using blocked "
"email domains are removed." "email domains or usr_ usernames are removed."
) )
) )
@@ -573,7 +637,7 @@ def main():
action="store_true", action="store_true",
help=( help=(
"Do not remove pending registrations " "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 args.mastodon_dir
) )
ruby = Path(
RUBY_BIN
) / "ruby"
if not ruby.exists():
raise SystemExit(
f"Ruby non trovato: {ruby}"
)
if not ( if not (
mastodon_dir / "bin/rails" mastodon_dir / "bin/rails"
).exists(): ).exists():
@@ -629,8 +684,23 @@ def main():
f"{mastodon_dir}" f"{mastodon_dir}"
) )
owner = mastodon_owner(
mastodon_dir
)
log( 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( log(
@@ -822,6 +892,8 @@ def main():
cleanup = { cleanup = {
"checked": 0, "checked": 0,
"matched": 0, "matched": 0,
"matched_email": 0,
"matched_username": 0,
"deleted": 0, "deleted": 0,
"errors": [], "errors": [],
} }
@@ -848,6 +920,16 @@ def main():
f"{cleanup['matched']:,}" 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: if args.dry_run:
log( log(
f"[pending] da eliminare: " f"[pending] da eliminare: "
@@ -873,7 +955,7 @@ def main():
log( log(
" " " "
f"user={error['id']} " f"user={error['id']} "
f"{error['email']} " f"{error.get('username', '')} "
f"{error['error']}" f"{error['error']}"
) )
mx_to_domains = defaultdict(set) mx_to_domains = defaultdict(set)
@@ -1069,6 +1151,12 @@ def main():
"pending_blocked": "pending_blocked":
cleanup["matched"], cleanup["matched"],
"pending_matched_email":
cleanup["matched_email"],
"pending_matched_username":
cleanup["matched_username"],
"pending_deleted": "pending_deleted":
cleanup["deleted"], cleanup["deleted"],
@@ -1180,6 +1268,16 @@ def main():
f"{cleanup['matched']:,}" f"{cleanup['matched']:,}"
) )
log(
f" per email : "
f"{cleanup['matched_email']:,}"
)
log(
f" per usr_ : "
f"{cleanup['matched_username']:,}"
)
log( log(
f"Pending eliminati : " f"Pending eliminati : "
f"{cleanup['deleted']:,}" f"{cleanup['deleted']:,}"
@@ -1232,3 +1330,4 @@ if __name__ == "__main__":
log("ERRORE FATALE:") log("ERRORE FATALE:")
log(str(exc)) log(str(exc))
raise raise