You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

138 lines
3.9 KiB

  1. # frozen_string_literal: true
  2. require 'concurrent'
  3. require_relative '../../config/boot'
  4. require_relative '../../config/environment'
  5. require_relative 'cli_helper'
  6. module Mastodon
  7. class EmailDomainBlocksCLI < Thor
  8. include CLIHelper
  9. def self.exit_on_failure?
  10. true
  11. end
  12. desc 'list', 'List blocked e-mail domains'
  13. def list
  14. EmailDomainBlock.where(parent_id: nil).order(id: 'DESC').find_each do |entry|
  15. say(entry.domain.to_s, :white)
  16. EmailDomainBlock.where(parent_id: entry.id).order(id: 'DESC').find_each do |child|
  17. say(" #{child.domain}", :cyan)
  18. end
  19. end
  20. end
  21. option :with_dns_records, type: :boolean
  22. desc 'add DOMAIN...', 'Block e-mail domain(s)'
  23. long_desc <<-LONG_DESC
  24. Blocking an e-mail domain prevents users from signing up
  25. with e-mail addresses from that domain. You can provide one or
  26. multiple domains to the command.
  27. When the --with-dns-records option is given, an attempt to resolve the
  28. given domains' DNS records will be made and the results (A, AAAA and MX) will
  29. also be blocked. This can be helpful if you are blocking an e-mail server that
  30. has many different domains pointing to it as it allows you to essentially block
  31. it at the root.
  32. LONG_DESC
  33. def add(*domains)
  34. if domains.empty?
  35. say('No domain(s) given', :red)
  36. exit(1)
  37. end
  38. skipped = 0
  39. processed = 0
  40. domains.each do |domain|
  41. if EmailDomainBlock.where(domain: domain).exists?
  42. say("#{domain} is already blocked.", :yellow)
  43. skipped += 1
  44. next
  45. end
  46. email_domain_block = EmailDomainBlock.new(domain: domain, with_dns_records: options[:with_dns_records] || false)
  47. email_domain_block.save!
  48. processed += 1
  49. next unless email_domain_block.with_dns_records?
  50. hostnames = []
  51. ips = []
  52. Resolv::DNS.open do |dns|
  53. dns.timeouts = 5
  54. hostnames = dns.getresources(email_domain_block.domain, Resolv::DNS::Resource::IN::MX).to_a.map { |e| e.exchange.to_s }
  55. ([email_domain_block.domain] + hostnames).uniq.each do |hostname|
  56. ips.concat(dns.getresources(hostname, Resolv::DNS::Resource::IN::A).to_a.map { |e| e.address.to_s })
  57. ips.concat(dns.getresources(hostname, Resolv::DNS::Resource::IN::AAAA).to_a.map { |e| e.address.to_s })
  58. end
  59. end
  60. (hostnames + ips).uniq.each do |hostname|
  61. another_email_domain_block = EmailDomainBlock.new(domain: hostname, parent: email_domain_block)
  62. if EmailDomainBlock.where(domain: hostname).exists?
  63. say("#{hostname} is already blocked.", :yellow)
  64. skipped += 1
  65. next
  66. end
  67. another_email_domain_block.save!
  68. processed += 1
  69. end
  70. end
  71. say("Added #{processed}, skipped #{skipped}", color(processed, 0))
  72. end
  73. desc 'remove DOMAIN...', 'Remove e-mail domain blocks'
  74. def remove(*domains)
  75. if domains.empty?
  76. say('No domain(s) given', :red)
  77. exit(1)
  78. end
  79. skipped = 0
  80. processed = 0
  81. failed = 0
  82. domains.each do |domain|
  83. entry = EmailDomainBlock.find_by(domain: domain)
  84. if entry.nil?
  85. say("#{domain} is not yet blocked.", :yellow)
  86. skipped += 1
  87. next
  88. end
  89. children_count = EmailDomainBlock.where(parent_id: entry.id).count
  90. result = entry.destroy
  91. if result
  92. processed += children_count + 1
  93. else
  94. say("#{domain} could not be unblocked.", :red)
  95. failed += 1
  96. end
  97. end
  98. say("Removed #{processed}, skipped #{skipped}, failed #{failed}", color(processed, failed))
  99. end
  100. private
  101. def color(processed, failed)
  102. if !processed.zero? && failed.zero?
  103. :green
  104. elsif failed.zero?
  105. :yellow
  106. else
  107. :red
  108. end
  109. end
  110. end
  111. end