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 $curentry{'current_dn'} = $current_dn; 141} 142 143 144sub 145remove_dn_from_stack 146{ 147 $current_dn =~ s/^.*?,\s*//; 148} 149 150 151sub 152parse_error 153{ 154 print "Parse error on line number $line_number at token number $token_number\n"; 155 exit (1); 156} 157 158sub 159new_entry 160{ 161 if (%curentry) { 162 $curentry{'current_dn'} = $current_dn; 163 push(@entrystack, {%curentry}); 164 undef(%curentry); 165 } 166} 167 168sub 169pop_entry 170{ 171 if (%curentry) { 172 push(@outputlist, {%curentry}); 173 } 174 $rentry = pop(@entrystack); 175 %curentry = %$rentry if $rentry; 176} 177 178 179sub 180print_entry 181{ 182 return if (scalar keys %curentry == 0); 183 184 if (!defined ($curentry{'type'})) 185 { 186 $hostdn = "cn=$server, $basedn"; 187 print "dn: $hostdn\n"; 188 print "cn: $server\n"; 189 print "objectClass: top\n"; 190 print "objectClass: dhcpServer\n"; 191 print "dhcpServiceDN: $curentry{'current_dn'}\n"; 192 if(grep(/FaIlOvEr/i, @use)) 193 { 194 foreach my $fo_peer (keys %failover) 195 { 196 next if(scalar(@{$failover{$fo_peer}}) <= 1); 197 print "dhcpStatements: failover peer $fo_peer { ", 198 join('; ', @{$failover{$fo_peer}}), "; }\n"; 199 } 200 } 201 print "\n"; 202 203 print "dn: $curentry{'current_dn'}\n"; 204 print "cn: $dhcpcn\n"; 205 print "objectClass: top\n"; 206 print "objectClass: dhcpService\n"; 207 if (defined ($curentry{'options'})) 208 { 209 print "objectClass: dhcpOptions\n"; 210 } 211 print "dhcpPrimaryDN: $hostdn\n"; 212 if(grep(/FaIlOvEr/i, @use) and ($second ne '')) 213 { 214 print "dhcpSecondaryDN: $second\n"; 215 } 216 } 217 elsif ($curentry{'type'} eq 'subnet') 218 { 219 print "dn: $curentry{'current_dn'}\n"; 220 print "cn: " . $curentry{'ip'} . "\n"; 221 print "objectClass: top\n"; 222 print "objectClass: dhcpSubnet\n"; 223 if (defined ($curentry{'options'})) 224 { 225 print "objectClass: dhcpOptions\n"; 226 } 227 228 print "dhcpNetMask: " . $curentry{'netmask'} . "\n"; 229 if (defined ($curentry{'ranges'})) 230 { 231 foreach $statement (@{$curentry{'ranges'}}) 232 { 233 print "dhcpRange: $statement\n"; 234 } 235 } 236 } 237 elsif ($curentry{'type'} eq 'shared-network') 238 { 239 print "dn: $curentry{'current_dn'}\n"; 240 print "cn: " . $curentry{'descr'} . "\n"; 241 print "objectClass: top\n"; 242 print "objectClass: dhcpSharedNetwork\n"; 243 if (defined ($curentry{'options'})) 244 { 245 print "objectClass: dhcpOptions\n"; 246 } 247 } 248 elsif ($curentry{'type'} eq 'group') 249 { 250 print "dn: $curentry{'current_dn'}\n"; 251 print "cn: group", $curentry{'idx'}, "\n"; 252 print "objectClass: top\n"; 253 print "objectClass: dhcpGroup\n"; 254 if (defined ($curentry{'options'})) 255 { 256 print "objectClass: dhcpOptions\n"; 257 } 258 } 259 elsif ($curentry{'type'} eq 'host') 260 { 261 print "dn: $curentry{'current_dn'}\n"; 262 print "cn: " . $curentry{'host'} . "\n"; 263 print "objectClass: top\n"; 264 print "objectClass: dhcpHost\n"; 265 if (defined ($curentry{'options'})) 266 { 267 print "objectClass: dhcpOptions\n"; 268 } 269 270 if (defined ($curentry{'hwaddress'})) 271 { 272 $curentry{'hwaddress'} =~ y/[A-Z]/[a-z]/; 273 print "dhcpHWAddress: " . $curentry{'hwaddress'} . "\n"; 274 } 275 } 276 elsif ($curentry{'type'} eq 'pool') 277 { 278 print "dn: $curentry{'current_dn'}\n"; 279 print "cn: pool", $curentry{'idx'}, "\n"; 280 print "objectClass: top\n"; 281 print "objectClass: dhcpPool\n"; 282 if (defined ($curentry{'options'})) 283 { 284 print "objectClass: dhcpOptions\n"; 285 } 286 287 if (defined ($curentry{'ranges'})) 288 { 289 foreach $statement (@{$curentry{'ranges'}}) 290 { 291 print "dhcpRange: $statement\n"; 292 } 293 } 294 } 295 elsif ($curentry{'type'} eq 'class') 296 { 297 print "dn: $curentry{'current_dn'}\n"; 298 print "cn: " . $curentry{'class'} . "\n"; 299 print "objectClass: top\n"; 300 print "objectClass: dhcpClass\n"; 301 if (defined ($curentry{'options'})) 302 { 303 print "objectClass: dhcpOptions\n"; 304 } 305 } 306 elsif ($curentry{'type'} eq 'subclass') 307 { 308 print "dn: $curentry{'current_dn'}\n"; 309 print "cn: " . $curentry{'subclass'} . "\n"; 310 print "objectClass: top\n"; 311 print "objectClass: dhcpSubClass\n"; 312 if (defined ($curentry{'options'})) 313 { 314 print "objectClass: dhcpOptions\n"; 315 } 316 print "dhcpClassData: " . $curentry{'class'} . "\n"; 317 } 318 319 if (defined ($curentry{'statements'})) 320 { 321 foreach $statement (@{$curentry{'statements'}}) 322 { 323 print "dhcpStatements: $statement\n"; 324 } 325 } 326 327 if (defined ($curentry{'options'})) 328 { 329 foreach $statement (@{$curentry{'options'}}) 330 { 331 print "dhcpOption: $statement\n"; 332 } 333 } 334 335 print "\n"; 336 undef (%curentry); 337} 338 339 340sub parse_netmask 341{ 342 local ($netmask) = @_; 343 local ($i); 344 345 if ((($a, $b, $c, $d) = $netmask =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) != 4) 346 { 347 parse_error (); 348 } 349 350 $num = (($a & 0xff) << 24) | 351 (($b & 0xff) << 16) | 352 (($c & 0xff) << 8) | 353 ($d & 0xff); 354 355 for ($i=1; $i<=32 && $num & (1 << (32 - $i)); $i++) 356 { 357 } 358 $i--; 359 360 return ($i); 361} 362 363 364sub parse_subnet 365{ 366 local ($ip, $tmp, $netmask); 367 368 new_entry (); 369 370 $ip = next_token (0); 371 parse_error () if !defined ($ip); 372 373 $tmp = next_token (1); 374 parse_error () if !defined ($tmp); 375 parse_error () if !($tmp eq 'netmask'); 376 377 $tmp = next_token (0); 378 parse_error () if !defined ($tmp); 379 $netmask = parse_netmask ($tmp); 380 381 $tmp = next_token (0); 382 parse_error () if !defined ($tmp); 383 parse_error () if !($tmp eq '{'); 384 385 add_dn_to_stack ("cn=$ip"); 386 $curentry{'type'} = 'subnet'; 387 $curentry{'ip'} = $ip; 388 $curentry{'netmask'} = $netmask; 389 $cursubnet = $ip; 390 $curcounter{$ip} = { pool => 0, group => 0 }; 391} 392 393 394sub parse_shared_network 395{ 396 local ($descr, $tmp); 397 398 new_entry (); 399 400 $descr = next_token (0); 401 parse_error () if !defined ($descr); 402 403 $tmp = next_token (0); 404 parse_error () if !defined ($tmp); 405 parse_error () if !($tmp eq '{'); 406 407 add_dn_to_stack ("cn=$descr"); 408 $curentry{'type'} = 'shared-network'; 409 $curentry{'descr'} = $descr; 410} 411 412 413sub parse_host 414{ 415 local ($descr, $tmp); 416 417 new_entry (); 418 419 $host = next_token (0); 420 parse_error () if !defined ($host); 421 422 $tmp = next_token (0); 423 parse_error () if !defined ($tmp); 424 parse_error () if !($tmp eq '{'); 425 426 add_dn_to_stack ("cn=$host"); 427 $curentry{'type'} = 'host'; 428 $curentry{'host'} = $host; 429} 430 431 432sub parse_group 433{ 434 local ($descr, $tmp); 435 436 new_entry (); 437 438 $tmp = next_token (0); 439 parse_error () if !defined ($tmp); 440 parse_error () if !($tmp eq '{'); 441 442 my $idx; 443 if(exists($curcounter{$cursubnet})) { 444 $idx = ++$curcounter{$cursubnet}->{'group'}; 445 } else { 446 $idx = ++$curcounter{''}->{'group'}; 447 } 448 449 add_dn_to_stack ("cn=group".$idx); 450 $curentry{'type'} = 'group'; 451 $curentry{'idx'} = $idx; 452} 453 454 455sub parse_pool 456{ 457 local ($descr, $tmp); 458 459 new_entry (); 460 461 $tmp = next_token (0); 462 parse_error () if !defined ($tmp); 463 parse_error () if !($tmp eq '{'); 464 465 my $idx; 466 if(exists($curcounter{$cursubnet})) { 467 $idx = ++$curcounter{$cursubnet}->{'pool'}; 468 } else { 469 $idx = ++$curcounter{''}->{'pool'}; 470 } 471 472 add_dn_to_stack ("cn=pool".$idx); 473 $curentry{'type'} = 'pool'; 474 $curentry{'idx'} = $idx; 475} 476 477 478sub parse_class 479{ 480 local ($descr, $tmp); 481 482 new_entry (); 483 484 $class = next_token (0); 485 parse_error () if !defined ($class); 486 487 $tmp = next_token (0); 488 parse_error () if !defined ($tmp); 489 parse_error () if !($tmp eq '{'); 490 491 $class =~ s/\"//g; 492 add_dn_to_stack ("cn=$class"); 493 $curentry{'type'} = 'class'; 494 $curentry{'class'} = $class; 495} 496 497 498sub parse_subclass 499{ 500 local ($descr, $tmp); 501 502 new_entry (); 503 504 $class = next_token (0); 505 parse_error () if !defined ($class); 506 507 $subclass = next_token (0); 508 parse_error () if !defined ($subclass); 509 510 if (substr($subclass,-1) eq ';') { 511 $tmp = ";"; 512 $subclass = substr($subclass,0,-1); 513 } else { 514 $tmp = next_token (0); 515 parse_error () if !defined ($tmp); 516 } 517 parse_error () if !($tmp eq '{' or $tmp eq ';'); 518 add_dn_to_stack ("cn=$subclass"); 519 $curentry{'type'} = 'subclass'; 520 $curentry{'class'} = $class; 521 $curentry{'subclass'} = $subclass; 522 523 if ($tmp eq ';') { 524 pop_entry (); 525 remove_dn_from_stack (); 526 } 527} 528 529 530sub parse_hwaddress 531{ 532 local ($type, $hw, $tmp); 533 534 $type = next_token (1); 535 parse_error () if !defined ($type); 536 537 $hw = next_token (1); 538 parse_error () if !defined ($hw); 539 $hw =~ s/;$//; 540 541 $curentry{'hwaddress'} = "$type $hw"; 542} 543 544 545sub parse_range 546{ 547 local ($tmp, $str); 548 549 $str = remaining_line (); 550 551 if (!($str eq '')) 552 { 553 $str =~ s/;$//; 554 push (@{$curentry{'ranges'}}, $str); 555 } 556} 557 558 559sub parse_statement 560{ 561 local ($token) = shift; 562 local ($str); 563 564 if ($token eq 'option') 565 { 566 $str = remaining_line (); 567 push (@{$curentry{'options'}}, $str); 568 } 569 elsif($token eq 'failover') 570 { 571 $str = remaining_line (1); # take care on block 572 if($str =~ /[{]/) 573 { 574 my ($peername, @statements); 575 576 parse_error() if($str !~ /^\s*peer\s+(.+?)\s+[{]\s*$/); 577 parse_error() if(($peername = $1) !~ /^\"?[^\"]+\"?$/); 578 579 # 580 # failover config block found: 581 # e.g. 'failover peer "some-name" {' 582 # 583 if(not grep(/FaIlOvEr/i, @use)) 584 { 585 print STDERR "Warning: Failover config 'peer $peername' found!\n"; 586 print STDERR " Skipping it, since failover disabled!\n"; 587 print STDERR " You may try out --use=failover option.\n"; 588 } 589 590 until($str =~ /[}]/ or $str eq "") 591 { 592 $str = remaining_line (1); 593 # collect all statements, except ending '}' 594 push(@statements, $str) if($str !~ /[}]/); 595 } 596 $failover{$peername} = [@statements]; 597 } 598 else 599 { 600 # 601 # pool reference to failover config is fine 602 # e.g. 'failover peer "some-name";' 603 # 604 if(not grep(/FaIlOvEr/i, @use)) 605 { 606 print STDERR "Warning: Failover reference '$str' found!\n"; 607 print STDERR " Skipping it, since failover disabled!\n"; 608 print STDERR " You may try out --use=failover option.\n"; 609 } 610 else 611 { 612 push (@{$curentry{'statements'}}, $token. " " . $str); 613 } 614 } 615 } 616 elsif($token eq 'zone') 617 { 618 $str = $token; 619 while($str !~ /}$/) { 620 $str .= ' ' . next_token (0); 621 } 622 push (@{$curentry{'statements'}}, $str); 623 } 624 elsif($token =~ /^(authoritative)[;]*$/) 625 { 626 push (@{$curentry{'statements'}}, $1); 627 } 628 else 629 { 630 $str = $token . " " . remaining_line (); 631 push (@{$curentry{'statements'}}, $str); 632 } 633} 634 635 636my $ok = GetOptions( 637 'basedn=s' => \$basedn, 638 'dhcpdn=s' => \$dhcpdn, 639 'server=s' => \$server, 640 'second=s' => \$second, 641 'conf=s' => \$i_conf, 642 'ldif=s' => \$o_ldif, 643 'use=s' => \@use, 644 'h|help|usage' => sub { usage(0); }, 645); 646 647unless($server =~ /^\w+/) 648 { 649 usage(1, "invalid server name '$server'"); 650 } 651unless($basedn =~ /^\w+=[^,]+/) 652 { 653 usage(1, "invalid base dn '$basedn'"); 654 } 655 656if($dhcpdn =~ /^cn=([^,]+)/i) 657 { 658 $dhcpcn = "$1"; 659 } 660$second = '' if not defined $second; 661unless($second eq '' or $second =~ /^cn=[^,]+\s*,\s*\w+=[^,]+/i) 662 { 663 if($second =~ /^cn=[^,]+$/i) 664 { 665 # relative DN 'cn=name' 666 $second = "$second, $basedn"; 667 } 668 elsif($second =~ /^\w+/) 669 { 670 # assume hostname only 671 $second = "cn=$second, $basedn"; 672 } 673 else 674 { 675 usage(1, "invalid secondary '$second'") 676 } 677 } 678 679usage(1) unless($ok); 680 681if($i_conf ne "" and -f $i_conf) 682 { 683 if(not open(STDIN, '<', $i_conf)) 684 { 685 print STDERR "Error: can't open conf file '$i_conf': $!\n"; 686 exit(1); 687 } 688 } 689if($o_ldif ne "") 690 { 691 if(-e $o_ldif) 692 { 693 print STDERR "Error: output ldif name '$o_ldif' already exists!\n"; 694 exit(1); 695 } 696 if(not open(STDOUT, '>', $o_ldif)) 697 { 698 print STDERR "Error: can't open ldif file '$o_ldif': $!\n"; 699 exit(1); 700 } 701 } 702 703 704print STDERR "Creating LDAP Configuration with the following options:\n"; 705print STDERR "\tBase DN: $basedn\n"; 706print STDERR "\tDHCP DN: $dhcpdn\n"; 707print STDERR "\tServer DN: cn=$server, $basedn\n"; 708print STDERR "\tSecondary DN: $second\n" 709 if(grep(/FaIlOvEr/i, @use) and $second ne ''); 710print STDERR "\n"; 711 712my $token; 713my $token_number = 0; 714my $line_number = 0; 715my $cursubnet = ''; 716my %curcounter = ( '' => { pool => 0, group => 0 } ); 717 718$current_dn = "$dhcpdn"; 719$curentry{'current_dn'} = $current_dn; 720$curentry{'descr'} = $dhcpcn; 721$line = ''; 722%failover = (); 723 724while (($token = next_token (1))) 725 { 726 if ($token eq '}') 727 { 728 pop_entry (); 729 if($current_dn =~ /.+?,\s*${dhcpdn}$/) { 730 # don't go below dhcpdn ... 731 remove_dn_from_stack (); 732 } 733 } 734 elsif ($token eq 'subnet') 735 { 736 parse_subnet (); 737 next; 738 } 739 elsif ($token eq 'shared-network') 740 { 741 parse_shared_network (); 742 next; 743 } 744 elsif ($token eq 'class') 745 { 746 parse_class (); 747 next; 748 } 749 elsif ($token eq 'subclass') 750 { 751 parse_subclass (); 752 next; 753 } 754 elsif ($token eq 'pool') 755 { 756 parse_pool (); 757 next; 758 } 759 elsif ($token eq 'group') 760 { 761 parse_group (); 762 next; 763 } 764 elsif ($token eq 'host') 765 { 766 parse_host (); 767 next; 768 } 769 elsif ($token eq 'hardware') 770 { 771 parse_hwaddress (); 772 next; 773 } 774 elsif ($token eq 'range') 775 { 776 parse_range (); 777 next; 778 } 779 else 780 { 781 parse_statement ($token); 782 next; 783 } 784 } 785 786pop_entry (); 787 788while ($#outputlist >= 0) { 789 $rentry = pop(@outputlist); 790 if ($rentry) { 791 %curentry = %$rentry; 792 print_entry (); 793 } 794} 795 796close(STDIN) if($i_conf); 797close(STDOUT) if($o_ldif); 798 799print STDERR "Done.\n"; 800 801