Port from mlmmj to mailman3

This commit is contained in:
Timothée Floure 2020-11-24 09:19:25 +01:00
parent 00e18d3d43
commit efcbced392
Signed by: tfloure
GPG key ID: 4502C902C00A1E12
13 changed files with 176 additions and 269 deletions

View file

@ -4,7 +4,6 @@ build:
install: install:
mkdir -p $(DESTDIR)$(PREFIX)/bin $(DESTDIR)/etc $(DESTDIR)/lib/systemd/system mkdir -p $(DESTDIR)$(PREFIX)/bin $(DESTDIR)/etc $(DESTDIR)/lib/systemd/system
install -m755 mlmmj-ldap-sync $(DESTDIR)$(PREFIX)/bin/mlmmj-ldap-sync install -m755 mailman3-ldap-sync $(DESTDIR)$(PREFIX)/bin/mailman3-ldap-sync
install -m644 conf.example.toml $(DESTDIR)/etc/mlmmj-ldap-sync.toml install -m644 conf.example.toml $(DESTDIR)/etc/mailman3-ldap-sync.toml
install -m644 mlmmj-ldap-sync.service mlmmj-ldap-sync.timer $(DESTDIR)/lib/systemd/system install -m644 mailman3-ldap-sync.service mailman3-ldap-sync.timer $(DESTDIR)/lib/systemd/system
install -m644 mlmmj-ldap-sync@.service mlmmj-ldap-sync@.timer $(DESTDIR)/lib/systemd/system

View file

