#!/usr/bin/env ruby require 'toml' require 'net/ldap' @configuration_file = '/etc/unipoly-mlmmj-ldap-sync.toml' @configuration = nil def read_configuration(path) begin TOML.load_file(path) rescue Errno::ENOENT => raisedError puts "Could not open configuration file: #{raisedError}" exit(1) end end def connect_ldap(conf) conn = Net::LDAP.new( host: conf['ldap']['host'], port: conf['ldap']['port'], auth: { method: :simple, username: @configuration['ldap']['auth']['username'], password: @configuration['ldap']['auth']['password'] } ) begin if conn.bind conn else puts "Failed to authenticate against LDAP server: \ #{conf['ldap']['host']}:#{conf['ldap']['port']}" exit(1) end rescue LdapError => raised_error puts "Failed to contact LDAP server: \ #{conf['ldap']['host']}:#{conf['ldap']['port']}" puts raised_error exit(1) end end def get_mlmmj_subscribers(list_name) mlmmj_list_binary = @configuration['mlmmj']['list_binary'] mlmmj_basepath = @configuration['mlmmj']['basepath'] list = "#{mlmmj_basepath}/#{list_name}@#{@configuration['domain']}" 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 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 conf['lists_remove'].include?(list_name) puts 'IGNORED' return end # Unsubscribe the address from the list. mlmmj_unsub_binary = conf['mlmmj']['unsub_binary'] `#{mlmmj_unsub_binary} -L #{list} -a #{s} -q -s` if $CHILD_STATUS.exitstatus.zero? puts 'OK' else puts 'FAILED' end end def sync_list(list_name, ldap_group_entry) # 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 #{entry.dn} with #{members.size} members" # Extract mail addresses from Mlmmj print 'Searchin mlmmj... ' mlmmj_subscribers = get_mlmmj_subscribers(list_name) puts 'OK' puts "Found #{entry.cn}@#{@configuration['domain']} with #{subscribers.size} \ subscribers" print "\n" # Add to the subscribers any missing entry # Remove successful matches ldap_members.each do |addr| add_subscriber_to(list_name, subscribers, list, addr) 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 # Parse configuration, bind to LDAP server @configuration = read_configuration(@configuration_file) conn = connect_ldap(conf) 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.empty? # Could not find matching LDAP group puts 'NOT FOUND' else puts 'OK' ldap_group_entry = matched_ldap_groups.first sync_list_with(list_name, ldap_group_entry) end end end ### # Execute script: main