# frozen_string_literal: true require 'concurrent' require_relative '../../config/boot' require_relative '../../config/environment' require_relative 'cli_helper' module Mastodon class EmailDomainBlocksCLI < Thor include CLIHelper def self.exit_on_failure? true end desc 'list', 'List blocked e-mail domains' def list EmailDomainBlock.where(parent_id: nil).order(id: 'DESC').find_each do |entry| say(entry.domain.to_s, :white) EmailDomainBlock.where(parent_id: entry.id).order(id: 'DESC').find_each do |child| say(" #{child.domain}", :cyan) end end end option :with_dns_records, type: :boolean desc 'add DOMAIN...', 'Block e-mail domain(s)' long_desc <<-LONG_DESC Blocking an e-mail domain prevents users from signing up with e-mail addresses from that domain. You can provide one or multiple domains to the command. When the --with-dns-records option is given, an attempt to resolve the given domains' DNS records will be made and the results (A, AAAA and MX) will also be blocked. This can be helpful if you are blocking an e-mail server that has many different domains pointing to it as it allows you to essentially block it at the root. LONG_DESC def add(*domains) if domains.empty? say('No domain(s) given', :red) exit(1) end skipped = 0 processed = 0 domains.each do |domain| if EmailDomainBlock.where(domain: domain).exists? say("#{domain} is already blocked.", :yellow) skipped += 1 next end email_domain_block = EmailDomainBlock.new(domain: domain, with_dns_records: options[:with_dns_records] || false) email_domain_block.save! processed += 1 next unless email_domain_block.with_dns_records? hostnames = [] ips = [] Resolv::DNS.open do |dns| dns.timeouts = 5 hostnames = dns.getresources(email_domain_block.domain, Resolv::DNS::Resource::IN::MX).to_a.map { |e| e.exchange.to_s } ([email_domain_block.domain] + hostnames).uniq.each do |hostname| ips.concat(dns.getresources(hostname, Resolv::DNS::Resource::IN::A).to_a.map { |e| e.address.to_s }) ips.concat(dns.getresources(hostname, Resolv::DNS::Resource::IN::AAAA).to_a.map { |e| e.address.to_s }) end end (hostnames + ips).uniq.each do |hostname| another_email_domain_block = EmailDomainBlock.new(domain: hostname, parent: email_domain_block) if EmailDomainBlock.where(domain: hostname).exists? say("#{hostname} is already blocked.", :yellow) skipped += 1 next end another_email_domain_block.save! processed += 1 end end say("Added #{processed}, skipped #{skipped}", color(processed, 0)) end desc 'remove DOMAIN...', 'Remove e-mail domain blocks' def remove(*domains) if domains.empty? say('No domain(s) given', :red) exit(1) end skipped = 0 processed = 0 failed = 0 domains.each do |domain| entry = EmailDomainBlock.find_by(domain: domain) if entry.nil? say("#{domain} is not yet blocked.", :yellow) skipped += 1 next end children_count = EmailDomainBlock.where(parent_id: entry.id).count result = entry.destroy if result processed += children_count + 1 else say("#{domain} could not be unblocked.", :red) failed += 1 end end say("Removed #{processed}, skipped #{skipped}, failed #{failed}", color(processed, failed)) end private def color(processed, failed) if !processed.zero? && failed.zero? :green elsif failed.zero? :yellow else :red end end end end