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