class ASF::Mail

Public Class Methods

_autosubid(dom, list) click to toggle source

Convert dom, list to form used in mail_list_autosub.yml This changed in March 2022 from dom-list format

# File lib/whimsy/asf/mail.rb, line 231
def self._autosubid(dom, list)
  return "#{list}@#{dom}"
end
_cannot_sub() click to toggle source
# File lib/whimsy/asf/mail.rb, line 52
def self._cannot_sub
  self._load_auto()
  @auto[:disallowed] + @auto[:intrinsic]
end
_cannot_unsub() click to toggle source
# File lib/whimsy/asf/mail.rb, line 57
def self._cannot_unsub
  self._load_auto()
  @auto[:intrinsic]
end
_chairs_allowed() click to toggle source
# File lib/whimsy/asf/mail.rb, line 67
def self._chairs_allowed
  self._load_auto()
  @auto[:chairs]
end
_committers_allowed() click to toggle source
# File lib/whimsy/asf/mail.rb, line 62
def self._committers_allowed
  self._load_auto()
  @auto[:committers]
end
_deprecated() click to toggle source

list of mailing lists that aren’t actively seeking new subscribers

# File lib/whimsy/asf/mail.rb, line 47
def self._deprecated
  self._load_auto()
  @auto[:deprecated]
end
_load_auto() click to toggle source

Load the auto-subscription file

# File lib/whimsy/asf/mail.rb, line 268
def self._load_auto
  apmail_bin = File.join(ASF::Config[:puppet_data], 'apmail_bin') # Loaded by puppet
  auto_file = File.join(apmail_bin, 'mail_list_autosub.yml')
  auto_mtime = File.mtime(auto_file) # fetch this up front in case file updated during loading
  if not @auto or auto_mtime != @auto_mtime
    @auto = YAML.load_file(auto_file)
    @auto_mtime = auto_mtime
  end
end
_load_flags() click to toggle source

Load the flags file

# File lib/whimsy/asf/mail.rb, line 279
def self._load_flags
  # flags for each mailing list
  @list_flags ||= File.join(ASF::Config[:subscriptions], 'list-flags')
  if not @flags or File.mtime(@list_flags) != @flags_mtime
    lists = []
    File.open(@list_flags).each do |line|
      if line.match(/(?:^F:-([a-zA-Z]{26}) )?(\S+) (\S+)/)
        flags, dom, list = $1, $2, $3
        next if list =~ /^infra-[a-z]$/ or (dom == 'incubator' and list == 'infra-dev')

        lists << [dom, list, flags || '']
      else
        raise "Unexpected flags: #{line}"
      end
    end
    @flags = lists
    @flags_hash = lists.map{|d,l,f| ["#{l}@#{d}",f]}.to_h
    @flags_mtime = File.mtime(@list_flags)
  end
end
_members_allowed() click to toggle source
# File lib/whimsy/asf/mail.rb, line 72
def self._members_allowed
  self._load_auto()
  @auto[:members] + @auto[:chairs]
end
archivelistid(dom, list) click to toggle source

Convert dom, list to form currently used in subreq.py

# File lib/whimsy/asf/mail.rb, line 222
def self.archivelistid(dom, list)
  return "apachecon-#{list}" if dom == 'apachecon.com'
  return list if dom == 'apache.org'

  dom.sub('.apache.org', '-') + list
end
canmod(ldap_pmcs, lidonly = true) click to toggle source

which lists are available for automatic moderation via Whimsy? Params: ldap_pmcs: list of (P)PMC mail_list names to which the user belongs (as owner) lid_only: return lid instead of [dom,list,lid] Return: an array of entries: lid or [dom,list,lid]

# File lib/whimsy/asf/mail.rb, line 156
def self.canmod(ldap_pmcs, lidonly = true)
  allowed = []
  parse_flags do |dom, list, _|
    autoid = _autosubid(dom, list)
    next if self._deprecated.include? autoid
    next if self._cannot_sub.include? autoid
    lid = archivelistid(dom, list)

    if ldap_pmcs.include? dom.sub('.apache.org', '')
      if lidonly
        allowed << lid
      else
        allowed << [dom, list, lid]
      end
    end
  end
  allowed
end
canread(listid, member=false, pmc_chair=false, ldap_pmcs=[]) click to toggle source

Can a list be read by the person with the specified attributes? Note: this is different from cansub - not all readable lists can be self-subscribed

Params: listid: list@domain member: true if member pmc_chair: true if pmc_chair ldap_pmcs: list of (P)PMC mail_list names to which the user belongs Return true if person is allowed to read the list nil if the list is not known otherwise true

# File lib/whimsy/asf/mail.rb, line 95
def self.canread(listid, member=false, pmc_chair=false, ldap_pmcs=[])
  flags = getflags(listid)
  return nil if flags.nil? # Not a known list
  return true unless isModSub?(flags) # subscription not needed
  return true if self._cannot_unsub.include? listid # must be system maintained, so assume OK
  return true if self._committers_allowed().include?(listid)
  return true if member # They can read anything
  return true if pmc_chair and self._chairs_allowed.include? listid
  return true if ldap_pmcs and ldap_pmcs.include? listid.split('@')[-1].sub('.apache.org', '')
  false
end
cansub(member, pmc_chair, ldap_pmcs, lidonly = true) click to toggle source

which lists are available for subscription via Whimsy? Params: member: true if member pmc_chair: true if pmc_chair ldap_pmcs: list of (P)PMC mail_list names to which the user belongs lid_only: return lid instead of [dom,list,lid] Return: an array of entries: lid or [dom,list,lid]

