class Schleuder::List

Public Class Methods

by_recipient(recipient) click to toggle source
# File lib/schleuder/list.rb, line 223
def self.by_recipient(recipient)
  listname = recipient.gsub(/-(sendkey|request|owner|bounce)@/, '@')
  where(email: listname).first
end
configurable_attributes() click to toggle source
# File lib/schleuder/list.rb, line 89
def self.configurable_attributes
  @configurable_attributes ||= begin
    all = self.validators.map(&:attributes).flatten.uniq.compact.sort
    all - [:email, :fingerprint]
  end
end
listdir(listname) click to toggle source
# File lib/schleuder/list.rb, line 274
def self.listdir(listname)
  File.join(
      Conf.lists_dir,
      listname.split('@').reverse
    )
end

Public Instance Methods

admin_only?(keyword) click to toggle source
# File lib/schleuder/list.rb, line 333
def admin_only?(keyword)
  keywords_admin_only.include?(keyword)
end
admins() click to toggle source
# File lib/schleuder/list.rb, line 108
def admins
  subscriptions.where(admin: true)
end
bounce_address() click to toggle source
# File lib/schleuder/list.rb, line 240
def bounce_address
  @bounce_address ||= email.gsub('@', '-bounce@')
end
check_keys() click to toggle source
# File lib/schleuder/list.rb, line 162
def check_keys
  now = Time.now
  checkdate = now + (60 * 60 * 24 * 14) # two weeks
  unusable = []
  expiring = []

  keys.each do |key|
    expiry = key.subkeys.first.expires
    if expiry && expiry > now && expiry < checkdate
      # key expires in the near future
      expdays = ((expiry - now)/86400).to_i
      expiring << [key, expdays]
    end

    if ! key.usable?
      unusable << [key, key.usability_issue]
    end
  end

  text = ''
  expiring.each do |key, days|
    text << I18n.t('key_expires',
                      days: days,
                      key_summary: key.summary
                  )
    text << "\n"
  end

  unusable.each do |key, usability_issue|
    text << I18n.t('key_unusable',
                      usability_issue: usability_issue,
                      key_summary: key.summary
                  )
    text << "\n"
  end
  text
end
cleanup() click to toggle source
TODO: place this somewhere sensible.
Call cleanup when script finishes.

Signal.trap(0, proc { @list.cleanup })

# File lib/schleuder/list.rb, line 256
def cleanup
  if @gpg_agent_pid
    Process.kill('TERM', @gpg_agent_pid.to_i)
  end
rescue => e
  $stderr.puts "Failed to kill gpg-agent: #{e}"
end
delete_key(fingerprint) click to toggle source
# File lib/schleuder/list.rb, line 135
def delete_key(fingerprint)
  if key = keys(fingerprint).first
    key.delete!
    true
  else
    false
  end
end
export_key(fingerprint=self.fingerprint) click to toggle source
# File lib/schleuder/list.rb, line 144
def export_key(fingerprint=self.fingerprint)
  key = keys(fingerprint).first
  if key.blank?
    return false
  end
  key.armored
end
fetch_keys(input) click to toggle source
# File lib/schleuder/list.rb, line 215
def fetch_keys(input)
  key_fetcher.fetch(input)
end
fingerprint=(arg) click to toggle source
# File lib/schleuder/list.rb, line 268
def fingerprint=(arg)
  if arg
    write_attribute(:fingerprint, arg.gsub(/\s*/, '').gsub(/^0x/, '').chomp.upcase)
  end
end
from_admin?(mail) click to toggle source
# File lib/schleuder/list.rb, line 337
def from_admin?(mail)
  return false if ! mail.was_validly_signed?
  admins.find do |admin|
    admin.fingerprint == mail.signing_key.fingerprint
  end.presence || false
end
gpg() click to toggle source
# File lib/schleuder/list.rb, line 244
def gpg
  @gpg_ctx ||= begin
    # TODO: figure out why set it again...
    # Set GNUPGHOME when list is created.
    set_gnupg_home
    GPGME::Ctx.new armor: true
  end
end
gpg_sign_options() click to toggle source
# File lib/schleuder/list.rb, line 264
def gpg_sign_options
  {sign: true, sign_as: self.fingerprint}
end
import_key(importable) click to toggle source
# File lib/schleuder/list.rb, line 124
def import_key(importable)
  gpg.keyimport(importable)
end
import_key_and_find_fingerprint(key_material) click to toggle source
# File lib/schleuder/list.rb, line 128
def import_key_and_find_fingerprint(key_material)
  return nil if key_material.blank?

  import_result = import_key(key_material)
  gpg.interpret_import_result(import_result)
end
key(fingerprint=self.fingerprint) click to toggle source
# File lib/schleuder/list.rb, line 112
def key(fingerprint=self.fingerprint)
  keys(fingerprint).first
end
key_fetcher() click to toggle source
# File lib/schleuder/list.rb, line 219
def key_fetcher
  @key_fetcher ||= KeyFetcher.new(self)
end
key_minimal_base64_encoded(fingerprint=self.fingerprint) click to toggle source
# File lib/schleuder/list.rb, line 152
def key_minimal_base64_encoded(fingerprint=self.fingerprint)
  key = keys(fingerprint).first
  
  if key.blank?
    return false
  end
  
  Base64.strict_encode64(key.minimal)
end
keys(identifier=nil, secret_only=nil) click to toggle source
# File lib/schleuder/list.rb, line 120
def keys(identifier=nil, secret_only=nil)
  gpg.find_keys(identifier, secret_only)
end
keywords_admin_notify() click to toggle source
# File lib/schleuder/list.rb, line 325
def keywords_admin_notify
  Array(read_attribute(:keywords_admin_notify))
