blob: 534a49503ab9034ad6a6ae7d13565c442194b18b [file] [log] [blame]
require 'weakref'
module ASF
class Mail
# return a Hash containing complete list of all known emails, and the
# ASF::Person that is associated with that email.
def self.list
begin
return Hash[@list.to_a] if @list
rescue NoMethodError, WeakRef::RefError
end
list = Hash.new
# 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)
list[icla.email.downcase] ||= person
next if icla.id == 'notinavail'
list["#{icla.id.downcase}@apache.org"] ||= person
end
@list = WeakRef.new(list)
list
end
# Parse the .archives file to get the list names
def self._load_lists
apmail_bin = ASF::SVN['apmail_bin']
file = File.join(apmail_bin, '.archives')
if not @lists or File.mtime(file) != @list_mtime
lists = Hash[File.read(file).scan(
/^\s+"(\w[-\w]+)", "\/home\/apmail\/(public|private)-arch\//
)]
# Drop the infra test lists
lists.delete_if {|list| list =~ /-infra-[a-z]$/ or list == 'incubator-infra-dev' }
@lists = lists
@list_mtime = File.mtime(file)
end
end
# get a list of all mailing lists. If <tt>public_private</tt> is
# <tt>false</tt> this will be a simple list. If <tt>public_private</tt> is
# <tt>true</tt>, return a Hash where the values are either <tt>public</tt>
# or <tt>private</tt>.
def self.lists(public_private=false)
Mail._load_lists
public_private ? @lists : @lists.keys
end
def self.list_mtime
Mail._load_lists
@list_mtime
end
# list of mailing lists that aren't actively seeking new subscribers
def self.deprecated
apmail_bin = ASF::SVN['apmail_bin']
YAML.load_file(File.join(apmail_bin, 'deprecated_mailing_lists.yml'))
end
def self.cannot_sub
self._load_auto()
@auto[:disallowed]
end
def self.committers_allowed
self._load_auto()
@auto[:committers]
end
def self.chairs_allowed
self._load_auto()
@auto[:chairs]
end
def self.members_allowed
self._load_auto()
@auto[:members]+@auto[:chairs]
end
# which lists are available for subscription via Whimsy?
# member: true if member
# pmc_chair: true if pmc_chair
# ldap_pmcs: list of (P)PMC mail_list names
# lid_only: return lid instead of [dom,list,lid]
# output is and array of entries: lid or [dom,list,lid]
def self.cansub(member, pmc_chair, ldap_pmcs, lidonly = true)
allowed = []
parse_flags do |dom,list,f|
lid = archivelistid(dom,list)
next if self.cannot_sub.include? lid # probably unnecessary
cansub = false
modsub = isModSub?(f)
if not modsub # subs not moderated; allow all
cansub = true
elsif self.committers_allowed().include?(lid) # always allowed
cansub = true
else # subs are moderated
if member
if list == 'private' or self.members_allowed.include?(lid)
cansub = true
end
else
if ldap_pmcs
cansub = true if list == 'private' and ldap_pmcs.include? dom.sub('.apache.org','')
end
end
if pmc_chair and self.chairs_allowed.include? lid
cansub = true
end
end
if cansub
if lidonly
allowed << lid
else
allowed << [dom,list,lid]
end
end
end
allowed
end
# common configuration for sending mail; loads <tt>:sendmail</tt>
# configuration from <tt>~/.whimsy</tt> if available; otherwise default
# to disable openssl verification as that is what it required in order
# to work on the infrastructure provided whimsy-vm.
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.untaint]}]
# 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
# List of .qmail files that could clash with user ids (See: INFRA-14566)
def self.qmail_ids
return [] unless File.exist? '/srv/subscriptions/qmail.ids'
File.read('/srv/subscriptions/qmail.ids').split
end
# Is the id used by qmail?
# See also ASF::ICLA.taken?
def self.taken?(id)
self.qmail_ids.include? id
end
# Convert list name to form used in bin/.archives
def self.archivelistid(dom,list)
return "apachecon-#{list}" if dom == 'apachecon.com'
return list if dom == 'apache.org'
dom.sub(".apache.org",'-') + list
end
# 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
def self.to_canonical(email)
parts = email.split('@')
if parts.length == 2
name, dom = parts
return email if name.length == 0 || dom.length == 0
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
private
# Load the auto-subscription file
def self._load_auto
apmail_bin = ASF::SVN['apmail_bin']
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 the flags file
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_mtime = File.mtime(@list_flags)
end
end
# 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
def self.parse_flags(filter=nil)
self._load_flags()
@flags.each do |d,l,f|
next if filter and not f =~ filter
yield [d,l,f]
end
end
# Do the flags indicate subscription moderation?
def self.isModSub?(flags)
flags.include? 's'
end
end
class Person < Base
# find a Person by email address
def self.find_by_email(value)
value.downcase!
person = Mail.list[value]
return person if person
end
# List of inactive email addresses: currently only contains the address in
# <tt>iclas.txt</tt> if it is not contained in the list of active email
# addresses.
def obsolete_emails
return @obsolete_emails if @obsolete_emails
result = []
if icla
unless active_emails.any? {|mail| mail.downcase == icla.email.downcase}
result << icla.email
end
end
@obsolete_emails = result
end
# Active emails: primary email address, alt email addresses, and
# member email addresses.
def active_emails
(mail + alt_email + member_emails).uniq
end
# All known email addresses: includes active, obsolete, and apache.org
# email addresses. (But don't add notinavail@apache.org)
def all_mail
(active_emails + obsolete_emails + (id == 'notinavail' ? [] : ["#{id}@apache.org"])).uniq
end
end
class Committee
# mailing list for this committee. Generally returns the first name in
# the dns (e.g. whimsical). If so, it can be prefixed by a number of
# list names (e.g. dev, private) and <tt>.apache.org</tt> is to be
# appended. In some cases, the name contains an <tt>@</tt> sign and
# is the full name for the mail list.
def mail_list
case name.downcase
when 'comdev'
'community'
when 'httpcomponents'
'hc'
when 'whimsy'
'whimsical'
when 'brandmanagement'
'trademarks@apache.org'
when 'infrastructure'
'infra'
when 'dataprivacy'
'privacy@apache.org'
when 'legalaffairs'
'legal-internal@apache.org'
when 'fundraising'
'fundraising-private@apache.org'
when 'marketingandpublicity'
'press@apache.org'
when 'tac'
'travel-assistance@apache.org'
when 'w3crelations'
'w3c@apache.org'
when 'concom'
'planners@apachecon.com'
else
name
end
end
end
class Podling
# base name used in constructing mailing list name.
def mail_list
case name.downcase
when 'odftoolkit'
'odf'
else
name
end
end
end
end