xref: /minix3/external/bsd/dhcp/dist/contrib/ldap/dhcpd-conf-to-ldap (revision 83ee113ee0d94f3844d44065af2311604e9a30ad)
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