r25127: Add ol-schema-migrate.pl to the repo.
authorSimo Sorce <idra@samba.org>
Thu, 13 Sep 2007 12:51:00 +0000 (12:51 +0000)
committerGerald (Jerry) Carter <jerry@samba.org>
Wed, 10 Oct 2007 17:30:42 +0000 (12:30 -0500)
This script is useful for migrating OpenLDAP schema files to FDS/RHDS
lidf schema files.

License kindly updated to GPLv3+ at our request.

Simo.
(This used to be commit ab7770b34b3202a5836cfa098187eeed1bd16be3)

examples/LDAP/README
examples/LDAP/ol-schema-migrate.pl [new file with mode: 0755]

index aa3207fd1f90ce84d02569d7a85a0ec3918e61d2..9fbabc977cfac05b08696b01ea5e3ca864a4feb2 100644 (file)
@@ -17,22 +17,22 @@ and add an include for it in the /etc/openldap/slapd.conf file.
 Note that samba.schema relies upon the uid and uidNumber attributes
 from the RFC2307 schema (i.e. nis.schema)
 
 Note that samba.schema relies upon the uid and uidNumber attributes
 from the RFC2307 schema (i.e. nis.schema)
 
-If you choose to import /etc/passwd, nis, or nisplus tables 
-into ldap, you can use migration tools provided by PADL Software 
-which are located at 
+If you choose to import /etc/passwd, nis, or nisplus tables
+into ldap, you can use migration tools provided by PADL Software
+which are located at
 
        http://www.padl.com/tools.html
 
 It is not a requirement that a user's /etc/passwd account
 is stored in LDAP for the samba.schema file to work (although
 
        http://www.padl.com/tools.html
 
 It is not a requirement that a user's /etc/passwd account
 is stored in LDAP for the samba.schema file to work (although
-the whole point of storing smbpasswd in LDAP is to have a 
+the whole point of storing smbpasswd in LDAP is to have a
 single location for user accounts, right?)
 
 The padl tools will leave you with LDIF files which you can import
 into OpenLDAP.  Before you can import them, you need to include
 nis.schema and cosine.schema in your slapd.conf file.
 
 single location for user accounts, right?)
 
 The padl tools will leave you with LDIF files which you can import
 into OpenLDAP.  Before you can import them, you need to include
 nis.schema and cosine.schema in your slapd.conf file.
 
-You must restart the LDAP server for these new included schema files 
+You must restart the LDAP server for these new included schema files
 to become active.
 
 SunOne/Netscape DS
 to become active.
 
 SunOne/Netscape DS
@@ -48,15 +48,25 @@ Novell eDirectory
 The schema file has not been updated for the sambaSamAccount
 objectclass.
 
 The schema file has not been updated for the sambaSamAccount
 objectclass.
 
+Fedora Directory Server /
+RedHat Directory Server /
+Netscape Directory Server
+-------------------------
+
+An *updated* schema file has been provided, plus a very useful script from
+Mike Jackson and Alyseo is available.
+ol-schema-migrate.pl can be used to migrate OpenLDAP schema files to FDS
+schema ldif files, it can also be used to validate the schema files to
+make sure no duplicate OIDs or malformed entries are found.
 
 smbldap-tools/
 --------------
 
 
 smbldap-tools/
 --------------
 
-The smbldap-tools have been removed from the samba svn 
-tree.  The latest version will continue to be included 
+The smbldap-tools have been removed from the samba svn
+tree.  The latest version will continue to be included
 in Samba releases.
 
 in Samba releases.
 
-The smbldap-tools package can be downloaded individually from 
+The smbldap-tools package can be downloaded individually from
 http://samba.idealx.org/dist/
 
 !==
 http://samba.idealx.org/dist/
 
 !==