# File lib/whimsy/asf/mail.rb, line 114
def self.cansub(member, pmc_chair, ldap_pmcs, lidonly = true)
  allowed = []
  parse_flags do |dom, list, f|
    autoid = _autosubid(dom, list)
    next if self._deprecated.include? autoid
    next if self._cannot_sub.include? autoid
    lid = archivelistid(dom, list)

    cansub = false
    modsub = isModSub?(f)
    if not modsub # subs not moderated; allow all
      cansub = true
    elsif self._committers_allowed().include?(autoid) # always allowed
      cansub = true
    else # subs are moderated
      if member
        if list == 'private' or self._members_allowed.include?(autoid)
          cansub = true
        end
      elsif ldap_pmcs and list == 'private' and ldap_pmcs.include? dom.sub('.apache.org', '')
        cansub = true
      end
      if pmc_chair and self._chairs_allowed.include? autoid
        cansub = true
      end
    end
    if cansub
      if lidonly
        allowed << lid
      else
        allowed << [dom, list, lid]
      end
    end
  end
  allowed
end
configure() click to toggle source

common configuration for sending mail; loads :sendmail configuration from ~/.whimsy if available; otherwise default to disable openssl verification as that is what it required in order to work on the infrastructure provided whimsy-vm.

# File lib/whimsy/asf/mail.rb, line 179
def self.configure
  # fetch overrides
  sendmail = ASF::Config.get(:sendmail)

  if sendmail
    # convert string keys to symbols
    options = Hash[sendmail.map {|key, value| [key.to_sym, value]}]

    # extract delivery method
    method = options.delete(:delivery_method).to_sym
  else
    # provide defaults that work on whimsy-vm* infrastructure.  Since
    # procmail is configured with a self-signed certificate, verification
    # isn't a possibility
    method = :smtp
    options = {openssl_verify_mode: 'none'}
  end

  ::Mail.defaults do
    delivery_method method, options
  end
end
getflags(listid) click to toggle source

get flags for list@domain; nil if not found

# File lib/whimsy/asf/mail.rb, line 316
def self.getflags(listid)
  self._load_flags()
  @flags_hash[listid]
end
isModSub?(flags) click to toggle source

Do the flags indicate subscription moderation?

# File lib/whimsy/asf/mail.rb, line 322
def self.isModSub?(flags)
  flags.include? 's'
end
list() click to toggle source

return a Hash containing complete list of all known emails, and the ASF::Person that is associated with that email.

# File lib/whimsy/asf/mail.rb, line 8
def self.list
  begin
    return @list.to_h if @list
  rescue NoMethodError, WeakRef::RefError
  end

  list = {}

  # load info from LDAP
  people = ASF::Person.preload(['mail', 'asf-altEmail'])
  people.each do |person|
    (person.mail + person.alt_email).each do |mail|
      list[mail.downcase] = person
    end
  end

  # load all member emails in one pass
  ASF::Member.each do |id, text|
    Member.emails(text).each do |mail|
      list[mail.downcase] ||= Person.find(id)
    end
  end

  # load all ICLA emails in one pass
  ASF::ICLA.each do |icla|
    person = Person.find(icla.id)
    icla.emails.each do |email|
      list[email.downcase] ||= person
    end
    next if icla.noId?

    list["#{icla.id.downcase}@apache.org"] ||= person
  end

  @list = WeakRef.new(list)
  list
end
listdom2listkey(listdom) click to toggle source

Convert list@host.apache.org to host-list (listkey) style

# File lib/whimsy/asf/mail.rb, line 216
def self.listdom2listkey(listdom)
  list, dom = listdom.split('@')
  self.archivelistid(dom, list)
end
parse_flags(filter=nil) { |d, l, f| ... } click to toggle source

parse the flags F:-aBcdeFgHiJklMnOpqrSTUVWXYz domain list Input: filter = RE to match against the flags, e.g. /s/ for subsmod Output: yields: domain, list, flags

# File lib/whimsy/asf/mail.rb, line 306
def self.parse_flags(filter=nil)
  self._load_flags()
  @flags.each do |d, l, f|
    next if filter and f !~ filter

    yield [d, l, f]
  end
end
qmail_ids() click to toggle source

List of .qmail files that could clash with user ids (See: INFRA-14566)

# File lib/whimsy/asf/mail.rb, line 203
def self.qmail_ids
  return [] unless File.exist? '/srv/subscriptions/qmail.ids'

  File.read('/srv/subscriptions/qmail.ids').split
end
taken?(id) click to toggle source

Is the id used by qmail? See also ASF::ICLA.taken?

# File lib/whimsy/asf/mail.rb, line 211
def self.taken?(id)
  self.qmail_ids.include? id
end
to_canonical(email) click to toggle source

Canonicalise an email address, removing aliases and ignored punctuation and downcasing the name if safe to do so

Currently only handles aliases for @gmail.com and @googlemail.com

All domains are converted to lower-case

The case of the name part is preserved since some providers may be case-sensitive Almost all providers ignore case in names, however that is not guaranteed

# File lib/whimsy/asf/mail.rb, line 244
def self.to_canonical(email)
  parts = email.split('@')
  if parts.length == 2
    name, dom = parts
    return email if name.empty? || dom.empty?

    dom.downcase!
    dom = 'gmail.com' if dom == 'googlemail.com' # same mailbox
    if dom == 'gmail.com'
      return name.sub(/\+.*/, '').gsub('.', '').downcase + '@' + dom
    else
      # Effectively the same:
      dom = 'apache.org' if dom == 'minotaur.apache.org'
      # only downcase the domain (done above)
      return name + '@' + dom
    end
  end
  # Invalid; return input rather than failing
  return email
end
unsubbable?(listid) click to toggle source

Is a list available for unsubscription via Whimsy? Params: listid (a@b) Return: true or false

# File lib/whimsy/asf/mail.rb, line 80
def self.unsubbable?(listid)
  !self._cannot_unsub.include? listid
end