2 ############################################################################
6 # Module to set up an LDAP server for testing pg_hba.conf ldap authentication
8 # Copyright (c) 2023-2024, PostgreSQL Global Development Group
10 ############################################################################
16 LdapServer - class for an LDAP server for testing pg_hba.conf authentication
22 # have we found openldap binaries suitable for setting up a server?
23 my $ldap_binaries_found = $LdapServer::setup;
25 # create a server with the given root password and auth type
26 # (users or anonymous)
27 my $server = LdapServer->new($root_password, $auth_type);
29 # Add the contents of an LDIF file to the server
30 $server->ldapadd_file ($path_to_ldif_data);
32 # set the Ldap password for a user
33 $server->ldapsetpw($user, $password);
35 # get details of some settings for the server
36 my @properties = $server->prop($propname1, $propname2, ...);
40 LdapServer tests in its INIT phase for the presence of suitable openldap
41 binaries. Its constructor method sets up and runs an LDAP server, and any
42 servers that are set up are terminated during its END phase.
49 use warnings FATAL
=> 'all';
51 use PostgreSQL
::Test
::Utils
;
58 my ($slapd, $ldap_schema_dir, @servers);
61 our ($setup, $setup_error);
65 # Find the OpenLDAP server binary and directory containing schema
66 # definition files. On success, $setup is set to 1. On failure,
67 # it's set to 0, and an error message is set in $setup_error.
71 if (-d
'/opt/homebrew/opt/openldap')
73 # typical paths for Homebrew on ARM
74 $slapd = '/opt/homebrew/opt/openldap/libexec/slapd';
75 $ldap_schema_dir = '/opt/homebrew/etc/openldap/schema';
77 elsif (-d
'/usr/local/opt/openldap')
79 # typical paths for Homebrew on Intel
80 $slapd = '/usr/local/opt/openldap/libexec/slapd';
81 $ldap_schema_dir = '/usr/local/etc/openldap/schema';
83 elsif (-d
'/opt/local/etc/openldap')
85 # typical paths for MacPorts
86 $slapd = '/opt/local/libexec/slapd';
87 $ldap_schema_dir = '/opt/local/etc/openldap/schema';
91 $setup_error = "OpenLDAP server installation not found";
95 elsif ($^O
eq 'linux')
97 if (-d
'/etc/ldap/schema')
99 $slapd = '/usr/sbin/slapd';
100 $ldap_schema_dir = '/etc/ldap/schema';
102 elsif (-d
'/etc/openldap/schema')
104 $slapd = '/usr/sbin/slapd';
105 $ldap_schema_dir = '/etc/openldap/schema';
109 $setup_error = "OpenLDAP server installation not found";
113 elsif ($^O
eq 'freebsd')
115 if (-d
'/usr/local/etc/openldap/schema')
117 $slapd = '/usr/local/libexec/slapd';
118 $ldap_schema_dir = '/usr/local/etc/openldap/schema';
122 $setup_error = "OpenLDAP server installation not found";
126 elsif ($^O
eq 'openbsd')
128 if (-d
'/usr/local/share/examples/openldap/schema')
130 $slapd = '/usr/local/libexec/slapd';
131 $ldap_schema_dir = '/usr/local/share/examples/openldap/schema';
135 $setup_error = "OpenLDAP server installation not found";
141 $setup_error = "ldap tests not supported on $^O";
148 foreach my $server (@servers)
150 next unless -f
$server->{pidfile
};
151 my $pid = slurp_file
($server->{pidfile
});
163 =item LdapServer->new($rootpw, $auth_type)
165 Create a new LDAP server.
167 The rootpw can be used when authenticating with the ldapbindpasswd option.
169 The auth_type is either 'users' or 'anonymous'.
177 die "no suitable binaries found" unless $setup;
181 my $authtype = shift; # 'users' or 'anonymous'
182 my $testname = basename
((caller)[1], '.pl');
185 my $test_temp = PostgreSQL
::Test
::Utils
::tempdir
("ldap-$testname");
187 my $ldap_datadir = "$test_temp/openldap-data";
188 my $slapd_certs = "$test_temp/slapd-certs";
189 my $slapd_pidfile = "$test_temp/slapd.pid";
190 my $slapd_conf = "$test_temp/slapd.conf";
192 "${PostgreSQL::Test::Utils::log_path}/slapd-$testname.log";
193 my $ldap_server = 'localhost';
194 my $ldap_port = PostgreSQL
::Test
::Cluster
::get_free_port
();
195 my $ldaps_port = PostgreSQL
::Test
::Cluster
::get_free_port
();
196 my $ldap_url = "ldap://$ldap_server:$ldap_port";
197 my $ldaps_url = "ldaps://$ldap_server:$ldaps_port";
198 my $ldap_basedn = 'dc=example,dc=net';
199 my $ldap_rootdn = 'cn=Manager,dc=example,dc=net';
200 my $ldap_rootpw = $rootpw;
201 my $ldap_pwfile = "$test_temp/ldappassword";
203 (my $conf = <<"EOC") =~ s/^\t\t//gm;
204 include $ldap_schema_dir/core.schema
205 include $ldap_schema_dir/cosine.schema
206 include $ldap_schema_dir/nis.schema
207 include $ldap_schema_dir/inetorgperson.schema
209 pidfile $slapd_pidfile
210 logfile $slapd_logfile
217 directory $ldap_datadir
219 TLSCACertificateFile $slapd_certs/ca.crt
220 TLSCertificateFile $slapd_certs/server.crt
221 TLSCertificateKeyFile $slapd_certs/server.key
223 suffix "dc=example,dc=net"
224 rootdn "$ldap_rootdn"
225 rootpw "$ldap_rootpw"
227 append_to_file
($slapd_conf, $conf);
229 mkdir $ldap_datadir or die "making $ldap_datadir: $!";
230 mkdir $slapd_certs or die "making $slapd_certs: $!";
232 my $certdir = dirname
(__FILE__
) . "/../ssl/ssl";
234 copy
"$certdir/server_ca.crt", "$slapd_certs/ca.crt"
235 || die "copying ca.crt: $!";
236 # check we actually have the file, as copy() sometimes gives a false success
237 -f
"$slapd_certs/ca.crt" || die "copying ca.crt (error unknown)";
238 copy
"$certdir/server-cn-only.crt", "$slapd_certs/server.crt"
239 || die "copying server.crt: $!";
240 copy
"$certdir/server-cn-only.key", "$slapd_certs/server.key"
241 || die "copying server.key: $!";
243 append_to_file
($ldap_pwfile, $ldap_rootpw);
244 chmod 0600, $ldap_pwfile or die "chmod on $ldap_pwfile";
246 # -s0 prevents log messages ending up in syslog
247 system_or_bail
$slapd, '-f', $slapd_conf, '-s0', '-h',
248 "$ldap_url $ldaps_url";
250 # wait until slapd accepts requests
257 "ldapsearch", "-sbase",
262 "-n", "'objectclass=*'") == 0);
263 die "cannot connect to slapd" if ++$retries >= 300;
264 note
"waiting for slapd to accept requests...";
265 Time
::HiRes
::usleep
(1000000);
268 $self->{pidfile
} = $slapd_pidfile;
269 $self->{pwfile
} = $ldap_pwfile;
270 $self->{url
} = $ldap_url;
271 $self->{s_url
} = $ldaps_url;
272 $self->{server
} = $ldap_server;
273 $self->{port
} = $ldap_port;
274 $self->{s_port
} = $ldaps_port;
275 $self->{basedn
} = $ldap_basedn;
276 $self->{rootdn
} = $ldap_rootdn;
279 push @servers, $self;
283 # private routine to set up the environment for methods below
288 $env{'LDAPURI'} = $self->{url
};
289 $env{'LDAPBINDDN'} = $self->{rootdn
};
297 =item ldapadd_file(filename)
299 filename is the path to a file containing LDIF data which is added to the LDAP
311 local %ENV = $self->_ldapenv;
313 system_or_bail
'ldapadd', '-x', '-y', $self->{pwfile
}, '-f', $file;
320 =item ldapsetpw(user, password)
322 Set the user's password in the LDAP server
332 my $password = shift;
334 local %ENV = $self->_ldapenv;
336 system_or_bail
'ldappasswd', '-x', '-y', $self->{pwfile
}, '-s', $password,
344 =item prop(name1, ...)
346 Returns the list of values for the specified properties of the instance, such
347 as 'url', 'port', 'basedn'.
357 push @settings, $self->{$_} foreach (@_);