diff --git a/examples/LDAP/ol-schema-migrate.pl b/examples/LDAP/ol-schema-migrate.pl
new file mode 100755 (executable)
index 0000000..12392cb
--- /dev/null
@@ -0,0 +1,384 @@
+#!/usr/bin/perl -w
+#
+# Convert OpenLDAP schema files into Fedora DS format with RFC2252 compliant printing
+#
+# First Release : Mike Jackson <mj@sci.fi> 14 June 2005
+#    http://www.netauth.com/~jacksonm/ldap/ol-schema-migrate.pl
+#    Professional LDAP consulting for large and small projects
+#
+# - 6 Dec 2005
+# - objectclass element ordering
+#
+# Second Release : Alyseo <info@alyseo.com> 05 Februrary 2006
+#    Francois Billard <francois@alyseo.com>
+#    Yacine Kheddache <yacine@alyseo.com>
+#    http://www.alyseo.com/
+#
+# - 05 Februrary 2006
+#  - parsing improvement to accept non-RFC compliant schemas (like ISPMAN)
+#  - adding RFC element : Usage, No-user-modification, collective keywords
+# - 08 Februrary 2006
+#  - adding help & usage
+#  - now this script can also beautify your schemas: "-b"
+#  - count attributes and objects class: "-c"
+#  - display items that can not be converted (empty OID...): "-d"
+# - 15 February 2006
+#  - adding workaround for Fedora DS bug 181465:
+#    https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=181465
+#  - adding duplicated OID check: "-d"
+#    Useful to manually correct nasty schemas like:
+#    https://sourceforge.net/tracker/?func=detail&atid=108390&aid=1429276&group_id=8390
+# - 13 September 2007
+#    Based on Samba Team GPL Compliance Officer request, license has been updated from 
+#    GPL to GPLv3+
+#
+# - Fedora DS bug you need to correct by hand :
+#    https://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=179956
+#
+# GPLv3+ license
+#
+
+my $optionCount = 0;
+my $optionPrint = 0;
+my $optionBadEntries = 0;
+my $optionHelp = 0;
+my $filename = "" ;
+
+foreach (@ARGV) {
+  $optionHelp = 1 if ( /^-h$/);
+  $optionCount = 1 if ( /^-c$/);
+  $optionPrint = 1 if ( /^-b$/);
+  $optionBadEntries = 1 if ( /^-d$/);
+  $filename = $_ if ( ! /^-b$/ && ! /^-c$/ && ! /^-d$/);
+}
+
+die "Usage : ol-schema-migrate-v2.pl [ -c ] [ -b ] [ -d ] schema\n" . 
+    "  -c\tcount attribute and object class\n" . 
+    "  -b\tconvert and beautify your schema\n" .
+    "  -d\tdisplay unrecognized elements, find empty and duplicated OID\n" .
+    "  -h\tthis help\n" if ($filename eq "" || ($optionHelp || (!$optionCount && !$optionPrint && !$optionBadEntries)));
+
+if($optionCount) {
+  print "Schema verification counters:\n";
+  my $ldapdata = &getSourceFile($filename);
+  print "".(defined($ldapdata->{attributes}) ? @{$ldapdata->{attributes}} : 0) . " attributes\n";
+  print "".(defined($ldapdata->{objectclass}) ?  @{$ldapdata->{objectclass}} : 0) . " object classes\n\n" 
+}
+
+if($optionPrint) {
+  my $ldapdata = &getSourceFile($filename);
+  &printit($ldapdata);
+}
+
+if($optionBadEntries) {
+  print "Display unrecognized entries:\n";
+  my $ldapdata = &getSourceFile($filename);
+  my $errorsAttr = 0;
+  my $errorsObjc = 0;
+  my $errorsDup  = 0;
+  my $emptyOid = 0;
+  my %dup;
+  
+  foreach (@{$ldapdata->{attributes}}) {
+    my $attr = $_;
+    
+    push @{$dup{$attr->{OID}}{attr}}, {NAME => $attr->{NAME}, LINENUMBER => $attr->{LINENUMBER}};
+    
+    $attr->{DATA} =~ s/\n/ /g;
+    $attr->{DATA} =~ s/\r//g;
+    $attr->{DATA} =~ s/attribute[t|T]ypes?:?\s*\(//;
+    $attr->{DATA} =~ s/\Q$attr->{OID}//                 if(defined $attr->{OID});
+    $attr->{DATA} =~ s/NAME\s*\Q$attr->{NAME}//         if(defined $attr->{NAME});
+    $attr->{DATA} =~ s/DESC\s*'\Q$attr->{DESC}'//       if(defined $attr->{DESC});
+    $attr->{DATA} =~ s/$attr->{OBSOLETE}//              if(defined $attr->{OBSOLETE});
+    $attr->{DATA} =~ s/SUP\s*\Q$attr->{SUP}//           if(defined $attr->{SUP});
+    $attr->{DATA} =~ s/EQUALITY\s*\Q$attr->{EQUALITY}// if(defined $attr->{EQUALITY});
+    $attr->{DATA} =~ s/ORDERING\s*\Q$attr->{ORDERING}// if(defined $attr->{ORDERING});
+    $attr->{DATA} =~ s/SUBSTR\s*\Q$attr->{SUBSTR}//     if(defined $attr->{SUBSTR});
+    $attr->{DATA} =~ s/SYNTAX\s*\Q$attr->{SYNTAX}//     if(defined $attr->{SYNTAX});
+    $attr->{DATA} =~ s/SINGLE-VALUE//                   if(defined $attr->{SINGLEVALUE});
+    $attr->{DATA} =~ s/NO-USER-MODIFICATION//           if(defined $attr->{NOUSERMOD});
+    $attr->{DATA} =~ s/COLLECTIVE//                     if(defined $attr->{COLLECTIVE});
+    $attr->{DATA} =~ s/USAGE\s*\Q$attr->{USAGE}//       if(defined $attr->{USAGE});
+    $attr->{DATA} =~ s/\)\s$//;
+    $attr->{DATA} =~ s/^\s+(\S)/\n$1/  ;
+    $attr->{DATA} =~ s/(\S)\s+$/$1\n/;
+    do {
+      $errorsAttr ++;      
+      do {  $emptyOid ++; 
+            print "Warning : no OID for attributes element at line $attr->{LINENUMBER} \n";
+      } if( !defined($attr->{OID}));
+      print "### Unknow element embedded in ATTRIBUTE at line $attr->{LINENUMBER} :\n$attr->{DATA}\n"
+    } if($attr->{DATA} =~ /\w/);
+  }
+
+  foreach (@{$ldapdata->{objectclass}}) {
+    my $objc = $_;
+    push @{$dup{$objc->{OID}}{objc}} , {NAME => $objc->{NAME}, LINENUMBER => $objc->{LINENUMBER}};
+    $objc->{DATA} =~ s/\n/ /g;
+    $objc->{DATA} =~ s/\r//g;
+    $objc->{DATA} =~ s/^object[c|C]lasse?s?:?\s*\(?//;
+    $objc->{DATA} =~ s/\Q$objc->{OID}//                       if(defined $objc->{OID});
+    $objc->{DATA} =~ s/NAME\s*\Q$objc->{NAME}\E//             if(defined $objc->{NAME});
+    $objc->{DATA} =~ s/DESC\s*'\Q$objc->{DESC}\E'//           if(defined $objc->{DESC});
+    $objc->{DATA} =~ s/OBSOLETE//                             if(defined $objc->{OBSOLETE});
+    $objc->{DATA} =~ s/SUP\s*\Q$objc->{SUP}//                 if(defined $objc->{SUP});
+    $objc->{DATA} =~ s/\Q$objc->{TYPE}//                      if(defined $objc->{TYPE});
+    $objc->{DATA} =~ s/MUST\s*\Q$objc->{MUST}\E\s*//          if(defined $objc->{MUST});
+    $objc->{DATA} =~ s/MUST\s*\(?\s*\Q$objc->{MUST}\E\s*\)?// if(defined $objc->{MUST});
+    $objc->{DATA} =~ s/MAY\s*\Q$objc->{MAY}\E//               if(defined $objc->{MAY});
+    $objc->{DATA} =~ s/\)\s$//;
+    $objc->{DATA} =~ s/^\s+(\S)/\n$1/  ;
+    $objc->{DATA} =~ s/(\S)\s+$/$1\n/;
+
+    do {
+      print "#" x 80 ."\n";
+      $errorsObjc ++;
+      do { $emptyOid++ ;
+           print "Warning : no OID for object class element at line $objc->{LINENUMBER} \n";
+      } if( $objc->{OID} eq "");
+      print "### Unknow element embedded in OBJECT CLASS at line $objc->{LINENUMBER} :\n$objc->{DATA}\n"
+    } if($objc->{DATA} =~ /\w/);
+  }
+
+  my $nbDup = 0;
+  foreach (keys %dup) {
+    my $sumOid = 0;
+    $sumOid += @{$dup{$_}{attr}} if(defined (@{$dup{$_}{attr}}));
+    $sumOid += @{$dup{$_}{objc}} if(defined (@{$dup{$_}{objc}}));
+    if( $sumOid > 1 && $_ ne "") {
+      $nbDup ++;
+      print "#" x 80 ."\n";
+      print "Duplicate OID founds : $_\n";
+      foreach (@{$dup{$_}{attr}}) {
+        
+        print "Attribute : $_->{NAME} (line : $_->{LINENUMBER})\n";
+      }
+      foreach (@{$dup{$_}{objc}}) {
+        print "Object class : $_->{NAME} (line : $_->{LINENUMBER})\n";
+      }
+      
+    }
+  }
+
+  print "\n$errorsAttr errors detected in ATTRIBUTES list\n";
+  print "$errorsObjc errors detected in OBJECT CLASS list\n";
+  print "$nbDup duplicate OID founds\n";
+  print "$emptyOid empty OID fields founds\n\n";
+
+}
+
+
+sub printit {
+  my $ldapdata = shift;
+  &printSeparator;
+  print "dn: cn=schema\n";
+  &printSeparator;
+
+  # print elements in RFC2252 order
+
+  foreach (@{$ldapdata->{attributes}}) {
+    my $attr = $_;
+    print "attributeTypes: (\n";
+    print "  $attr->{OID}\n";
+    print "  NAME $attr->{NAME}\n";
+    print "  DESC '$attr->{DESC}'\n"         if(defined $attr->{DESC});
+    print "  OBSOLETE\n"                     if(defined $attr->{OBSOLETE});
+    print "  SUP $attr->{SUP}\n"             if(defined $attr->{SUP});
+    print "  EQUALITY $attr->{EQUALITY}\n"   if(defined $attr->{EQUALITY});
+    print "  ORDERING $attr->{ORDERING}\n"   if(defined $attr->{ORDERING});
+    print "  SUBSTR $attr->{SUBSTR}\n"       if(defined $attr->{SUBSTR});
+    print "  SYNTAX $attr->{SYNTAX}\n"       if(defined $attr->{SYNTAX});
+    print "  SINGLE-VALUE\n"                 if(defined $attr->{SINGLEVALUE});
+    print "  NO-USER-MODIFICATION\n"         if(defined $attr->{NOUSERMOD});
+    print "  COLLECTIVE\n"                   if(defined $attr->{COLLECTIVE});
+    print "  USAGE $attr->{USAGE}\n"         if(defined $attr->{USAGE});
+    print "  )\n";
+    &printSeparator;
+  }
+
+  foreach (@{$ldapdata->{objectclass}}) {
+    my $objc = $_;
+    # next 3 lines : Fedora DS space sensitive bug workaround 
+    $objc->{SUP}         =~ s/^\(\s*(.*?)\s*\)$/\( $1 \)/  if (defined $objc->{SUP});  
+    $objc->{MUST}        =~ s/^\(\s*(.*?)\s*\)$/\( $1 \)/  if (defined $objc->{MUST}); 
+    $objc->{MAY}         =~ s/^\(\s*(.*?)\s*\)$/\( $1 \)/  if (defined $objc->{MAY}); 
+
+    print "objectClasses: (\n";
+    print "  $objc->{OID}\n";
+    print "  NAME $objc->{NAME}\n";
+    print "  DESC '$objc->{DESC}'\n"  if(defined $objc->{DESC});
+    print "  OBSOLETE\n"              if(defined $objc->{OBSOLETE});
+    print "  SUP $objc->{SUP}\n"      if(defined $objc->{SUP});
+    print "  $objc->{TYPE}\n"         if(defined $objc->{TYPE});  
+    print "  MUST $objc->{MUST}\n"    if(defined $objc->{MUST});
+    print "  MAY $objc->{MAY}\n"      if(defined $objc->{MAY});
+    print "  )\n";
+    &printSeparator;
+  }
+}
+
+sub printSeparator {
+  print "#\n";
+  print "#" x 80 . "\n";
+  print "#\n";
+}  
+
+sub getSourceFile {
+  my @data = &getFile(shift);
+  my %result;
+  my $result = \%result;
+  my @allattrs;
+  my @allattrsLineNumber;
+  my @allobjc;
+  my @allobjcLineNumber;
+  my $at = 0;
+  my $oc = 0;
+  my $at_string;
+  my $oc_string;
+  my $idx = 0;
+  my $beginParenthesis = 0;
+  my $endParenthesis = 0;
+  my $lineNumber = 0;  
+  for(@data) {
+    $lineNumber++;
+    next if (/^\s*\#/); # skip comments
+
+    if($at) {
+      s/ +/ /;                    # remove embedded tabs
+      s/\t/ /;                    # remove multiple spaces after the $ sign
+
+       $at_string .= $_;
+      $beginParenthesis = 0;      # Use best matching elements
+      $endParenthesis = 0;
+      for(my $i=0;$ i < length($at_string); $i++) {
+        $beginParenthesis++ if(substr ($at_string,$i,1) eq "(");
+        $endParenthesis++ if(substr ($at_string,$i,1) eq ")");
+      }
+      if($beginParenthesis == $endParenthesis) {
+        push @allattrs, $at_string;
+        $at = 0;
+        $at_string = "";
+        $endParenthesis = 0;
+        $beginParenthesis = 0;
+      }
+    }
+
+    if (/^attribute[t|T]ype/) {
+      my $line = $_;
+       push @allattrsLineNumber, $lineNumber;      # keep starting line number
+      for(my $i=0;$ i < length($line); $i++) {
+        $beginParenthesis++ if(substr ($line, $i, 1) eq "(");
+        $endParenthesis++ if(substr ($line, $i, 1) eq ")");
+      }
+      if($beginParenthesis == $endParenthesis && $beginParenthesis != 0) {
+        push @allattrs, $line;
+        $endParenthesis = 0;
+        $beginParenthesis = 0;
+      } else {
+        $at_string = $line;
+        $at = 1;
+      }
+    }
+
+    #####################################
+
+    if($oc) {
+      s/ +/ /;
+      s/\t/ /;
+      
+      $oc_string .= $_;
+      $endParenthesis = 0;          # best methode to accept an elements : 
+      $beginParenthesis = 0;        # left parenthesis sum == right parenthesis sum, so we are sure to 
+      for(my $i=0;$ i < length($oc_string); $i++) {      # have an element.
+        $beginParenthesis++ if(substr ($oc_string, $i, 1) eq "(");
+        $endParenthesis++ if(substr ($oc_string, $i, 1) eq ")");
+      }
+      if($beginParenthesis == $endParenthesis) {
+        push @allobjc, $oc_string;
+        $oc = 0;
+        $oc_string = "";
+        $endParenthesis = 0;
+        $beginParenthesis = 0;
+      }
+    }
+
+    if (/^object[c|C]lass/) {
+      my $line = $_;
+       push @allobjcLineNumber, $lineNumber;    # keep starting line number
+      for(my $i=0;$ i < length($line); $i++) {
+        $beginParenthesis++ if(substr ($line, $i, 1) eq "(");
+        $endParenthesis++ if(substr ($line, $i, 1) eq ")");
+      }
+      if($beginParenthesis == $endParenthesis && $beginParenthesis != 0) {
+        push @allobjc, $line;
+        $endParenthesis = 0;
+        $beginParenthesis = 0;
+      } else {
+        $oc_string = $line;
+        $oc = 1;
+      }
+    }
+  }
+
+  # Parsing attribute elements
+  
+  for(@allattrs) {
+    s/\n/ /g;
+    s/\r//g;
+    s/ +/ /g;
+    s/\t/ /g;
+    $result->{attributes}->[$idx]->{DATA}        = $_              if($optionBadEntries);     # keep original data
+    $result->{attributes}->[$idx]->{LINENUMBER}  = $allattrsLineNumber[$idx];
+    $result->{attributes}->[$idx]->{OID}         = $1              if (m/^attribute[t|T]ypes?:?\s*\(?\s*([\.\d]*?)\s+/);
+    $result->{attributes}->[$idx]->{NAME}        = $1              if (m/NAME\s+('.*?')\s*/ || m/NAME\s+(\(.*?\))/);
+    $result->{attributes}->[$idx]->{DESC}        = $1              if (m/DESC\s+'(.*?)'\s*/);
+    $result->{attributes}->[$idx]->{OBSOLETE}    = "OBSOLETE"      if (m/OBSOLETE/);
+    $result->{attributes}->[$idx]->{SUP}         = $1              if (m/SUP\s+(.*?)\s/);
+    $result->{attributes}->[$idx]->{EQUALITY}    = $1              if (m/EQUALITY\s+(.*?)\s/);
+    $result->{attributes}->[$idx]->{ORDERING}    = $1              if (m/ORDERING\s+(.*?)\s/);
+    $result->{attributes}->[$idx]->{SUBSTR}      = $1              if (m/SUBSTR\s+(.*?)\s/);
+    $result->{attributes}->[$idx]->{SYNTAX}      = $1              if (m/SYNTAX\s+(.*?)(\s|\))/);
+    $result->{attributes}->[$idx]->{SINGLEVALUE} = "SINGLE-VALUE"  if (m/SINGLE-VALUE/);
+    $result->{attributes}->[$idx]->{COLLECTIVE}  = "COLLECTIVE"    if (m/COLLECTIVE/);
+    $result->{attributes}->[$idx]->{USAGE}       = $1              if (m/USAGE\s+(.*?)\s/);
+    $result->{attributes}->[$idx]->{NOUSERMOD}   = "NO-USER-MODIFICATION"    if (m/NO-USER-MODIFICATION/);
+    $idx ++;
+  }
+  
+  $idx = 0;
+  
+  # Parsing object class elements
+  
+  for(@allobjc) {
+    s/\n/ /g;
+    s/\r//g;
+    s/ +/ /g;
+    s/\t/ /g;
+    $result->{objectclass}->[$idx]->{DATA}        = $_          if($optionBadEntries);     # keep original data
+    $result->{objectclass}->[$idx]->{LINENUMBER}  = $allobjcLineNumber[$idx];
+    $result->{objectclass}->[$idx]->{OID}         = $1          if (m/^object[c|C]lasse?s?:?\s*\(?\s*([\.\d]*?)\s+/);
+    $result->{objectclass}->[$idx]->{NAME}        = $1          if (m/NAME\s+('.*?')\s*/ || m/NAME\s+(\(.*?\))/);
+    $result->{objectclass}->[$idx]->{DESC}        =  $1         if (m/DESC\s+'(.*?)'\s*/);
+    $result->{objectclass}->[$idx]->{OBSOLETE}    = "OBSOLETE"  if (m/OBSOLETE/);
+    $result->{objectclass}->[$idx]->{SUP}         = $1          if (m/SUP\s+([^()]+?)\s/ || m/SUP\s+(\(.+?\))\s/);
+    $result->{objectclass}->[$idx]->{TYPE}        = $1          if (m/((?:STRUCTURAL)|(?:AUXILIARY)|(?:ABSTRACT))/);
+    $result->{objectclass}->[$idx]->{MUST}        = $1          if (m/MUST\s+(\w+)\)?/ || m/MUST\s+(\(.*?\))(\s|\))/s);
+    $result->{objectclass}->[$idx]->{MAY}         = $1          if (m/MAY\s+(\w+)\)?/ || m/MAY\s+(\(.*?\))(\s|\))/s);
+
+    $idx++;
+  }
+  
+  return $result;
+}
+
+sub getFile {
+  my @data;
+  my $file = shift;
+  die "File not found : $file\n" if(! -e $file);
+  open FH, $file;
+  @data = <FH>;
+  close FH;
+  @data;
+}
+