From b62ff3b37a538d6f545a42a8d7c944d2b15e6c31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Floure?= Date: Sun, 23 Dec 2018 14:17:09 +0100 Subject: [PATCH] Refactor, fix rubocop lint errors --- .rubocop.yml | 4 + unipoly-mlmmj-ldap-sync | 229 ++++++++++++++++++++++++---------------- 2 files changed, 143 insertions(+), 90 deletions(-) create mode 100644 .rubocop.yml diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..b5767a2 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,4 @@ +Metrics/MethodLength: + Max: 25 +Metrics/AbcSize: + Enabled: false diff --git a/unipoly-mlmmj-ldap-sync b/unipoly-mlmmj-ldap-sync index c9ed30d..a3b14a7 100755 --- a/unipoly-mlmmj-ldap-sync +++ b/unipoly-mlmmj-ldap-sync @@ -3,120 +3,169 @@ require 'toml' require 'net/ldap' -@configuration_file = "/etc/unipoly-mlmmj-ldap-sync.toml" +@configuration_file = '/etc/unipoly-mlmmj-ldap-sync.toml' +@configuration = nil def read_configuration(path) - TOML.load_file(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 => conf["ldap"]["auth"]["username"], - :password => conf["ldap"]["auth"]["password"] - }) + 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"]}" + #{conf['ldap']['host']}:#{conf['ldap']['port']}" exit(1) end - rescue - puts "Failed to contact LDAP server: \ - #{conf["ldap"]["host"]}:#{conf["ldap"]["port"]}" - exit(1) + 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 - conf = read_configuration(@configuration_file) + # Parse configuration, bind to LDAP server + @configuration = read_configuration(@configuration_file) conn = connect_ldap(conf) - domain = conf["domain"] - basetree = conf["ldap"]["lists"]["basetree"] - lists = (conf["lists_add"] + conf["lists_remove"]).uniq - lists.each do |cn| - puts ">> Processing list #{cn}@#{domain}" + domain = @configuration['domain'] + basetree = @configuration['ldap']['lists']['basetree'] + lists = (@configuration['lists_add'] + @configuration['lists_remove']).uniq - filter = Net::LDAP::Filter.eq("cn", cn) + # Sync Mlmmj lists with LDAP groups + lists.each do |list_name| + puts ">> Processing list #{list_name}@#{domain}" + print 'Searching LDAP... ' - print "Searching LDAP... " - match = conn.search(:base => basetree, :filter => filter) - if (match.size > 0) - entry = match.first - members = entry.uniquemember.map do |dn| - /mail=([^,]+),/.match(dn).values_at(1).first.downcase - end - - puts "OK" - puts "Found #{entry.dn} with #{members.size} members" - - print "Searchin mlmmj... " - mlmmj_list_binary = conf["mlmmj"]["list_binary"] - mlmmj_basepath = conf["mlmmj"]["basepath"] - list = "#{mlmmj_basepath}/#{cn}@#{domain}" - if (File.executable?(mlmmj_list_binary)) - raw = %x(#{mlmmj_list_binary} -L #{list} -s) - - if ($?.exitstatus == 0) - puts "OK" - subscribers = raw.split("\n") - puts "Found #{cn}@#{domain} with #{subscribers.size} subscribers" - print "\n" - - # Add to the subscribers any missing entry - # Remove successful matches - members.each do |m| - if (subscribers.include?(m)) - subscribers.delete(m) - else - print "Adding #{m}... " - if (conf["lists_add"].include?(cn)) - mlmmj_sub_binary = conf["mlmmj"]["sub_binary"] - %x(#{mlmmj_sub_binary} -L #{list} -a #{m} -q -f -s) - if ($?.exitstatus == 0) - puts "OK" - else - puts "FAILED" - end - else - puts "IGNORED" - end - end - end - - # Remove remaining "subcribers" (= they did not match with members) - subscribers.each do |s| - print "Removing #{s}... " - if (conf["lists_remove"].include?(cn)) - mlmmj_unsub_binary = conf["mlmmj"]["unsub_binary"] - %x(#{mlmmj_unsub_binary} -L #{list} -a #{s} -q -s) - if ($?.exitstatus == 0) - puts "OK" - else - puts "FAILED" - end - else - puts "IGNORED" - end - end - print "\n" - else - puts "FAIL" - end - else - puts "could not execute #{mlmmj_list_binary}" - end + 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 "FAIL" + puts 'OK' + ldap_group_entry = matched_ldap_groups.first + sync_list_with(list_name, ldap_group_entry) end end end -main() +### +# Execute script: +main