1#!/usr/bin/perl -w 2 3# Brian Masney <masneyb@gftp.org> 4# To use this script, set your base DN below. Then run 5# ./dhcpd-conf-to-ldap.pl < /path-to-dhcpd-conf/dhcpd.conf > output-file 6# The output of this script will generate entries in LDIF format. You can use 7# the slapadd command to add these entries into your LDAP server. You will 8# definately want to double check that your LDAP entries are correct before 9# you load them into LDAP. 10 11# This script does not do much error checking. Make sure before you run this 12# that the DHCP server doesn't give any errors about your config file 13 14# FailOver notes: 15# Failover is disabled by default, since it may need manually intervention. 16# You can try the '--use=failover' option to see what happens :-) 17# 18# If enabled, the failover pool references will be written to LDIF output. 19# The failover configs itself will be added to the dhcpServer statements 20# and not to the dhcpService object (since this script uses only one and 21# it may be usefull to have multiple service containers in failover mode). 22# Further, this script does not check if primary or secondary makes sense, 23# it simply converts what it gets... 24 25use Net::Domain qw(hostname hostfqdn hostdomain); 26use Getopt::Long; 27 28my $domain = hostdomain(); # your.domain 29my $basedn = "dc=".$domain; 30 $basedn =~ s/\./,dc=/g; # dc=your,dc=domain 31my $server = hostname(); # hostname (nodename) 32my $dhcpcn = 'DHCP Config'; # CN of DHCP config tree 33my $dhcpdn = "cn=$dhcpcn, $basedn"; # DHCP config tree DN 34my $second = ''; # secondary server DN / hostname 35my $i_conf = ''; # dhcp.conf file to read or stdin 36my $o_ldif = ''; # output ldif file name or stdout 37my @use = (); # extended flags (failover) 38 39sub usage($;$) 40{ 41 my $rc = shift; 42 my $err= shift; 43 44 print STDERR "Error: $err\n\n" if(defined $err); 45 print STDERR <<__EOF_USAGE__; 46usage: 47 $0 [options] < dhcpd.conf > dhcpd.ldif 48 49options: 50 51 --basedn "dc=your,dc=domain" ("$basedn") 52 53 --dhcpdn "dhcp config DN" ("$dhcpdn") 54 55 --server "dhcp server name" ("$server") 56 57 --second "secondary server or DN" ("$second") 58 59 --conf "/path/to/dhcpd.conf" (default is stdin) 60 --ldif "/path/to/output.ldif" (default is stdout) 61 62 --use "extended features" (see source comments) 63__EOF_USAGE__ 64 exit($rc); 65} 66 67 68sub next_token 69{ 70 local ($lowercase) = @_; 71 local ($token, $newline); 72 73 do 74 { 75 if (!defined ($line) || length ($line) == 0) 76 { 77 $line = <>; 78 return undef if !defined ($line); 79 chop $line; 80 $line_number++; 81 $token_number = 0; 82 } 83 84 $line =~ s/#.*//; 85 $line =~ s/^\s+//; 86 $line =~ s/\s+$//; 87 } 88 while (length ($line) == 0); 89 90 if (($token, $newline) = $line =~ /^(.*?)\s+(.*)/) 91 { 92 if ($token =~ /^"/) { 93 #handle quoted token 94 if ($token !~ /"\s*$/) 95 { 96 ($tok, $newline) = $newline =~ /([^"]+")(.*)/; 97 $token .= " $tok"; 98 } 99 } 100 $line = $newline; 101 } 102 else 103 { 104 $token = $line; 105 $line = ''; 106 } 107 $token_number++; 108 109 $token =~ y/[A-Z]/[a-z]/ if $lowercase; 110 111 return ($token); 112} 113 114 115sub remaining_line 116{ 117 local ($block) = shift || 0; 118 local ($tmp, $str); 119 120 $str = ""; 121 while (defined($tmp = next_token (0))) 122 { 123 $str .= ' ' if !($str eq ""); 124 $str .= $tmp; 125 last if $tmp =~ /;\s*$/; 126 last if($block and $tmp =~ /\s*[}{]\s*$/); 127 } 128 129 $str =~ s/;$//; 130 return ($str); 131} 132 133 134sub 135add_dn_to_stack 136{ 137 local ($dn) = @_; 138 139 $current_dn = "$dn, $current_dn"; 140} 141 142 143sub 144remove_dn_from_stack 145{ 146 $current_dn =~ s/^.*?,\s*//; 147} 148 149 150sub 151parse_error 152{ 153 print "Parse error on line number $line_number at token number $token_number\n"; 154 exit (1); 155} 156 157 158sub 159print_entry 160{ 161 return if (scalar keys %curentry == 0); 162 163 if (!defined ($curentry{'type'})) 164 { 165 $hostdn = "cn=$server, $basedn"; 166 print "dn: $hostdn\n"; 167 print "cn: $server\n"; 168 print "objectClass: top\n"; 169 print "objectClass: dhcpServer\n"; 170 print "dhcpServiceDN: $current_dn\n"; 171 if(grep(/FaIlOvEr/i, @use)) 172 { 173 foreach my $fo_peer (keys %failover) 174 { 175 next if(scalar(@{$failover{$fo_peer}}) <= 1); 176 print "dhcpStatements: failover peer $fo_peer { ", 177 join('; ', @{$failover{$fo_peer}}), "; }\n"; 178 } 179 } 180 print "\n"; 181 182 print "dn: $current_dn\n"; 183 print "cn: $dhcpcn\n"; 184 print "objectClass: top\n"; 185 print "objectClass: dhcpService\n"; 186 if (defined ($curentry{'options'})) 187 { 188 print "objectClass: dhcpOptions\n"; 189 } 190 print "dhcpPrimaryDN: $hostdn\n"; 191 if(grep(/FaIlOvEr/i, @use) and ($second ne '')) 192 { 193 print "dhcpSecondaryDN: $second\n"; 194 } 195 } 196 elsif ($curentry{'type'} eq 'subnet') 197 { 198 print "dn: $current_dn\n"; 199 print "cn: " . $curentry{'ip'} . "\n"; 200 print "objectClass: top\n"; 201 print "objectClass: dhcpSubnet\n"; 202 if (defined ($curentry{'options'})) 203 { 204 print "objectClass: dhcpOptions\n"; 205 } 206 207 print "dhcpNetMask: " . $curentry{'netmask'} . "\n"; 208 if (defined ($curentry{'ranges'})) 209 { 210 foreach $statement (@{$curentry{'ranges'}}) 211 { 212 print "dhcpRange: $statement\n"; 213 } 214 } 215 } 216 elsif ($curentry{'type'} eq 'shared-network') 217 { 218 print "dn: $current_dn\n"; 219 print "cn: " . $curentry{'descr'} . "\n"; 220 print "objectClass: top\n"; 221 print "objectClass: dhcpSharedNetwork\n"; 222 if (defined ($curentry{'options'})) 223 { 224 print "objectClass: dhcpOptions\n"; 225 } 226 } 227 elsif ($curentry{'type'} eq 'group') 228 { 229 print "dn: $current_dn\n"; 230 print "cn: group", $curentry{'idx'}, "\n"; 231 print "objectClass: top\n"; 232 print "objectClass: dhcpGroup\n"; 233 if (defined ($curentry{'options'})) 234 { 235 print "objectClass: dhcpOptions\n"; 236 } 237 } 238 elsif ($curentry{'type'} eq 'host') 239 { 240 print "dn: $current_dn\n"; 241 print "cn: " . $curentry{'host'} . "\n"; 242 print "objectClass: top\n"; 243 print "objectClass: dhcpHost\n"; 244 if (defined ($curentry{'options'})) 245 { 246 print "objectClass: dhcpOptions\n"; 247 } 248 249 if (defined ($curentry{'hwaddress'})) 250 { 251 $curentry{'hwaddress'} =~ y/[A-Z]/[a-z]/; 252 print "dhcpHWAddress: " . $curentry{'hwaddress'} . "\n"; 253 } 254 } 255 elsif ($curentry{'type'} eq 'pool') 256 { 257 print "dn: $current_dn\n"; 258 print "cn: pool", $curentry{'idx'}, "\n"; 259 print "objectClass: top\n"; 260 print "objectClass: dhcpPool\n"; 261 if (defined ($curentry{'options'})) 262 { 263 print "objectClass: dhcpOptions\n"; 264 } 265 266 if (defined ($curentry{'ranges'})) 267 { 268 foreach $statement (@{$curentry{'ranges'}}) 269 { 270 print "dhcpRange: $statement\n"; 271 } 272 } 273 } 274 elsif ($curentry{'type'} eq 'class') 275 { 276 print "dn: $current_dn\n"; 277 print "cn: " . $curentry{'class'} . "\n"; 278 print "objectClass: top\n"; 279 print "objectClass: dhcpClass\n"; 280 if (defined ($curentry{'options'})) 281 { 282 print "objectClass: dhcpOptions\n"; 283 } 284 } 285 elsif ($curentry{'type'} eq 'subclass') 286 { 287 print "dn: $current_dn\n"; 288 print "cn: " . $curentry{'subclass'} . "\n"; 289 print "objectClass: top\n"; 290 print "objectClass: dhcpSubClass\n"; 291 if (defined ($curentry{'options'})) 292 { 293 print "objectClass: dhcpOptions\n"; 294 } 295 print "dhcpClassData: " . $curentry{'class'} . "\n"; 296 } 297 298 if (defined ($curentry{'statements'})) 299 { 300 foreach $statement (@{$curentry{'statements'}}) 301 { 302 print "dhcpStatements: $statement\n"; 303 } 304 } 305 306 if (defined ($curentry{'options'})) 307 { 308 foreach $statement (@{$curentry{'options'}}) 309 { 310 print "dhcpOption: $statement\n"; 311 } 312 } 313 314 print "\n"; 315 undef (%curentry); 316} 317 318 319sub parse_netmask 320{ 321 local ($netmask) = @_; 322 local ($i); 323 324 if ((($a, $b, $c, $d) = $netmask =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) != 4) 325 { 326 parse_error (); 327 } 328 329 $num = (($a & 0xff) << 24) | 330 (($b & 0xff) << 16) | 331 (($c & 0xff) << 8) | 332 ($d & 0xff); 333 334 for ($i=1; $i<=32 && $num & (1 << (32 - $i)); $i++) 335 { 336 } 337 $i--; 338 339 return ($i); 340} 341 342 343sub parse_subnet 344{ 345 local ($ip, $tmp, $netmask); 346 347 print_entry () if %curentry; 348 349 $ip = next_token (0); 350 parse_error () if !defined ($ip); 351 352 $tmp = next_token (1); 353 parse_error () if !defined ($tmp); 354 parse_error () if !($tmp eq 'netmask'); 355 356 $tmp = next_token (0); 357 parse_error () if !defined ($tmp); 358 $netmask = parse_netmask ($tmp); 359 360 $tmp = next_token (0); 361 parse_error () if !defined ($tmp); 362 parse_error () if !($tmp eq '{'); 363 364 add_dn_to_stack ("cn=$ip"); 365 $curentry{'type'} = 'subnet'; 366 $curentry{'ip'} = $ip; 367 $curentry{'netmask'} = $netmask; 368 $cursubnet = $ip; 369 $curcounter{$ip} = { pool => 0, group => 0 }; 370} 371 372 373sub parse_shared_network 374{ 375 local ($descr, $tmp); 376 377 print_entry () if %curentry; 378 379 $descr = next_token (0); 380 parse_error () if !defined ($descr); 381 382 $tmp = next_token (0); 383 parse_error () if !defined ($tmp); 384 parse_error () if !($tmp eq '{'); 385 386 add_dn_to_stack ("cn=$descr"); 387 $curentry{'type'} = 'shared-network'; 388 $curentry{'descr'} = $descr; 389} 390 391 392sub parse_host 393{ 394 local ($descr, $tmp); 395 396 print_entry () if %curentry; 397 398 $host = next_token (0); 399 parse_error () if !defined ($host); 400 401 $tmp = next_token (0); 402 parse_error () if !defined ($tmp); 403 parse_error () if !($tmp eq '{'); 404 405 add_dn_to_stack ("cn=$host"); 406 $curentry{'type'} = 'host'; 407 $curentry{'host'} = $host; 408} 409 410 411sub parse_group 412{ 413 local ($descr, $tmp); 414 415 print_entry () if %curentry; 416 417 $tmp = next_token (0); 418 parse_error () if !defined ($tmp); 419 parse_error () if !($tmp eq '{'); 420 421 my $idx; 422 if(exists($curcounter{$cursubnet})) { 423 $idx = ++$curcounter{$cursubnet}->{'group'}; 424 } else { 425 $idx = ++$curcounter{''}->{'group'}; 426 } 427 428 add_dn_to_stack ("cn=group".$idx); 429 $curentry{'type'} = 'group'; 430 $curentry{'idx'} = $idx; 431} 432 433 434sub parse_pool 435{ 436 local ($descr, $tmp); 437 438 print_entry () if %curentry; 439 440 $tmp = next_token (0); 441 parse_error () if !defined ($tmp); 442 parse_error () if !($tmp eq '{'); 443 444 my $idx; 445 if(exists($curcounter{$cursubnet})) { 446 $idx = ++$curcounter{$cursubnet}->{'pool'}; 447 } else { 448 $idx = ++$curcounter{''}->{'pool'}; 449 } 450 451 add_dn_to_stack ("cn=pool".$idx); 452 $curentry{'type'} = 'pool'; 453 $curentry{'idx'} = $idx; 454} 455 456 457sub parse_class 458{ 459 local ($descr, $tmp); 460 461 print_entry () if %curentry; 462 463 $class = next_token (0); 464 parse_error () if !defined ($class); 465 466 $tmp = next_token (0); 467 parse_error () if !defined ($tmp); 468 parse_error () if !($tmp eq '{'); 469 470 $class =~ s/\"//g; 471 add_dn_to_stack ("cn=$class"); 472 $curentry{'type'} = 'class'; 473 $curentry{'class'} = $class; 474} 475 476 477sub parse_subclass 478{ 479 local ($descr, $tmp); 480 481 print_entry () if %curentry; 482 483 $class = next_token (0); 484 parse_error () if !defined ($class); 485 486 $subclass = next_token (0); 487 parse_error () if !defined ($subclass); 488 489 $tmp = next_token (0); 490 parse_error () if !defined ($tmp); 491 parse_error () if !($tmp eq '{'); 492 493 add_dn_to_stack ("cn=$subclass"); 494 $curentry{'type'} = 'subclass'; 495 $curentry{'class'} = $class; 496 $curentry{'subclass'} = $subclass; 497} 498 499 500sub parse_hwaddress 501{ 502 local ($type, $hw, $tmp); 503 504 $type = next_token (1); 505 parse_error () if !defined ($type); 506 507 $hw = next_token (1); 508 parse_error () if !defined ($hw); 509 $hw =~ s/;$//; 510 511 $curentry{'hwaddress'} = "$type $hw"; 512} 513 514 515sub parse_range 516{ 517 local ($tmp, $str); 518 519 $str = remaining_line (); 520 521 if (!($str eq '')) 522 { 523 $str =~ s/;$//; 524 push (@{$curentry{'ranges'}}, $str); 525 } 526} 527 528 529sub parse_statement 530{ 531 local ($token) = shift; 532 local ($str); 533 534 if ($token eq 'option') 535 { 536 $str = remaining_line (); 537 push (@{$curentry{'options'}}, $str); 538 } 539 elsif($token eq 'failover') 540 { 541 $str = remaining_line (1); # take care on block 542 if($str =~ /[{]/) 543 { 544 my ($peername, @statements); 545 546 parse_error() if($str !~ /^\s*peer\s+(.+?)\s+[{]\s*$/); 547 parse_error() if(($peername = $1) !~ /^\"?[^\"]+\"?$/); 548 549 # 550 # failover config block found: 551 # e.g. 'failover peer "some-name" {' 552 # 553 if(not grep(/FaIlOvEr/i, @use)) 554 { 555 print STDERR "Warning: Failover config 'peer $peername' found!\n"; 556 print STDERR " Skipping it, since failover disabled!\n"; 557 print STDERR " You may try out --use=failover option.\n"; 558 } 559 560 until($str =~ /[}]/ or $str eq "") 561 { 562 $str = remaining_line (1); 563 # collect all statements, except ending '}' 564 push(@statements, $str) if($str !~ /[}]/); 565 } 566 $failover{$peername} = [@statements]; 567 } 568 else 569 { 570 # 571 # pool reference to failover config is fine 572 # e.g. 'failover peer "some-name";' 573 # 574 if(not grep(/FaIlOvEr/i, @use)) 575 { 576 print STDERR "Warning: Failover reference '$str' found!\n"; 577 print STDERR " Skipping it, since failover disabled!\n"; 578 print STDERR " You may try out --use=failover option.\n"; 579 } 580 else 581 { 582 push (@{$curentry{'statements'}}, $token. " " . $str); 583 } 584 } 585 } 586 elsif($token eq 'zone') 587 { 588 $str = $token; 589 while($str !~ /}$/) { 590 $str .= ' ' . next_token (0); 591 } 592 push (@{$curentry{'statements'}}, $str); 593 } 594 elsif($token =~ /^(authoritative)[;]*$/) 595 { 596 push (@{$curentry{'statements'}}, $1); 597 } 598 else 599 { 600 $str = $token . " " . remaining_line (); 601 push (@{$curentry{'statements'}}, $str); 602 } 603} 604 605 606my $ok = GetOptions( 607 'basedn=s' => \$basedn, 608 'dhcpdn=s' => \$dhcpdn, 609 'server=s' => \$server, 610 'second=s' => \$second, 611 'conf=s' => \$i_conf, 612 'ldif=s' => \$o_ldif, 613 'use=s' => \@use, 614 'h|help|usage' => sub { usage(0); }, 615); 616 617unless($server =~ /^\w+/) 618 { 619 usage(1, "invalid server name '$server'"); 620 } 621unless($basedn =~ /^\w+=[^,]+/) 622 { 623 usage(1, "invalid base dn '$basedn'"); 624 } 625 626if($dhcpdn =~ /^cn=([^,]+)/i) 627 { 628 $dhcpcn = "$1"; 629 } 630$second = '' if not defined $second; 631unless($second eq '' or $second =~ /^cn=[^,]+\s*,\s*\w+=[^,]+/i) 632 { 633 if($second =~ /^cn=[^,]+$/i) 634 { 635 # relative DN 'cn=name' 636 $second = "$second, $basedn"; 637 } 638 elsif($second =~ /^\w+/) 639 { 640 # assume hostname only 641 $second = "cn=$second, $basedn"; 642 } 643 else 644 { 645 usage(1, "invalid secondary '$second'") 646 } 647 } 648 649usage(1) unless($ok); 650 651if($i_conf ne "" and -f $i_conf) 652 { 653 if(not open(STDIN, '<', $i_conf)) 654 { 655 print STDERR "Error: can't open conf file '$i_conf': $!\n"; 656 exit(1); 657 } 658 } 659if($o_ldif ne "") 660 { 661 if(-e $o_ldif) 662 { 663 print STDERR "Error: output ldif name '$o_ldif' already exists!\n"; 664 exit(1); 665 } 666 if(not open(STDOUT, '>', $o_ldif)) 667 { 668 print STDERR "Error: can't open ldif file '$o_ldif': $!\n"; 669 exit(1); 670 } 671 } 672 673 674print STDERR "Creating LDAP Configuration with the following options:\n"; 675print STDERR "\tBase DN: $basedn\n"; 676print STDERR "\tDHCP DN: $dhcpdn\n"; 677print STDERR "\tServer DN: cn=$server, $basedn\n"; 678print STDERR "\tSecondary DN: $second\n" 679 if(grep(/FaIlOvEr/i, @use) and $second ne ''); 680print STDERR "\n"; 681 682my $token; 683my $token_number = 0; 684my $line_number = 0; 685my %curentry; 686my $cursubnet = ''; 687my %curcounter = ( '' => { pool => 0, group => 0 } ); 688 689$current_dn = "$dhcpdn"; 690$curentry{'descr'} = $dhcpcn; 691$line = ''; 692%failover = (); 693 694while (($token = next_token (1))) 695 { 696 if ($token eq '}') 697 { 698 print_entry () if %curentry; 699 if($current_dn =~ /.+?,\s*${dhcpdn}$/) { 700 # don't go below dhcpdn ... 701 remove_dn_from_stack (); 702 } 703 } 704 elsif ($token eq 'subnet') 705 { 706 parse_subnet (); 707 next; 708 } 709 elsif ($token eq 'shared-network') 710 { 711 parse_shared_network (); 712 next; 713 } 714 elsif ($token eq 'class') 715 { 716 parse_class (); 717 next; 718 } 719 elsif ($token eq 'subclass') 720 { 721 parse_subclass (); 722 next; 723 } 724 elsif ($token eq 'pool') 725 { 726 parse_pool (); 727 next; 728 } 729 elsif ($token eq 'group') 730 { 731 parse_group (); 732 next; 733 } 734 elsif ($token eq 'host') 735 { 736 parse_host (); 737 next; 738 } 739 elsif ($token eq 'hardware') 740 { 741 parse_hwaddress (); 742 next; 743 } 744 elsif ($token eq 'range') 745 { 746 parse_range (); 747 next; 748 } 749 else 750 { 751 parse_statement ($token); 752 next; 753 } 754 } 755 756close(STDIN) if($i_conf); 757close(STDOUT) if($o_ldif); 758 759print STDERR "Done.\n"; 760 761