mailman3-ldap-sync/unipoly-mlmmj-ldap-sync

173 lines
4.6 KiB
Text
Raw Normal View History

#!/usr/bin/env ruby
require 'toml'
require 'net/ldap'
require 'English'
2018-12-23 14:17:09 +01:00
@configuration_file = '/etc/unipoly-mlmmj-ldap-sync.toml'
@configuration = nil
def read_configuration(path)
2018-12-23 14:17:09 +01:00
begin
TOML.load_file(path)
rescue Errno::ENOENT => raised_error
puts "Could not open configuration file: #{raised_error}"
2018-12-23 14:17:09 +01:00
exit(1)
end
end
def connect_ldap
conn = Net::LDAP.new(
host: @configuration['ldap']['host'],
port: @configuration['ldap']['port'],
2018-12-23 14:17:09 +01:00
auth: {
method: :simple,
username: @configuration['ldap']['auth']['username'],
password: @configuration['ldap']['auth']['password']
}
)
2018-12-23 16:40:02 +01:00
ldap_addr = @configuration['ldap']['host'] + ':' +
2018-12-23 16:41:10 +01:00
@configuration['ldap']['port'].to_s
begin
if conn.bind
conn
else
puts "Failed to authenticate against LDAP server: #{ldap_addr}"
exit(1)
end
2018-12-23 14:17:09 +01:00
rescue LdapError => raised_error
puts "Failed to contact LDAP server: #{ldap_addr}"
2018-12-23 14:17:09 +01:00
puts raised_error
exit(1)
end
end
def get_mlmmj_subscribers(list)
2018-12-23 14:17:09 +01:00
mlmmj_list_binary = @configuration['mlmmj']['list_binary']
unless File.executable?(mlmmj_list_binary)
puts "could not execute #{mlmmj_list_binary}"
exit(1)
end
# Execute mlmmj-list and check whether it exited successfuly
raw = `#{mlmmj_list_binary} -L #{list} -s`
unless $CHILD_STATUS.exitstatus.zero?
puts 'FAIL'
exit(1)
end
# Return a list of mail addresses.
raw.split("\n")
end
def add_subscriber_to(list_name, subscribers, list, addr)
if subscribers.include?(addr)
# The address is already subscribed to the list: we're done with it.
subscribers.delete(addr)
else
# The address is not subscribed to the list yet.
print "Adding #{addr}... "
# Since the configuration file doesn't say we have to add new subscribers
# to this list, we ignore this action.
unless @configuration['lists_add'].include?(list_name)
puts 'IGNORED'
return
end
# Subscribe the address to the list.
mlmmj_sub_binary = @configuration['mlmmj']['sub_binary']
`#{mlmmj_sub_binary} -L #{list} -a #{addr} -q -f -s`
if $CHILD_STATUS.exitstatus.zero?
puts 'OK'
else
puts 'FAILED'
end
end
end
2018-12-23 14:17:09 +01:00
def remove_subscriber_from(list_name, list, addr)
print "Removing #{addr}... "
# We ignore this action if the configuration file doesn't say we are allowed
# to remove addresses from this list.
unless @configuration['lists_remove'].include?(list_name)
2018-12-23 14:17:09 +01:00
puts 'IGNORED'
return
end
# Unsubscribe the address from the list.
mlmmj_unsub_binary = @configuration['mlmmj']['unsub_binary']
2019-02-25 17:38:27 +01:00
`#{mlmmj_unsub_binary} -L #{list} -a #{addr} -q -s`
2018-12-23 14:17:09 +01:00
if $CHILD_STATUS.exitstatus.zero?
puts 'OK'
else
puts 'FAILED'
end
end
def sync_list(list_name, ldap_group_entry)
mlmmj_basepath = @configuration['mlmmj']['basepath']
list = "#{mlmmj_basepath}/#{list_name}@#{@configuration['domain']}"
2018-12-23 14:17:09 +01:00
# Members are formatted as
# 'mail=user@domain.tld,ou=Users,dc=unipoly,dc=epfl,dc=ch': we extract the
# mail address.
ldap_members = ldap_group_entry.uniquemember.map do |dn|
/mail=([^,]+),/.match(dn).values_at(1).first.downcase
end
puts "Found #{ldap_group_entry.dn} with #{ldap_members.size} members"
2018-12-23 14:17:09 +01:00
# Extract mail addresses from Mlmmj
print 'Searchin mlmmj... '
mlmmj_subscribers = get_mlmmj_subscribers(list)
2018-12-23 14:17:09 +01:00
puts 'OK'
puts "Found #{list_name}@#{@configuration['domain']} with\
#{mlmmj_subscribers.size} subscribers"
2018-12-23 14:17:09 +01:00
# Add to the subscribers any missing entry
# Remove successful matches
ldap_members.each do |addr|
add_subscriber_to(list_name, mlmmj_subscribers, list, addr)
2018-12-23 14:17:09 +01:00
end
# Remove remaining "subcribers" (= they did not match with LDAP members)
mlmmj_subscribers.each do |addr|
remove_subscriber_from(list_name, list, addr)
end
print "\n"
end
def main
2018-12-23 14:17:09 +01:00
# Parse configuration, bind to LDAP server
@configuration = read_configuration(@configuration_file)
2018-12-23 16:40:02 +01:00
conn = connect_ldap
2018-12-23 14:17:09 +01:00
domain = @configuration['domain']
basetree = @configuration['ldap']['lists']['basetree']
lists = (@configuration['lists_add'] + @configuration['lists_remove']).uniq
# Sync Mlmmj 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 = conn.search(base: basetree, filter: filter)
if matched_ldap_groups.nil? || matched_ldap_groups.empty?
2018-12-23 14:17:09 +01:00
# Could not find matching LDAP group
puts 'NOT FOUND'
else
2018-12-23 14:17:09 +01:00
puts 'OK'
ldap_group_entry = matched_ldap_groups.first
sync_list(list_name, ldap_group_entry)
end
end
end
2018-12-23 14:17:09 +01:00
###
# Execute script:
main