146 lines
3.7 KiB
Ruby
Executable file
146 lines
3.7 KiB
Ruby
Executable file
#!/usr/bin/env ruby
|
|
|
|
require 'toml'
|
|
require 'net/ldap'
|
|
require 'English'
|
|
require 'open3'
|
|
|
|
@mailman3_exec = '/usr/bin/mailman'
|
|
@configuration_file = '/etc/mailman3-ldap-sync.toml'
|
|
@configuration = nil
|
|
|
|
def read_configuration(path)
|
|
begin
|
|
TOML.load_file(path)
|
|
rescue Errno::ENOENT => e
|
|
puts "Could not open configuration file: #{e}"
|
|
exit(1)
|
|
end
|
|
end
|
|
|
|
def connect_ldap
|
|
conn = Net::LDAP.new(
|
|
host: @configuration['ldap']['host'],
|
|
port: @configuration['ldap']['port'],
|
|
encryption: :simple_tls,
|
|
auth: {
|
|
method: :simple,
|
|
username: @configuration['ldap']['auth']['username'],
|
|
password: @configuration['ldap']['auth']['password']
|
|
}
|
|
)
|
|
|
|
ldap_addr = "#{@configuration['ldap']['host']}:#{@configuration['ldap']['port']}"
|
|
begin
|
|
if conn.bind
|
|
conn
|
|
else
|
|
puts "Failed to authenticate against LDAP server: #{ldap_addr}"
|
|
exit(1)
|
|
end
|
|
rescue Net::LDAP::LdapError => e
|
|
puts "Failed to contact LDAP server: #{ldap_addr}"
|
|
puts e
|
|
exit(1)
|
|
end
|
|
end
|
|
|
|
def get_mailman3_subscribers(list)
|
|
unless File.executable?(@mailman3_exec)
|
|
puts "could not execute #{@mailman3_exec}"
|
|
exit(1)
|
|
end
|
|
|
|
# Execute mailman and check whether it exited successfuly
|
|
raw = `#{@mailman3_exec} members #{list}`
|
|
unless $CHILD_STATUS.exitstatus.zero?
|
|
puts 'FAIL'
|
|
exit(1)
|
|
end
|
|
|
|
# Return a list of mail addresses.
|
|
raw.split("\n")
|
|
end
|
|
|
|
def add_subscribers_to(list, addresses)
|
|
# The address is not subscribed to the list yet.
|
|
print "Adding #{addresses.size} addresses... "
|
|
|
|
# Subscribe the address to the list.
|
|
Open3.popen3(@mailman3_exec, 'members', '-a', '-', list) do |stdin, stdout, _stderr, wait_thr|
|
|
stdin.write(addresses.join("\n"))
|
|
stdin.close_write
|
|
|
|
process = wait_thr.value
|
|
if process.exitstatus.zero?
|
|
puts 'OK'
|
|
else
|
|
puts 'FAILED'
|
|
end
|
|
|
|
puts '> Mailman output:'
|
|
puts stdout.read
|
|
end
|
|
end
|
|
|
|
def sync_list(list_name, ldap_group_entry, ldap_conn)
|
|
user_basetree = @configuration['ldap']['users']['basetree']
|
|
list = "#{list_name}@#{@configuration['domain']}"
|
|
|
|
ldap_members = ldap_group_entry.memberuid.map do |uid|
|
|
filter = Net::LDAP::Filter.eq('uid', uid)
|
|
matched_ldap_users = ldap_conn.search(base: user_basetree, filter: filter)
|
|
if matched_ldap_users.nil? || matched_ldap_users.empty?
|
|
''
|
|
else
|
|
user = matched_ldap_users.first
|
|
address = user.mail.first # 'mail' is an array for some reason.
|
|
|
|
address
|
|
end
|
|
end
|
|
|
|
ldap_members.select! {|e| not e.empty? }
|
|
puts "Found #{ldap_group_entry.dn} with #{ldap_members.size} members"
|
|
|
|
# Extract mail addresses from Mailman
|
|
print 'Searchin mailman... '
|
|
mailman3_subscribers = get_mailman3_subscribers(list)
|
|
puts 'OK'
|
|
puts "Found #{list} with #{mailman3_subscribers.size} subscribers"
|
|
|
|
# Add found addresses to the list.
|
|
add_subscribers_to(list, ldap_members)
|
|
end
|
|
|
|
def main
|
|
# Parse configuration, bind to LDAP server
|
|
configuration_file = ARGV[0] ? ARGV[0] : @configuration_file
|
|
@configuration = read_configuration(configuration_file)
|
|
ldap_conn = connect_ldap
|
|
|
|
domain = @configuration['domain']
|
|
list_basetree = @configuration['ldap']['lists']['basetree']
|
|
lists = @configuration['lists']
|
|
|
|
# Sync mailman3 lists with LDAP groups
|
|
lists.each do |list_name|
|
|
puts ">> Processing list #{list_name}@#{domain}"
|
|
print 'Searching LDAP... '
|
|
|
|
filter = Net::LDAP::Filter.eq('cn', list_name)
|
|
matched_ldap_groups = ldap_conn.search(base: list_basetree, filter: filter)
|
|
if matched_ldap_groups.nil? || matched_ldap_groups.empty?
|
|
# Could not find matching LDAP group
|
|
puts 'NOT FOUND'
|
|
else
|
|
puts 'OK'
|
|
ldap_group_entry = matched_ldap_groups.first
|
|
sync_list(list_name, ldap_group_entry, ldap_conn)
|
|
end
|
|
end
|
|
end
|
|
|
|
###
|
|
# Execute script:
|
|
main
|