#!/usr/bin/perl -w # # con2mmr.pl - configure supplier->consumer replication between two fedora-ds # servers; it is assumed the supplier will be multi-master # # Mike Jackson 19.11.2005 # # Professional LDAP consulting for large and small projects # # http://www.netauth.com # # GPLv2 License # # Modified from mmr.pl by Martin Dellwo 29.11.2006 # use strict; use Getopt::Long; use Net::LDAP qw(LDAP_ALREADY_EXISTS LDAP_TYPE_OR_VALUE_EXISTS); use Pod::Usage; my %o; GetOptions( \%o, 'base=s', # optional, default to get_base() 'binddn=s', # optional, default to "cn=directory manager" 'bindpw=s', 'repmanpw=s', 'supplier=s', 'consumer=s', 'supplier_id=i', 'create', 'display', 'remove', 'with-ssl', 'help', 'man', ); pod2usage(-verbose => 1) if ($o{help}); pod2usage(-verbose => 2) if ($o{man} ); pod2usage(-verbose => 1) if (! ($o{create} || $o{display} || $o{remove}) ); # mandatory in all cases my $supplier = $o{supplier}; my $consumer = $o{consumer}; my $bindpw = $o{bindpw}; # mandatory in create case my $create = $o{create}; my $supplier_id = $o{supplier_id}; my $repmanpw = $o{repmanpw}; # mandatory in display case my $display = $o{display}; # mandatory in remove case my $remove = $o{remove}; # optional in all cases my $base = $o{base} || get_base($supplier); my $binddn = $o{binddn} || "cn=directory manager"; # optional in create case my $with_ssl = $o{'with-ssl'}; # all cases check if (!($supplier && $consumer && $bindpw)) { pod2usage(-verbose => 1); exit(1); } ############################################### # create ############################################### if ($create) { if (!($supplier_id && $repmanpw)) { pod2usage(-verbose => 1); exit(1); } else { # configure supplier, consumer config_supplier($supplier, $supplier_id, $repmanpw); config_consumer($consumer, $repmanpw); # add replication agreements add_rep_agreement($supplier, $consumer, $repmanpw); # initialize consumer from supplier initialize($supplier, $consumer); } } ############################################### # remove ############################################### if ($remove) { if (!($supplier && $consumer)) { pod2usage(-verbose => 1); exit(1); } else { # remove agreement remove_agreement($supplier, $consumer); } } ############################################### # display ############################################### if ($display) { if (!($supplier && $consumer)) { pod2usage(-verbose => 1); exit(1); } else { # display agreements print "\n"; display_agreement($supplier); print "\n"; display_agreement($consumer); print "\n"; } } ############################################### # subs ############################################### sub display_agreement { my $server = shift; my $msg; my $res; my $ldap = Net::LDAP->new($server) || die "$@"; $msg = $ldap->bind($binddn, password => $bindpw, version => 3); $msg->code && die $msg->error; print "replication agreements from $server\n"; $res = $ldap->search( base => "cn=config", filter => "(objectClass=nsDS5ReplicationAgreement)", ); $res->code && die $res->error; if ($res->count) { for ($res->entries) { print "\t ->" . $_->get_value("nsDS5ReplicaHost") . "\n"; } } else { print "\t -> none found\n"; } $ldap->unbind; } sub remove_agreement { my ($from, $to) = @_; my $msg; my $res; my $ldap = Net::LDAP->new($from) || die "$@"; $msg = $ldap->bind($binddn, password => $bindpw, version => 3); $msg->code && die $msg->error; print "removing replication agreement from $from -> $to\n"; $res = $ldap->search( base => "cn=config", filter => "(&(objectClass=nsDS5ReplicationAgreement)(nsDS5ReplicaHost=$to))", ); $res->code && die $res->error; my $dn = $res->entry(0)->dn; $res = $ldap->delete($dn); $res->code && warn "failed to remove replication agreement: " . $res->error; $ldap->unbind; } sub config_supplier { my ($server, $replicaid, $repmanpw) = @_; my $msg; my $res; my $ldap = Net::LDAP->new($server) || die "$@"; $msg = $ldap->bind($binddn, password => $bindpw, version => 3); $msg->code && die $msg->error; ############################## # find the instance-dir ############################## $res = $ldap->search ( base => "cn=config", scope => "base", filter => "(objectClass=*)", ); my $instance_dir = $res->entry(0)->get_value("nsslapd-instancedir"); ############################## ############################## # add changelog ############################## print "adding to $server -> cn=changelog5,cn=config\n"; $res = $ldap->add( "cn=changelog5,cn=config", attr => [ objectclass => [qw (top extensibleObject)], cn => "changelog5", "nsslapd-changelogdir" => "$instance_dir/changelogdb", ] ); if ($res->code == LDAP_ALREADY_EXISTS) { print "\t -> already exists\n\n"; } else { $res->code && die "failed to add changelog entry: " . $res->error; } ############################## ############################## # add replication user ############################## print "adding to $server -> cn=repman,cn=config\n"; $res = $ldap->add( "cn=repman,cn=config", attr => [ objectclass => [qw (top person)], cn => "repman", sn => "repman", userPassword => $repmanpw, ] ); if ($res->code == LDAP_ALREADY_EXISTS) { print "\t -> already exists\n\n"; } else { $res->code && die "failed to add repman entry: " . $res->error; } ############################## ############################## # add multi-master enabled replica object ############################## print "adding to $server -> cn=replica,cn=\"$base\",cn=mapping tree,cn=config\n"; $res = $ldap->add( "cn=replica,cn=\"$base\",cn=mapping tree,cn=config", attr => [ objectclass => [qw (top nsDS5Replica)], cn => "replica", nsDS5ReplicaId => $replicaid, nsDS5ReplicaRoot => $base, nsDS5Flags => 1, nsDS5ReplicaBindDN => "cn=repman,cn=config", nsds5ReplicaPurgeDelay => 604800, nsds5ReplicaLegacyConsumer => "off", nsDS5ReplicaType => 3, ] ); if ($res->code == LDAP_ALREADY_EXISTS) { print "\t -> already exists\n\n"; } else { $res->code && die "failed to add replica entry: " . $res->error; } ############################## $ldap->unbind; } sub config_consumer { my ($server, $repmanpw) = @_; my $msg; my $res; my $ldap = Net::LDAP->new($server) || die "$@"; $msg = $ldap->bind($binddn, password => $bindpw, version => 3); $msg->code && die $msg->error; ############################## # find the instance-dir ############################## $res = $ldap->search ( base => "cn=config", scope => "base", filter => "(objectClass=*)", ); my $instance_dir = $res->entry(0)->get_value("nsslapd-instancedir"); ############################## ############################## # consumer does not get changelog ############################## ############################## # add replication user ############################## print "adding to $server -> cn=repman,cn=config\n"; $res = $ldap->add( "cn=repman,cn=config", attr => [ objectclass => [qw (top person)], cn => "repman", sn => "Replication Manager", userPassword => $repmanpw, ] ); if ($res->code == LDAP_ALREADY_EXISTS) { print "\t -> already exists\n\n"; } else { $res->code && die "failed to add repman entry: " . $res->error; } ############################## ############################## # add consumer read-only replica object ############################## print "adding to $server -> cn=replica,cn=\"$base\",cn=mapping tree,cn=config\n"; $res = $ldap->add( "cn=replica,cn=\"$base\",cn=mapping tree,cn=config", attr => [ objectclass => [qw (top nsDS5Replica)], cn => "replica", nsDS5ReplicaId => 65535, nsDS5ReplicaRoot => $base, nsDS5Flags => 0, nsDS5ReplicaBindDN => "cn=repman,cn=config", nsds5ReplicaPurgeDelay => 604800, nsDS5ReplicaType => 2, ] ); if ($res->code == LDAP_ALREADY_EXISTS) { print "\t -> already exists\n\n"; } else { $res->code && die "failed to add replica entry: " . $res->error; } ############################## $ldap->unbind; } sub add_rep_agreement { my ($from, $to, $repmanpw) = @_; my $msg; my $res; my $ldap = Net::LDAP->new($from) || die "$@"; $msg = $ldap->bind($binddn, password => $bindpw, version => 3); $msg->code && die $msg->error; if ($with_ssl) { print "adding to $from -> SSL replication $from -> $to\n"; $res = $ldap->add( "cn=\"Replication to $to\",cn=replica,cn=\"$base\",cn=mapping tree,cn=config", attr => [ objectclass => [qw (top nsDS5ReplicationAgreement)], cn => "\"Replication to $to\"", nsDS5ReplicaHost => $to, nsDS5ReplicaRoot => "$base", nsDS5ReplicaPort => 636, nsDS5ReplicaTransportInfo => "SSL", nsDS5ReplicaBindDN => "cn=repman,cn=config", nsDS5ReplicaBindMethod => "simple", nsDS5ReplicaCredentials => $repmanpw, nsDS5ReplicaUpdateSchedule => "0000-2359 0123456", nsDS5ReplicaTimeOut => 120, ] ); if ($res->code == LDAP_ALREADY_EXISTS) { print "\t -> already exists\n\n"; } else { $res->code && die "failed to add replication agreement entry: " . $res->error; } } else { print "adding to $from -> plaintext replication $from -> $to\n"; $res = $ldap->add( "cn=\"Replication to $to\",cn=replica,cn=\"$base\",cn=mapping tree,cn=config", attr => [ objectclass => [qw (top nsDS5ReplicationAgreement)], cn => "\"Replication to $to\"", nsDS5ReplicaHost => $to, nsDS5ReplicaRoot => "$base", nsDS5ReplicaPort => 389, nsDS5ReplicaBindDN => "cn=repman,cn=config", nsDS5ReplicaBindMethod => "simple", nsDS5ReplicaCredentials => $repmanpw, nsDS5ReplicaUpdateSchedule => "0000-2359 0123456", nsDS5ReplicaTimeOut => 120, ] ); if ($res->code == LDAP_ALREADY_EXISTS) { print "\t -> already exists\n\n"; } else { $res->code && die "failed to add replication agreement entry: " . $res->error; } } $ldap->unbind; } sub initialize { my ($from, $to) = @_; my $msg; my $res; my $ldap = Net::LDAP->new($from) || die "$@"; $msg = $ldap->bind($binddn, password => $bindpw, version => 3); $msg->code && die $msg->error; print "initializing replication $from -> $to\n"; my $dn = "cn=\"Replication to $to\",cn=replica,cn=\"$base\",cn=mapping tree,cn=config"; $res = $ldap->modify($dn, add => { nsDS5BeginReplicaRefresh => 'start' }); $res->code && die "failed to add initialization attribute: " . $res->error; $ldap->unbind; } sub get_base { my $server = shift; my $base; my $res; my $ldap = Net::LDAP->new($server) || die "$@"; $ldap->bind; $res = $ldap->search( base => "", scope => "base", filter => "(objectClass=*)", ); $res->code && die $res->error; my @contexts = $res->entry(0)->get_value("namingContexts"); for (@contexts) { if (! /o=NetscapeRoot/) { $base = $_; } } $ldap->unbind; return $base; } __END__ =head1 NAME con2mmr.pl - Configure supplier->consumer replication between two Fedora Directory Servers, with Multi-Master supplier =head1 SYNOPSIS Usage: con2mmr.pl [options] Mandatory in all cases: --supplier FQDN of supplier --consumer FQDN of consumer --bindpw Password for Directory Manager Optional in all cases: --base LDAP naming context --binddn Alternative distinguished name for Directory Manager Mandatory for creating a 1-way agreement on the supplier: --supplier_id Replication ID number of supplier --repmanpw Password for Replication Manager --create Optional when creating a 1-way agreement on the supplier: --with-ssl Use SSL for Replication (requires CA and server certs on both machines) Mandatory for removing a 1-way agreement from the supplier: --remove Mandatory for displaying agreements --display Examples: # create a 1-way replication agreement % con2mmr.pl \ --supplier b.bigcorp.com \ --consumer a.bigcorp.com \ --supplier_id 1 \ --bindpw secret \ --repmanpw repsecret \ --create # create a 1-way replication agreement with SSL % con2mmr.pl \ --supplier b.bigcorp.com \ --consumer a.bigcorp.com \ --supplier_id 1 \ --bindpw secret \ --repmanpw repsecret \ --create \ --with-ssl # remove a 1-way replication agreement % con2mmr.pl \ --supplier b.bigcorp.com \ --consumer a.bigcorp.com \ --bindpw secret \ --remove # display replication agreements % con2mmr.pl \ --supplier b.bigcorp.com \ --consumer a.bigcorp.com \ --bindpw secret \ --display =head1 DESCRIPTION B will configure supplier-consumer replication between two Fedora or Redhat Directory Servers. The supplier will be set up in Multi-Master mode. The "cn=Directory Manager" user's password must be the same on both machines. As well, the "cn=Directory Manager" user which was created during installation must exist on both machines, or if this username was changed to something else then it can be provided with the --binddn option. The LDAP naming context must be the same on both machines. If there is only one naming context configured on the machines, then it is not necessary to provide the --base option as the naming context will be discovered automatically. =cut