dhcpd-conf-to-ldap revision 1.1 1 #!/usr/bin/perl -w
2
3 # Brian Masney <masneyb (at] 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
25 use Net::Domain qw(hostname hostfqdn hostdomain);
26 use Getopt::Long;
27
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)
38
39 sub 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__;
46 usage:
47 $0 [options] < dhcpd.conf > dhcpd.ldif
48
49 options:
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
68 sub 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
115 sub 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
134 sub
135 add_dn_to_stack
136 {
137 local ($dn) = @_;
138
139 $current_dn = "$dn, $current_dn";
140 $curentry{'current_dn'} = $current_dn;
141 }
142
143
144 sub
145 remove_dn_from_stack
146 {
147 $current_dn =~ s/^.*?,\s*//;
148 }
149
150
151 sub
152 parse_error
153 {
154 print "Parse error on line number $line_number at token number $token_number\n";
155 exit (1);
156 }
157
158 sub
159 new_entry
160 {
161 if (%curentry) {
162 $curentry{'current_dn'} = $current_dn;
163 push(@entrystack, {%curentry});
164 undef(%curentry);
165 }
166 }
167
168 sub
169 pop_entry
170 {
171 if (%curentry) {
172 push(@outputlist, {%curentry});
173 }
174 $rentry = pop(@entrystack);
175 %curentry = %$rentry if $rentry;
176 }
177
178
179 sub
180 print_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
340 sub 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
364 sub 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
394 sub 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
413 sub 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
432 sub 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
455 sub 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
478 sub 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
498 sub 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
530 sub 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
545 sub 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
559 sub 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
636 my $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
647 unless($server =~ /^\w+/)
648 {
649 usage(1, "invalid server name '$server'");
650 }
651 unless($basedn =~ /^\w+=[^,]+/)
652 {
653 usage(1, "invalid base dn '$basedn'");
654 }
655
656 if($dhcpdn =~ /^cn=([^,]+)/i)
657 {
658 $dhcpcn = "$1";
659 }
660 $second = '' if not defined $second;
661 unless($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
679 usage(1) unless($ok);
680
681 if($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 }
689 if($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
704 print STDERR "Creating LDAP Configuration with the following options:\n";
705 print STDERR "\tBase DN: $basedn\n";
706 print STDERR "\tDHCP DN: $dhcpdn\n";
707 print STDERR "\tServer DN: cn=$server, $basedn\n";
708 print STDERR "\tSecondary DN: $second\n"
709 if(grep(/FaIlOvEr/i, @use) and $second ne '');
710 print STDERR "\n";
711
712 my $token;
713 my $token_number = 0;
714 my $line_number = 0;
715 my $cursubnet = '';
716 my %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
724 while (($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
786 pop_entry ();
787
788 while ($#outputlist >= 0) {
789 $rentry = pop(@outputlist);
790 if ($rentry) {
791 %curentry = %$rentry;
792 print_entry ();
793 }
794 }
795
796 close(STDIN) if($i_conf);
797 close(STDOUT) if($o_ldif);
798
799 print STDERR "Done.\n";
800
801