Add the sync script to the core source package.
authorSteinar H. Gunderson <sesse@samfundet.no>
Wed, 15 May 2013 19:05:13 +0000 (21:05 +0200)
committerSteinar H. Gunderson <sesse@samfundet.no>
Wed, 15 May 2013 19:05:13 +0000 (21:05 +0200)
itkacl-2.0/Makefile
itkacl-2.0/config.pm [new file with mode: 0644]
itkacl-2.0/debian/control
itkacl-2.0/debian/itkacl-sync.dirs [new file with mode: 0644]
itkacl-2.0/debian/itkacl-sync.install [new file with mode: 0644]
itkacl-2.0/sync-itkacl.pl [new file with mode: 0755]

index f7ccafdae3925881b6de9eab139cf383f2873678..05801b6907e8bf0905b3558cdf97902cf2e5c7a1 100644 (file)
@@ -35,5 +35,9 @@ install:
        cp itkacl.h $(DESTDIR)$(PREFIX)/include/
        mkdir -p $(DESTDIR)$(PREFIX)/share/itkacl/
        cp itkacl.sql $(DESTDIR)$(PREFIX)/share/itkacl/
-       
+       mkdir -p $(DESTDIR)$(PREFIX)/bin/
+       cp sync-itkacl.pl $(DESTDIR)$(PREFIX)/bin/sync-itkacl
+       mkdir -p $(DESTDIR)/etc/itkacl
+       cp config.pm $(DESTDIR)/etc/itkacl
+
 .PHONY: clean install
diff --git a/itkacl-2.0/config.pm b/itkacl-2.0/config.pm
new file mode 100644 (file)
index 0000000..c5e9742
--- /dev/null
@@ -0,0 +1,37 @@
+#! /usr/bin/perl
+
+#
+# ITKACL sync: Default configuration file.
+# Set your local configuration in config.local.pm instead of editing this file.
+#
+
+use strict;
+use warnings;
+
+package itkaclsyncconfig;
+
+our $dns_zone = 'itkacl.samfundet.no';
+our $dns_server = 'cirkus.samfundet.no';
+our $dns_key = "ITKACL_UPDATER:somekey==";
+
+our $updated_file = "/etc/itkacl/updated";
+our $last_sync_file = "/etc/itkacl/last-sync";
+
+our $db_host = 'sql.samfundet.no';
+our $db_name = 'itkacl';
+our $db_user = 'itkacl';
+our $db_pass = undef;
+
+our $minimum_uid = 500;        
+
+# Users that are included even though they are below $minimum_uid.
+our @force_include_users = (
+       'root',
+);
+
+# Local configuration overrides defaults.
+eval {
+       require 'config.local.pm';
+};
+
+1;
index 7e6e45b1e34042817a568a4b8d7034c7563371a5..7eb151677f7b27e8412598284713ae83c220cb39 100644 (file)
@@ -18,3 +18,10 @@ Architecture: any
 Depends: ${misc:Depends}, ${shlibs:Depends}
 Description: ITKACL library
  ITKACL core library.