end
keywords_admin_only() click to toggle source
# File lib/schleuder/list.rb, line 329
def keywords_admin_only
  Array(read_attribute(:keywords_admin_only))
end
listdir() click to toggle source
# File lib/schleuder/list.rb, line 281
def listdir
  @listdir ||= self.class.listdir(self.email)
end
logfile() click to toggle source
# File lib/schleuder/list.rb, line 96
def logfile
  @logfile ||= File.join(Conf.listlogs_dir, self.email.split('@').reverse, 'list.log')
end
logger() click to toggle source
# File lib/schleuder/list.rb, line 100
def logger
  @logger ||= Listlogger.new(self)
end
owner_address() click to toggle source
# File lib/schleuder/list.rb, line 236
def owner_address
  @owner_address ||= email.gsub('@', '-owner@')
end
refresh_keys() click to toggle source
# File lib/schleuder/list.rb, line 200
def refresh_keys
  # reorder keys so the update pattern is random
  output = self.keys.shuffle.map do |key|
    # Sleep a short while to make traffic analysis less easy.
    sleep rand(1.0..5.0)
    key_fetcher.fetch(key.fingerprint, 'key_updated').presence
  end
  # Filter out some "noise" (if a key was unchanged, it wasn't really updated, was it?)
  # It would be nice to prevent these "false" lines in the first place, but I don't know how.
  output.reject! do |line|
    line.match('updated \(unchanged\)')
  end
  output.compact.join("\n")
end
request_address() click to toggle source
# File lib/schleuder/list.rb, line 232
def request_address
  @request_address ||= email.gsub('@', '-request@')
end
secret_key() click to toggle source
# File lib/schleuder/list.rb, line 116
def secret_key
  keys(self.fingerprint, true).first
end
send_list_key_to_subscriptions() click to toggle source
# File lib/schleuder/list.rb, line 348
def send_list_key_to_subscriptions
  mail = Mail.new
  mail.from = self.email
  mail.subject = I18n.t('list_public_key_subject')
  mail.body = I18n.t('list_public_key_attached')
  mail.attach_list_key!(self)
  send_to_subscriptions(mail)
  true
end
send_to_subscriptions(mail, incoming_mail=nil) click to toggle source
# File lib/schleuder/list.rb, line 358
def send_to_subscriptions(mail, incoming_mail=nil)
  logger.debug 'Sending to subscriptions.'
  mail.add_internal_footer!
  self.subscriptions.each do |subscription|
    begin
      
      if ! subscription.delivery_enabled
        logger.info "Not sending to #{subscription.email}: delivery is disabled."
        next
      end
      
      if ! self.deliver_selfsent && incoming_mail&.was_validly_signed? && ( subscription == incoming_mail&.signer )
        logger.info "Not sending to #{subscription.email}: delivery of self sent is disabled."
        next
      end
      
      subscription.send_mail(mail, incoming_mail)
      
    rescue => exc
      msg = I18n.t('errors.delivery_error',
                   { email: subscription.email, error: exc.to_s })
      logger.error msg
      logger.error exc
    end
  end
end
sendkey_address() click to toggle source
# File lib/schleuder/list.rb, line 228
def sendkey_address
  @sendkey_address ||= email.gsub('@', '-sendkey@')
end
set_attribute(attrib, value) click to toggle source
# File lib/schleuder/list.rb, line 344
def set_attribute(attrib, value)
  self.send("#{attrib}=", value)
end
subscribe(email, fingerprint=nil, adminflag=nil, deliveryflag=nil, key_material=nil) click to toggle source

A convenience-method to simplify other code.

# File lib/schleuder/list.rb, line 286
def subscribe(email, fingerprint=nil, adminflag=nil, deliveryflag=nil, key_material=nil)
  messages = nil
  args = {
      list_id: self.id,
      email: email
  }
  if key_material.present?
    fingerprint, messages = import_key_and_find_fingerprint(key_material)
  end
  args[:fingerprint] = fingerprint
  # ActiveRecord does not treat nil as falsy for boolean columns, so we
  # have to avoid that in order to not receive an invalid object. The
  # database will use the column's default-value if no value is being
  # given. (I'd rather not duplicate the defaults here.)
  if ! adminflag.nil?
    args[:admin] = adminflag
  end
  if ! deliveryflag.nil?
    args[:delivery_enabled] = deliveryflag
  end
  subscription = Subscription.create(args)
  [subscription, messages]
end
to_s() click to toggle source
# File lib/schleuder/list.rb, line 104
def to_s
  email
end
unsubscribe(email, delete_key=false) click to toggle source
# File lib/schleuder/list.rb, line 310
def unsubscribe(email, delete_key=false)
  sub = subscriptions.where(email: email).first
  if sub.blank?
    false
  end

  if ! sub.destroy
    return sub
  end

  if delete_key
    sub.delete_key
  end
end

Private Instance Methods

delete_listdirs() click to toggle source
# File lib/schleuder/list.rb, line 391
def delete_listdirs
  if File.exist?(self.listdir)
    FileUtils.rm_rf(self.listdir, secure: true)
    Schleuder.logger.info "Deleted #{self.listdir}"
  end
  # If listlogs_dir is different from lists_dir, the logfile still exists
  # and needs to be deleted, too.
  logfile_dir = File.dirname(self.logfile)
  if File.exist?(logfile_dir)
    FileUtils.rm_rf(logfile_dir, secure: true)
    Schleuder.logger.info "Deleted #{logfile_dir}"
  end
  true
rescue => exc
  # Don't use list-logger here — if the list-dir isn't present we can't log to it!
  Schleuder.logger.error "Error while deleting listdir: #{exc}"
  return false
end
set_gnupg_home() click to toggle source
# File lib/schleuder/list.rb, line 387
def set_gnupg_home
  ENV['GNUPGHOME'] = listdir
end