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.
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
15 # Failover is disabled by default, since it may need manually intervention.
16 # You can try the '--use=failover' option to see what happens :-)
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...
25 use Net
::Domain
qw(hostname hostfqdn hostdomain);
28 my $domain = hostdomain
(); # your.domain
29 my $basedn = "dc=".$domain;
30 $basedn =~ s/\./,dc=/g; # dc=your,dc=domain
31 my $server = hostname
(); # hostname (nodename)
32 my $dhcpcn = 'DHCP Config'; # CN of DHCP config tree
33 my $dhcpdn = "cn=$dhcpcn, $basedn"; # DHCP config tree DN
34 my $second = ''; # secondary server DN / hostname
35 my $i_conf = ''; # dhcp.conf file to read or stdin
36 my $o_ldif = ''; # output ldif file name or stdout
37 my @use = (); # extended flags (failover)
44 print STDERR
"Error: $err\n\n" if(defined $err);
45 print STDERR
<<__EOF_USAGE__
;
47 $0 [options
] < dhcpd
.conf
> dhcpd
.ldif
51 --basedn
"dc=your,dc=domain" ("$basedn")
53 --dhcpdn
"dhcp config DN" ("$dhcpdn")
55 --server
"dhcp server name" ("$server")
57 --second
"secondary server or DN" ("$second")
59 --conf
"/path/to/dhcpd.conf" (default is stdin
)
60 --ldif
"/path/to/output.ldif" (default is stdout
)
62 --use "extended features" (see source comments
)
70 local ($lowercase) = @_;
71 local ($token, $newline);
75 if (!defined ($line) || length ($line) == 0)
78 return undef if !defined ($line);
88 while (length ($line) == 0);
90 if (($token, $newline) = $line =~ /^(.*?)\s+(.*)/)
94 if ($token !~ /"\s*$/)
96 ($tok, $newline) = $newline =~ /([^"]+")(.*)/;
109 $token =~ y/[A-Z]/[a-z]/ if $lowercase;
117 local ($block) = shift || 0;
121 while (defined($tmp = next_token
(0)))
123 $str .= ' ' if !($str eq "");
125 last if $tmp =~ /;\s*$/;
126 last if($block and $tmp =~ /\s*[}{]\s*$/);
139 $current_dn = "$dn, $current_dn";
146 $current_dn =~ s/^.*?,\s*//;
153 print "Parse error on line number $line_number at token number $token_number\n";
161 return if (scalar keys %curentry == 0);
163 if (!defined ($curentry{'type'}))
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))
173 foreach my $fo_peer (keys %failover)
175 next if(scalar(@
{$failover{$fo_peer}}) <= 1);
176 print "dhcpStatements: failover peer $fo_peer { ",
177 join('; ', @
{$failover{$fo_peer}}), "; }\n";
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'}))
188 print "objectClass: dhcpOptions\n";
190 print "dhcpPrimaryDN: $hostdn\n";
191 if(grep(/FaIlOvEr/i, @use) and ($second ne ''))
193 print "dhcpSecondaryDN: $second\n";
196 elsif ($curentry{'type'} eq 'subnet')
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'}))
204 print "objectClass: dhcpOptions\n";
207 print "dhcpNetMask: " . $curentry{'netmask'} . "\n";
208 if (defined ($curentry{'ranges'}))
210 foreach $statement (@
{$curentry{'ranges'}})
212 print "dhcpRange: $statement\n";
216 elsif ($curentry{'type'} eq 'shared-network')
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'}))
224 print "objectClass: dhcpOptions\n";
227 elsif ($curentry{'type'} eq 'group')
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'}))
235 print "objectClass: dhcpOptions\n";
238 elsif ($curentry{'type'} eq 'host')
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'}))
246 print "objectClass: dhcpOptions\n";
249 if (defined ($curentry{'hwaddress'}))
251 $curentry{'hwaddress'} =~ y/[A-Z]/[a-z]/;
252 print "dhcpHWAddress: " . $curentry{'hwaddress'} . "\n";
255 elsif ($curentry{'type'} eq 'pool')
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'}))
263 print "objectClass: dhcpOptions\n";
266 if (defined ($curentry{'ranges'}))
268 foreach $statement (@
{$curentry{'ranges'}})
270 print "dhcpRange: $statement\n";
274 elsif ($curentry{'type'} eq 'class')
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'}))
282 print "objectClass: dhcpOptions\n";
285 elsif ($curentry{'type'} eq 'subclass')
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'}))
293 print "objectClass: dhcpOptions\n";
295 print "dhcpClassData: " . $curentry{'class'} . "\n";
298 if (defined ($curentry{'statements'}))
300 foreach $statement (@
{$curentry{'statements'}})
302 print "dhcpStatements: $statement\n";
306 if (defined ($curentry{'options'}))
308 foreach $statement (@
{$curentry{'options'}})
310 print "dhcpOption: $statement\n";
321 local ($netmask) = @_;
324 if ((($a, $b, $c, $d) = $netmask =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/) != 4)
329 $num = (($a & 0xff) << 24) |
330 (($b & 0xff) << 16) |
334 for ($i=1; $i<=32 && $num & (1 << (32 - $i)); $i++)
345 local ($ip, $tmp, $netmask);
347 print_entry
() if %curentry;
349 $ip = next_token
(0);
350 parse_error
() if !defined ($ip);
352 $tmp = next_token
(1);
353 parse_error
() if !defined ($tmp);
354 parse_error
() if !($tmp eq 'netmask');
356 $tmp = next_token
(0);
357 parse_error
() if !defined ($tmp);
358 $netmask = parse_netmask
($tmp);
360 $tmp = next_token
(0);
361 parse_error
() if !defined ($tmp);
362 parse_error
() if !($tmp eq '{');
364 add_dn_to_stack
("cn=$ip");
365 $curentry{'type'} = 'subnet';
366 $curentry{'ip'} = $ip;
367 $curentry{'netmask'} = $netmask;
369 $curcounter{$ip} = { pool
=> 0, group
=> 0 };
373 sub parse_shared_network
375 local ($descr, $tmp);
377 print_entry
() if %curentry;
379 $descr = next_token
(0);
380 parse_error
() if !defined ($descr);
382 $tmp = next_token
(0);
383 parse_error
() if !defined ($tmp);
384 parse_error
() if !($tmp eq '{');
386 add_dn_to_stack
("cn=$descr");
387 $curentry{'type'} = 'shared-network';
388 $curentry{'descr'} = $descr;
394 local ($descr, $tmp);
396 print_entry
() if %curentry;
398 $host = next_token
(0);
399 parse_error
() if !defined ($host);
401 $tmp = next_token
(0);
402 parse_error
() if !defined ($tmp);
403 parse_error
() if !($tmp eq '{');
405 add_dn_to_stack
("cn=$host");
406 $curentry{'type'} = 'host';
407 $curentry{'host'} = $host;
413 local ($descr, $tmp);
415 print_entry
() if %curentry;
417 $tmp = next_token
(0);
418 parse_error
() if !defined ($tmp);
419 parse_error
() if !($tmp eq '{');
422 if(exists($curcounter{$cursubnet})) {
423 $idx = ++$curcounter{$cursubnet}->{'group'};
425 $idx = ++$curcounter{''}->{'group'};
428 add_dn_to_stack
("cn=group".$idx);
429 $curentry{'type'} = 'group';
430 $curentry{'idx'} = $idx;
436 local ($descr, $tmp);
438 print_entry
() if %curentry;
440 $tmp = next_token
(0);
441 parse_error
() if !defined ($tmp);
442 parse_error
() if !($tmp eq '{');
445 if(exists($curcounter{$cursubnet})) {
446 $idx = ++$curcounter{$cursubnet}->{'pool'};
448 $idx = ++$curcounter{''}->{'pool'};
451 add_dn_to_stack
("cn=pool".$idx);
452 $curentry{'type'} = 'pool';
453 $curentry{'idx'} = $idx;
459 local ($descr, $tmp);
461 print_entry
() if %curentry;
463 $class = next_token
(0);
464 parse_error
() if !defined ($class);
466 $tmp = next_token
(0);
467 parse_error
() if !defined ($tmp);
468 parse_error
() if !($tmp eq '{');
471 add_dn_to_stack
("cn=$class");
472 $curentry{'type'} = 'class';
473 $curentry{'class'} = $class;
479 local ($descr, $tmp);
481 print_entry
() if %curentry;
483 $class = next_token
(0);
484 parse_error
() if !defined ($class);
486 $subclass = next_token
(0);
487 parse_error
() if !defined ($subclass);
489 $tmp = next_token
(0);
490 parse_error
() if !defined ($tmp);
491 parse_error
() if !($tmp eq '{');
493 add_dn_to_stack
("cn=$subclass");
494 $curentry{'type'} = 'subclass';
495 $curentry{'class'} = $class;
496 $curentry{'subclass'} = $subclass;
502 local ($type, $hw, $tmp);
504 $type = next_token
(1);
505 parse_error
() if !defined ($type);
507 $hw = next_token
(1);
508 parse_error
() if !defined ($hw);
511 $curentry{'hwaddress'} = "$type $hw";
519 $str = remaining_line
();
524 push (@
{$curentry{'ranges'}}, $str);
531 local ($token) = shift;
534 if ($token eq 'option')
536 $str = remaining_line
();
537 push (@
{$curentry{'options'}}, $str);
539 elsif($token eq 'failover')
541 $str = remaining_line
(1); # take care on block
544 my ($peername, @statements);
546 parse_error
() if($str !~ /^\s*peer\s+(.+?)\s+[{]\s*$/);
547 parse_error
() if(($peername = $1) !~ /^\"?[^\"]+\"?$/);
550 # failover config block found:
551 # e.g. 'failover peer "some-name" {'
553 if(not grep(/FaIlOvEr/i, @use))
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";
560 until($str =~ /[}]/ or $str eq "")
562 $str = remaining_line
(1);
563 # collect all statements, except ending '}'
564 push(@statements, $str) if($str !~ /[}]/);
566 $failover{$peername} = [@statements];
571 # pool reference to failover config is fine
572 # e.g. 'failover peer "some-name";'
574 if(not grep(/FaIlOvEr/i, @use))
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";
582 push (@
{$curentry{'statements'}}, $token. " " . $str);
586 elsif($token eq 'zone')
589 while($str !~ /}$/) {
590 $str .= ' ' . next_token
(0);
592 push (@
{$curentry{'statements'}}, $str);
594 elsif($token =~ /^(authoritative)[;]*$/)
596 push (@
{$curentry{'statements'}}, $1);
600 $str = $token . " " . remaining_line
();
601 push (@
{$curentry{'statements'}}, $str);
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,
614 'h|help|usage' => sub { usage
(0); },
617 unless($server =~ /^\w+/)
619 usage
(1, "invalid server name '$server'");
621 unless($basedn =~ /^\w+=[^,]+/)
623 usage
(1, "invalid base dn '$basedn'");
626 if($dhcpdn =~ /^cn=([^,]+)/i)
630 $second = '' if not defined $second;
631 unless($second eq '' or $second =~ /^cn=[^,]+\s*,\s*\w+=[^,]+/i)
633 if($second =~ /^cn=[^,]+$/i)
635 # relative DN 'cn=name'
636 $second = "$second, $basedn";
638 elsif($second =~ /^\w+/)
640 # assume hostname only
641 $second = "cn=$second, $basedn";
645 usage
(1, "invalid secondary '$second'")
649 usage
(1) unless($ok);
651 if($i_conf ne "" and -f
$i_conf)
653 if(not open(STDIN
, '<', $i_conf))
655 print STDERR
"Error: can't open conf file '$i_conf': $!\n";
663 print STDERR
"Error: output ldif name '$o_ldif' already exists!\n";
666 if(not open(STDOUT
, '>', $o_ldif))
668 print STDERR
"Error: can't open ldif file '$o_ldif': $!\n";
674 print STDERR
"Creating LDAP Configuration with the following options:\n";
675 print STDERR
"\tBase DN: $basedn\n";
676 print STDERR
"\tDHCP DN: $dhcpdn\n";
677 print STDERR
"\tServer DN: cn=$server, $basedn\n";
678 print STDERR
"\tSecondary DN: $second\n"
679 if(grep(/FaIlOvEr/i, @use) and $second ne '');
683 my $token_number = 0;
687 my %curcounter = ( '' => { pool
=> 0, group
=> 0 } );
689 $current_dn = "$dhcpdn";
690 $curentry{'descr'} = $dhcpcn;
694 while (($token = next_token
(1)))
698 print_entry
() if %curentry;
699 if($current_dn =~ /.+?,\s*${dhcpdn}$/) {
700 # don't go below dhcpdn ...
701 remove_dn_from_stack
();
704 elsif ($token eq 'subnet')
709 elsif ($token eq 'shared-network')
711 parse_shared_network
();
714 elsif ($token eq 'class')
719 elsif ($token eq 'subclass')
724 elsif ($token eq 'pool')
729 elsif ($token eq 'group')
734 elsif ($token eq 'host')
739 elsif ($token eq 'hardware')
744 elsif ($token eq 'range')
751 parse_statement
($token);
756 close(STDIN
) if($i_conf);
757 close(STDOUT
) if($o_ldif);
759 print STDERR
"Done.\n";