+
+Package: itkacl-sync
+Section: admin
+Architecture: all
+Depends: perl, libdbd-pg-perl, libappconfig-perl
+Description: ITKACL sync script
+ A script to sync from the ITKACL database into DNS.
diff --git a/itkacl-2.0/debian/itkacl-sync.dirs b/itkacl-2.0/debian/itkacl-sync.dirs
new file mode 100644 (file)
index 0000000..358c5e9
--- /dev/null
@@ -0,0 +1,2 @@
+etc/itkacl
+usr/bin
diff --git a/itkacl-2.0/debian/itkacl-sync.install b/itkacl-2.0/debian/itkacl-sync.install
new file mode 100644 (file)
index 0000000..0ef5afc
--- /dev/null
@@ -0,0 +1,2 @@
+etc/itkacl/config.pm
+usr/bin/sync-itkacl
diff --git a/itkacl-2.0/sync-itkacl.pl b/itkacl-2.0/sync-itkacl.pl
new file mode 100755 (executable)
index 0000000..ccc4c56
--- /dev/null
@@ -0,0 +1,179 @@
+#! /usr/bin/perl
+use strict;
+use warnings;
+no warnings qw(once);
+use DBI;
+use AppConfig;
+use lib qw(. /etc/itkacl);
+require 'config.pm';
+
+my $conf = AppConfig->new();
+$conf->define('force!');
+$conf->getopt(\@ARGV);
+
+exit 0 if (!should_run());
+
+my $dbh = DBI->connect("dbi:Pg:" .
+       "dbname=" . $itkaclsyncconfig::db_name . ";" .
+       "host= " . $itkaclsyncconfig::db_host,
+       $itkaclsyncconfig::db_user,
+       $itkaclsyncconfig::db_pass)
+or die "Couldn't connect to database: " . DBI::errstr();
+$dbh->{RaiseError} = 1;
+
+# Fetch members of all groups.
+my %members = ();
+while (my ($name,$passwd,$gid,$members) = getgrent()) {
+       push @{$members{$name}}, ( split /\s+/, $members );
+}
+
+my %access = ();
+while (my ($name,$passwd,$uid,$gid,$quota,$comment,$gcos,$dir,$shell,$expire) = getpwent()) {
+       # No system users, except those that are explicitly included
+       next if ($uid < $itkaclsyncconfig::minimum_uid && (grep { $name eq $_ } (@itkaclsyncconfig::force_include_users)) == 0);
+
+       # No Samba machine accounts
+       next if $name =~ /\$$/;
+
+       # Initially, nobody has access.
+       $access{$name} = 0;
+
+       # The user is implicitly a member in his/her primary group.
+       my ($grnam) = getgrgid($gid);
+       if (defined($grnam)) {
+               push @{$members{$grnam}}, $name;
+       } else {
+               warn "User $name has unknown gid $gid";
+       }
+               
+       # Everybody is a member of the "<everyone>" group.
+       push @{$members{'<everyone>'}}, $name;
+}
+
+my %entries = ();
+dump_recursively($itkaclsyncconfig::dns_zone . ".", undef, \%access, \%entries);
+
+do { } while (update_zone() == 0);
+
+# Don't run again before we've got updates
+utime(time(), time(), $itkaclsyncconfig::last_sync_file);
+
+sub update_zone {
+       # Dump the zone in its current form.
+       my %current_entries = ();
+       open ZONE, "dig +nocmd +nostats +nocomments -t axfr $itkaclsyncconfig::dns_zone. \@$itkaclsyncconfig::dns_server |"
+               or die "dig failed: $!";
+       while (<ZONE>) {
+               chomp;
+               /( .*? \Q$itkaclsyncconfig::dns_zone\E \. ) \s+ 10 \s+ IN \s+ A \s+ 127\.0\.0\.1 \s* $/x or next;
+               $current_entries{$1} = 1;
+
+       }
+       close ZONE;
+
+       if ($? != 0) {
+               die "dig failed with \$\? = $?";
+       }
+       if (scalar keys %current_entries < 1) {
+               die "No entries in zone, transfer probably failed.";
+       }
+
+       # Call nsupdate to update DNS.
+       # Note: We limit ourselves to 1000 records at a time due to nsupdate limitations.
+       # If we hit the limit, we'll return 0 to signal that we should try again.
+       my $num_lines = 0;
+
+       open NSUPDATE, "| nsupdate -y $itkaclsyncconfig::dns_key"
+               or die "nsupdate failed: $!";
+       print NSUPDATE "zone $itkaclsyncconfig::dns_zone.\n";
+       print NSUPDATE "server $itkaclsyncconfig::dns_server\n";
+
+       for my $entry (keys %entries) {
+               next if (exists($current_entries{$entry}));
+               last if (++$num_lines == 1000);
+               print NSUPDATE "update add $entry 10 A 127.0.0.1\n";
+       }
+       for my $entry (keys %current_entries) {
+               next if (exists($entries{$entry}));
+               last if (++$num_lines == 1000);
+               print NSUPDATE "update delete $entry\n";
+       }
+       print NSUPDATE "send\n";
+       close NSUPDATE;
+
+       print "Made $num_lines updates.\n";
+       if ($num_lines >= 1000) {
+               print "Note: Hit limit of 1000 updates, will continue in a separate transaction.\n";
+               return 0;
+       }
+       return 1;
+}
+
+sub dump_recursively {
+       my ($path, $id, $allowed, $entries) = @_;
+
+       if (defined($id)) {
+               # Find all changes to the access tree at this level in the tree.
+               my $q = $dbh->prepare('SELECT entity_type,entity,allow FROM aclentries WHERE object=? ORDER BY entity_type ASC, allow DESC');
+               $q->execute($id);
+
+               while (my $ref = $q->fetchrow_hashref) {
+                       my $entity_type = $ref->{'entity_type'};
+                       my $entity = $ref->{'entity'};
+                       my $allow = ($ref->{'allow'} eq 'grant');
+
+                       if ($entity_type eq 'user') {
+                               if (!exists($allowed->{$entity})) {
+                                       warn "$path has an ACL entry for non-existant user $entity";
+                               } else {
+                                       $allowed->{$entity} = $allow;
+                               }
+                       } elsif ($entity_type eq 'group') {
+                               if (!exists($members{$entity})) {
+                                       warn "$path has an ACL entry for non-existant group $entity";
+                               } else {
+                                       for my $member (@{$members{$entity}}) {
+                                               $allowed->{$member} = $allow;
+                                       }
+                               }
+                       }
+               }
+       }
+
+       # Output everyone who has access to this path.
+       for my $user (keys %$allowed) {
+               next if (!$allowed->{$user});
+               $entries->{"$user.$path"} = 1;
+       }
+
+       # Now, find all children.
+       my $q;
+       if (defined($id)) {
+               $q = $dbh->prepare('SELECT id,name FROM objects WHERE parent=?');
+               $q->execute($id);
+       } else {
+               $q = $dbh->prepare('SELECT id,name FROM objects WHERE parent IS NULL');
+               $q->execute;
+       }
+
+       while (my $ref = $q->fetchrow_hashref) {
+               my %allowed_copy = %$allowed;
+               dump_recursively($ref->{'name'} . "." . $path, $ref->{'id'}, \%allowed_copy, $entries);
+       }
+}
+
+# Check if we have updates since last run (or if we're forced)
+sub should_run {
+       return 1 if ($conf->force);
+       my $last_update = (stat($itkaclsyncconfig::updated_file))[10] or die "Can't get mtime for $itkaclsyncconfig::updated_file";
+       my $last_sync = (stat($itkaclsyncconfig::last_sync_file))[10] or die "Can't get mtime for $itkaclsyncconfig::last_sync_file";
+       my $last_group = (stat('/etc/group'))[10] or die "Can't get mtime for /etc/group";
+       my $last_group_db = (stat('/var/lib/misc/group.db'))[10] or die "Can't get mtime for /etc/group";
+       my $last_passwd = (stat('/etc/passwd'))[10] or die "Can't get mtime for /etc/passwd";
+       return 1 if ($last_sync - 10 < $last_update);
+       return 1 if ($last_sync - 10 < $last_group);
+       return 1 if ($last_sync - 10 < $last_group_db);
+       return 1 if ($last_sync - 10 < $last_passwd);
+
+       return 0;
+}