1# Functions for parsing and updating the LDAP config file
2
3BEGIN { push(@INC, ".."); };
4use WebminCore;
5&init_config();
6
7@base_types = ("passwd", "shadow", "group", "hosts", "networks", "netmasks",
8	       "services", "protocols", "aliases", "netgroup");
9
10# get_ldap_config_file()
11# Returns the first config file that exists
12sub get_ldap_config_file
13{
14my @confs = split(/\s+/, $config{'auth_ldap'});
15foreach my $c (@$confs) {
16	return $c if (-e $c);
17	}
18return $confs[0];
19}
20
21# get_config()
22# Parses the NSS LDAP config file into a list of names and values
23sub get_config
24{
25local $file = $_[0] || &get_ldap_config_file();
26if (!scalar(@get_config_cache)) {
27	local $lnum = 0;
28	@get_config_cache = ( );
29	&open_readfile(CONF, $file);
30	while(<CONF>) {
31		s/\r|\n//g;
32		if (/^(#?)(\S+)\s*(.*)/) {
33			my $dir = { 'name' => lc($2),
34				    'value' => $3,
35				    'enabled' => !$1,
36				    'line' => $lnum,
37				    'file' => $file };
38			$dir->{'value'} =~ s/\s+#.*$//;   # Trailing comments
39			push(@get_config_cache, $dir);
40			}
41		$lnum++;
42		}
43	close(CONF);
44	}
45return \@get_config_cache;
46}
47
48# find(name, &conf, disabled-mode(0=enabled, 1=disabled, 2=both))
49# Returns the directive objects with some name
50sub find
51{
52local ($name, $conf, $dis) = @_;
53local @rv = grep { $_->{'name'} eq $name } @$conf;
54if ($dis == 0) {
55	# Enabled only
56	@rv = grep { $_->{'enabled'} } @rv;
57	}
58elsif ($dis == 1) {
59	# Disabled only
60	@rv = grep { !$_->{'enabled'} } @rv;
61	}
62return wantarray ? @rv : $rv[0];
63}
64
65# find_value(name, &conf, [disabled])
66# Finds the value or values of a directive
67sub find_value
68{
69local ($name, $conf, $dis) = @_;
70local @rv = map { $_->{'value'} } &find($name, $conf, $dis);
71return wantarray ? @rv : $rv[0];
72}
73
74# find_svalue(name, &conf, [disabled])
75# Like find_value, but only returns a single value
76sub find_svalue
77{
78local $rv = &find_value(@_);
79return $rv;
80}
81
82# save_directive(&conf, name, [value|&values])
83# Update one or more directives with some name
84sub save_directive
85{
86local ($conf, $name, $valuez) = @_;
87local @values = ref($valuez) ? @$valuez : ( $valuez );
88local @old = &find($name, $conf);
89local @oldcmt = &find($name, $conf, 1);
90local $deffile = &get_ldap_config_file();
91
92for(my $i=0; $i<@old || $i<@values; $i++) {
93	local $old = $old[$i];
94	local $oldcmt = $oldcmt[$i];
95	local $value = $values[$i];
96	local $lref = &read_file_lines($old ? $old->{'file'} :
97				       $oldcmt ? $oldcmt->{'file'} :
98						 $deffile);
99	if (defined($value) && $old) {
100		# Just update value
101		$old->{'value'} = $value;
102		$lref->[$old->{'line'}] = "$name $value";
103		}
104	elsif (defined($value) && $oldcmt) {
105		# Add value after commented version
106		splice(@$lref, $oldcmt->{'line'}+1, 0, "$name $value");
107		&renumber($conf, $oldcmt->{'line'}+1, $oldcmt->{'file'}, 1);
108		push(@$conf, { 'name' => $name,
109			       'value' => $value,
110			       'enabled' => 1,
111			       'line' => $oldcmt->{'line'}+1,
112			       'file' => $oldcmt->{'file'} });
113		}
114	elsif (!defined($value) && $old) {
115		# Delete current value
116		splice(@$lref, $old->{'line'}, 1);
117		&renumber($conf, $old->{'line'}, $old->{'file'}, -1);
118		@$conf = grep { $_ ne $old } @$conf;
119		}
120	elsif ($value) {
121		# Add value at end of file
122		push(@$conf, { 'name' => $name,
123			       'value' => $value,
124			       'enabled' => 1,
125			       'line' => scalar(@$lref),
126			       'file' => $deffile });
127		push(@$lref, "$name $value");
128		}
129	}
130}
131
132sub renumber
133{
134local ($conf, $line, $file, $offset) = @_;
135foreach my $c (@$conf) {
136	if ($c->{'line'} >= $line && $c->{'file'} eq $file) {
137		$c->{'line'} += $offset;
138		}
139	}
140}
141
142# get_rootbinddn_secret()
143# Returns the password used when the root user connects to the LDAP server
144sub get_rootbinddn_secret
145{
146local @secrets = split(/\t+/, $config{'secret'});
147&open_readfile(SECRET, $secrets[0]) || return undef;
148local $secret = <SECRET>;
149close(SECRET);
150$secret =~ s/\r|\n//g;
151return $secret;
152}
153
154# save_rootbinddn_secret(secret)
155# Saves the password used when the root user connects to the LDAP server
156sub save_rootbinddn_secret
157{
158local @secrets = split(/\t+/, $config{'secret'});
159if (defined($_[0])) {
160	foreach my $secret (@secrets) {
161		&open_tempfile(SECRET, ">$secret");
162		&print_tempfile(SECRET, $_[0],"\n");
163		&close_tempfile(SECRET);
164		&set_ownership_permissions(0, 0, 0600, $secret);
165		}
166	}
167else {
168	&unlink_file(@secrets);
169	}
170}
171
172# ldap_connect(return-error, [&host])
173# Connect to the LDAP server and return a handle to the Net::LDAP object
174sub ldap_connect
175{
176# Load the LDAP module
177eval "use Net::LDAP";
178if ($@) {
179	local $err = &text('ldap_emodule', "<tt>Net::LDAP</tt>",
180		   "../cpan/download.cgi?source=3&".
181		   "cpan=Convert::ASN1%20Net::LDAP&mode=2&".
182		   "return=../$module_name/&".
183		   "returndesc=".&urlize($module_info{'desc'}));
184	if ($_[0]) { return $err; }
185	else { &error($err); }
186	}
187local $err = &generic_ldap_connect($config{'ldap_hosts'}, $config{'ldap_port'},
188			     $config{'ldap_tls'}, $config{'ldap_user'},
189			     $config{'ldap_pass'});
190if (ref($err)) { return $err; }		# Worked
191elsif ($_[0]) { return $err; }		# Caller asked for error return
192else { &error($err); }			# Caller asked for error() call
193}
194
195# generic_ldap_connect([host], [port], [ssl], [login], [password])
196# A generic function for connecting to an LDAP server. Uses the system's
197# LDAP client config file if any parameters are missing. Returns the LDAP
198# handle on success or an error message on failure.
199sub generic_ldap_connect
200{
201local ($ldap_hosts, $ldap_port, $ldap_ssl, $ldap_user, $ldap_pass) = @_;
202
203# Check for perl module and config file
204eval "use Net::LDAP";
205if ($@) {
206	return &text('ldap_emodule2', "<tt>Net::LDAP</tt>");
207	}
208my $deffile = &get_ldap_config_file();
209if (!-r $deffile) {
210	$ldap_hosts && $ldap_user ||
211		return &text('ldap_econf', "<tt>$deffile</tt>");
212	}
213
214# Get the host and port
215local $conf = &get_config();
216local $uri = &find_svalue("uri", $conf);
217local ($ldap, $use_ssl, $err);
218local $ssl = &find_svalue("ssl", $conf);
219local $cafile = &find_svalue("tls_cacertfile", $conf);
220local $certfile = &find_svalue("tls_cert", $conf);
221local $keyfile = &find_svalue("tls_key", $conf);
222local $ciphers = &find_svalue("tls_ciphers", $conf);
223local $host;
224if ($ldap_hosts) {
225	# Using hosts from parameter
226	local @hosts = split(/[ \t,]+/, $ldap_hosts);
227	if ($ldap_ssl ne '') {
228		$use_ssl = $ldap_ssl;
229		}
230	else {
231		$use_ssl = $ssl eq 'yes' ? 1 :
232			   $ssl eq 'start_tls' ? 2 : 0;
233		}
234	local $port = $ldap_port ||
235		      &find_svalue("port", $conf) ||
236		      ($use_ssl == 1 ? 636 : 389);
237	foreach my $h (@hosts) {
238		eval {
239			$ldap = Net::LDAP->new($h, port => $port,
240				scheme => $use_ssl == 1 ? 'ldaps' : 'ldap',
241				inet6 => &should_use_inet6($h));
242			};
243		if ($@) {
244			$err = &text('ldap_econn2',
245				     "<tt>$host</tt>", "<tt>$port</tt>",
246				     &html_escape($@));
247			}
248		elsif (!$ldap) {
249			$err = &text('ldap_econn',
250				     "<tt>$host</tt>", "<tt>$port</tt>");
251			}
252		else {
253			$host = $h;
254			$err = undef;
255			last;
256			}
257		}
258	}
259elsif ($uri) {
260	# Using uri directive
261	foreach my $u (split(/\s+/, $uri)) {
262		if ($u =~ /^(ldap|ldaps|ldapi):\/\/([a-z0-9\_\-\.]+)(:(\d+))?/i) {
263			($proto, $host, $port) = ($1, $2, $4);
264			if (!$port && $proto eq "ldap") {
265				$port = 389;
266				}
267			elsif (!$port && $proto eq "ldaps") {
268				$port = 636;
269				}
270			$ldap = Net::LDAP->new($host, port => $port,
271				       scheme => $proto,
272				       inet6 => &should_use_inet6($host));
273			if (!$ldap) {
274				$err = &text('ldap_econn',
275					     "<tt>$host</tt>","<tt>$port</tt>");
276				}
277			else {
278				$err = undef;
279				$use_ssl = $proto eq "ldaps" ? 1 :
280					   $ssl eq 'start_tls' ? 2 : 0;
281				last;
282				}
283			}
284		}
285	if (!$ldap && !$err) {
286		$err = &text('ldap_eparse', $uri);
287		}
288	}
289else {
290	# Using host and port directives
291	$use_ssl = $ssl eq 'yes' ? 1 :
292		   $ssl eq 'start_tls' ? 2 : 0;
293	local @hosts = split(/[ ,]+/, &find_svalue("host", $conf));
294	local $port = &find_svalue("port", $conf) ||
295		      ($use_ssl == 1 ? 636 : 389);
296	@hosts = ( "localhost" ) if (!@hosts);
297
298	foreach my $h (@hosts) {
299		$ldap = Net::LDAP->new($h, port => $port,
300			       scheme => $use_ssl == 1 ? 'ldaps' : 'ldap',
301			       inet6 => &should_use_inet6($h));
302		if (!$ldap) {
303			$err = &text('ldap_econn',
304				     "<tt>$host</tt>", "<tt>$port</tt>");
305			}
306		else {
307			$host = $h;
308			$err = undef;
309			last;
310			}
311		}
312	}
313
314# Start TLS if configured
315if ($use_ssl == 2 && !$err) {
316	local $mesg;
317	if ($certfile) {
318		# Use cert to connect
319		eval { $mesg = $ldap->start_tls(
320					cafile     => $cafile,
321                                        clientcert => $certfile,
322                                        clientkey  => $keyfile,
323                                        ciphers    => $ciphers
324					); };
325
326		}
327	else {
328		eval { $mesg = $ldap->start_tls(); };
329		}
330	if ($@ || !$mesg || $mesg->code) {
331		$err = &text('ldap_etls', $@ ? $@ : $mesg ? $mesg->error :
332					  "Unknown error");
333		}
334	}
335
336if ($err) {
337	return $err;
338	}
339
340local ($dn, $password);
341local $rootbinddn = &find_svalue("rootpwmoddn", $conf) ||
342		    &find_svalue("rootbinddn", $conf);
343if ($ldap_user) {
344	# Use login from config
345	$dn = $ldap_user;
346	$password = $ldap_pass;
347	}
348elsif ($rootbinddn) {
349	# Use the root login if we have one
350	$dn = $rootbinddn;
351	$password = &find_svalue("rootpwmodpw", $conf) ||
352		    &get_rootbinddn_secret();
353	}
354else {
355	# Use the normal login
356	$dn = &find_svalue("binddn", $conf);
357	$password = &find_svalue("bindpw", $conf);
358	}
359local $mesg;
360if ($password) {
361	$mesg = $ldap->bind(dn => $dn, password => $password);
362	}
363else {
364	$mesg = $ldap->bind(dn => $dn, anonymous => 1);
365	}
366if (!$mesg || $mesg->code) {
367	local $err = &text('ldap_elogin', "<tt>$host</tt>",
368		     	   $dn || $text{'ldap_anon'},
369			   $mesg ? $mesg->error : "Unknown error");
370	if ($_[0]) { return $err; }
371	else { &error($err); }
372	}
373return $ldap;
374}
375
376# should_use_inet6(host)
377# Returns 1 if some host has a v6 address but not v4
378sub should_use_inet6
379{
380local ($host) = @_;
381return !&to_ipaddress($host) && &to_ip6address($host);
382}
383
384# base_chooser_button(field, node, form)
385# Returns HTML for a popup LDAP base chooser button
386sub base_chooser_button
387{
388local ($field, $node, $form) = @_;
389$form ||= 0;
390local $w = 500;
391local $h = 500;
392if ($gconfig{'db_sizeusers'}) {
393	($w, $h) = split(/x/, $gconfig{'db_sizeusers'});
394	}
395return "<input type=button onClick='ifield = document.forms[$form].$field; chooser = window.open(\"popup_browser.cgi?node=$node&base=\"+escape(ifield.value), \"chooser\", \"toolbar=no,menubar=no,scrollbars=yes,width=$w,height=$h\"); chooser.ifield = ifield; window.ifield = ifield' value=\"...\">\n";
396}
397
398# get_ldap_host()
399# Returns the hostname probably used for connecting
400sub get_ldap_host
401{
402local @hosts;
403if ($config{'ldap_hosts'}) {
404	@hosts = split(/\s+/, $config{'ldap_hosts'});
405	}
406elsif (!-r &get_ldap_config_file()) {
407	@hosts = ( );
408	}
409else {
410	local $conf = &get_config();
411	local $uri = &find_svalue("uri", $conf);
412	if ($uri) {
413		foreach my $u (split(/\s+/, $uri)) {
414			if ($u =~ /^(ldap|ldaps|ldapi):\/\/([a-z0-9\_\-\.]+)(:(\d+))?/) {
415				push(@hosts, $2);
416				}
417			}
418		}
419	else {
420		@hosts = split(/[ ,]+/, &find_svalue("host", $conf));
421		}
422	if (!@hosts) {
423		@hosts = ( "localhost" );
424		}
425	}
426return wantarray ? @hosts : $hosts[0];
427}
428
429# fix_ldap_authconfig()
430# If the systme has a /etc/sysconfig/authconfig file, enable LDAP in it.
431sub fix_ldap_authconfig
432{
433my $afile = "/etc/sysconfig/authconfig";
434return 0 if (!-r $afile);
435&lock_file($afile);
436my %auth;
437&read_env_file($afile, \%auth);
438if ($auth{'USELDAP'} =~ /no/i) {
439	$auth{'USELDAP'} = 'yes';
440	$changed++;
441	}
442if ($auth{'USELDAPAUTH'} =~ /no/i) {
443	$auth{'USELDAPAUTH'} = 'yes';
444	$changed++;
445	}
446if ($changed) {
447	&write_env_file($afile, \%auth);
448	}
449&unlock_file($afile);
450}
451
452# get_ldap_client()
453# Returns either "nss" or "nslcd" depending on the LDAP client being used
454sub get_ldap_client
455{
456return $config{'auth_ldap'} =~ /nslcd/ ? 'nslcd' : 'nss';
457}
458
4591;
460
461