@ -1,3 +1,4 @@
# mlmmj-ldap-sync # mailman3-ldap-sync
Allows to sync LDAP groups with mlmmj lists. Simple script to sync mailman3 lists with LDAP groups, forked from [GNU Generation's
mlmmj-ldap-sync](https://gitlab.gnugen.ch/gnugen/mlmmj-ldap-sync).

View file

@ -1,24 +1,16 @@
domain = "unipoly.ch" domain = "lists.recycled.cloud"
lists_add = ["membres"] lists = [ 'demo' ]
lists_remove = ["membres"]
[ldap] [ldap]
host = "ldap.gnugen.ch" host = "ldap1.recycled.cloud"
port = 389 port = 636
[ldap.auth] [ldap.auth]
username = "cn=unipoly-mlmmj,ou=Services,dc=unipoly,dc=epfl,dc=ch" username = "uid=mailman3-ldap-sync,ou=services,dc=recycled,dc=cloud"
password = "secret" password = "secret"
[ldap.lists] [ldap.lists]
basetree = "ou=Lists,dc=unipoly,dc=epfl,dc=ch" basetree = "ou=groups,dc=recycled,dc=cloud"
[ldap.users] [ldap.users]
basetree = "ou=Users,dc=unipoly,dc=epfl,dc=ch" basetree = "ou=users,dc=recycled,dc=cloud"
[mlmmj]
basepath = "/var/spool/mlmmj"
list_binary = "/usr/bin/mlmmj-list"
sub_binary = "/usr/bin/mlmmj-sub"
unsub_binary = "/usr/bin/mlmmj-unsub"

37
debian/changelog vendored
View file

@ -1,38 +1,5 @@
mlmmj-ldap-sync (0.6) unstable; urgency=medium mailman3-ldap-sync (0.1) unstable; urgency=medium
* Adapt code for GNU Generation LDAP structure.
* Fix systemd unit files installation.
-- Timothée Floure <timothee.floure@epfl.ch> Thu, 31 Oct 2019 17:49:44 +0100
mlmmj-ldap-sync (0.5) unstable; urgency=medium
* Rename package from unipoly-mlmmj-ldap-sync to mlmmj-ldap-sync.
* Support custom path for the configuration file.
-- Timothée Floure <timothee.floure@epfl.ch> Wed, 30 Oct 2019 12:20:01 +0100
unipoly-mlmmj-ldap-sync (0.4) unstable; urgency=medium
* Fix typo in remove_subscriber_from
-- Timothée Floure <timothee.floure@epfl.ch> Mon, 25 Feb 2019 17:47:40 +0100
unipoly-mlmmj-ldap-sync (0.3) unstable; urgency=medium
* Dissociate addition and removal of users.
-- Timothée Floure <timothee.floure@epfl.ch> Sun, 23 Dec 2018 12:45:58 +0100
unipoly-mlmmj-ldap-sync (0.2) unstable; urgency=low
* Add systemd units.
* Add default configuration file.
-- Timothée Floure <timothee.floure@epfl.ch> Wed, 22 Aug 2018 16:25:00 +0200
unipoly-mlmmj-ldap-sync (0.1) unstable; urgency=low
* Let there be package. * Let there be package.
-- Timothée Floure <timothee.floure@epfl.ch> Fri, 29 Jun 2018 12:02:05 +0200 -- Timothée Floure <t.floure@e-durable.ch> Thu, 24 Nov 2020 09:17:00 +0100

10
debian/control vendored
View file

@ -1,15 +1,15 @@
Source: mlmmj-ldap-sync Source: mailman3-ldap-sync
Section: admin Section: admin
Priority: optional Priority: optional
Maintainer: Timothée Floure <timothee.floure@epfl.ch> Maintainer: Timothée Floure <timothee.floure@epfl.ch>
Build-Depends: debhelper (>= 9) Build-Depends: debhelper (>= 9)
Standards-Version: 4.1.1 Standards-Version: 4.1.1
Homepage: https://gitlab.gnugen.ch/gnugen/mlmmj-ldap-sync Homepage: https://code.recycled.cloud/e-Durable/mailman3-ldap-sync
Vcs-Git: git://gitlab.gnugen.ch/gnugen/mlmmj-ldap-sync.git Vcs-Git: https://code.recycled.cloud/e-Durable/mailman3-ldap-sync
Package: mlmmj-ldap-sync Package: mailman3-ldap-sync
Architecture: all Architecture: all
Depends: ruby, ruby-net-ldap, ruby-toml, ${misc:Depends} Depends: ruby, ruby-net-ldap, ruby-toml, ${misc:Depends}
Description: Cronjobs to maintain the synchronization of mlmmj mailing lists. Description: Cronjobs to maintain the synchronization of mailman3 mailing lists.
This package installs cronjobs to maintain the synchronization between LDAP This package installs cronjobs to maintain the synchronization between LDAP
groups and mailing lists. groups and mailing lists.

4
debian/copyright vendored
View file

@ -1,11 +1,11 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Files: * Files: *
Copyright: 2018 Timothée Floure <timothee.floure@fnux.ch> Copyright: 2018 Timothée Floure <t.floure@e-durable.ch>
License: Apache-2.0 License: Apache-2.0
Files: debian/* Files: debian/*
Copyright: 2018 Timothée Floure <timothee.floure@epfl.ch> Copyright: 2018 Timothée Floure <t.floure@e-durable.ch>
License: Apache-2.0 License: Apache-2.0
License: Apache-2.0 License: Apache-2.0

146
mailman3-ldap-sync Executable file
View file

@ -0,0 +1,146 @@
#!/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

View file

@ -0,0 +1,7 @@
[Unit]
Description=Synchronize mailman3 mailing lists with LDAP groups
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/mailman3-ldap-sync

View file

@ -1,5 +1,5 @@
[Unit] [Unit]
Description=Synchronize mlmmj mailing lists with LDAP groups once a day Description=Synchronize mailman3 mailing lists with LDAP groups once a day
[Timer] [Timer]
OnCalendar=*-*-* 3:00:00 OnCalendar=*-*-* 3:00:00

View file

@ -1,182 +0,0 @@
#!/usr/bin/env ruby
require 'toml'
require 'net/ldap'
require 'English'
@configuration_file = '/etc/mlmmj-ldap-sync.toml'
@configuration = nil
def read_configuration(path)
begin
TOML.load_file(path)
rescue Errno::ENOENT => raised_error
puts "Could not open configuration file: #{raised_error}"
exit(1)
end
end
def connect_ldap
conn = Net::LDAP.new(
host: @configuration['ldap']['host'],
port: @configuration['ldap']['port'],
auth: {
method: :simple,
username: @configuration['ldap']['auth']['username'],
password: @configuration['ldap']['auth']['password']
}
)
ldap_addr = @configuration['ldap']['host'] + ':' +
@configuration['ldap']['port'].to_s
begin
if conn.bind
conn
else
puts "Failed to authenticate against LDAP server: #{ldap_addr}"
exit(1)
end
rescue LdapError => raised_error
puts "Failed to contact LDAP server: #{ldap_addr}"
puts raised_error
exit(1)
end
end
def get_mlmmj_subscribers(list)
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
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)
puts 'IGNORED'
return
end
# Unsubscribe the address from the list.
mlmmj_unsub_binary = @configuration['mlmmj']['unsub_binary']
`#{mlmmj_unsub_binary} -L #{list} -a #{addr} -q -s`
if $CHILD_STATUS.exitstatus.zero?
puts 'OK'
else
puts 'FAILED'
end
end
def sync_list(list_name, ldap_group_entry, ldap_conn)
mlmmj_basepath = @configuration['mlmmj']['basepath']
user_basetree = @configuration['ldap']['users']['basetree']
list = "#{mlmmj_basepath}/#{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 = 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 Mlmmj
print 'Searchin mlmmj... '
mlmmj_subscribers = get_mlmmj_subscribers(list)
puts 'OK'
puts "Found #{list_name}@#{@configuration['domain']} with\
#{mlmmj_subscribers.size} subscribers"
# Add to the subscribers any missing entry
# Remove successful matches
ldap_members.each do |addr|
add_subscriber_to(list_name, mlmmj_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_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_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 = 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

View file

@ -1,7 +0,0 @@
[Unit]
Description=Synchronize mlmmj mailing lists with LDAP groups
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/mlmmj-ldap-sync

View file

@ -1,9 +0,0 @@
[Unit]
Description=Synchronize mlmmj mailing lists with LDAP groups once a day
[Timer]
OnCalendar=*-*-* 3:00:00
Persistent=true
[Install]
WantedBy=timers.target

View file

@ -1,7 +0,0 @@
[Unit]
Description=Synchronize mlmmj mailing lists with LDAP groups
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/mlmmj-ldap-sync /etc/mlmmj-ldap-sync-%i.toml