#!/usr/bin/perl

# This script automatically runs:
#
#   monkeysphere-authentication update-users <user> 
#
# every time it detects a change in an authorized_keys or authorized_user_ids
# file. The update-users command operates on the username that owns the file
# that was updated.
#
# The list of files to monitor is generated from the AUTHORIZED_USER_IDS and
# RAW_AUTHORIZED_KEYS variables found in
# /etc/monkeysphere/monkeysphere-authentication.conf and expanded using a list
# of users on the system. 
#
# Additionally, the /var/lib/monkeysphere/user-update/lastchange file is
# monitored. If a change is made to that file, the list of files to monitor is
# re-generated based on a fresh listing of users. If you run a hook on user
# creation and deletion that generates a file in this directory, you can ensure
# that the list of files to monitor is always up-to-date.
#
# On debian system you can install required perl modules with: aptitude install
# libfile-changenotify-perl libfile-spec-perl libconfig-general-perl
#
# This script is designed to run at system start and should be run with root
# privileges.
#
# File::ChangeNotify is cross platform - it will choose a sub class for
# monitoring file system changes appropriate to your operating system (if you
# are running Linux, liblinux-inotify2-perl is recommended).

use strict;
use File::ChangeNotify;
use File::Basename;
use File::Spec;
use Config::General;

my $user_update_file = '/var/lib/monkeysphere/user-update/lastchange';
my $debug = 0;

sub debug {
  if ($debug eq 1) { print $_[0]; }
}

sub get_watch_files() {
  my @watch_files;
  my %key_file_locations = get_key_file_locations(); 
  # get list of users on the system
  while((my $name,my $passwd,my $uid,my $gid,my $gcos,my $dir,my $shell,my $home) = getpwent( )){
    while (my ($key, $file) = each (%key_file_locations)) {
      $file =~ s/%h/$home/;
      $file =~ s/%u/$name/;
      push(@watch_files,$file);
    }
  }
  endpwent();
  push(@watch_files,$user_update_file);
  return @watch_files;
}

sub get_key_file_locations {
  # set defaults
  my %key_file_locations;
  $key_file_locations{ 'authorized_user_ids' } = '%h/.monkeysphere/authorized_user_ids';
  $key_file_locations{ 'authorized_keys' } = '%h/.ssh/authorized_keys';

  # check monkeysphere-authentication configuration
  my $config_file = '/etc/monkeysphere/monkeysphere-authentication.conf';
  if (-f $config_file) {
    if (-r $config_file) {
      my %config;
      %config = Config::General::ParseConfig($config_file);
      if (exists $config{'AUTHORIZED_USER_IDS'}) {
        $key_file_locations{'authorized_user_ids'} = $config{'AUTHORIZED_USER_IDS'};
      }
      if (exists $config{'RAW_AUTHORIZED_KEYS'}) {
        $key_file_locations{'authorized_keys'} = $config{'RAW_AUTHORIZED_KEYS'};
      }
    }
  }
  return %key_file_locations;
}

sub get_watcher {
  my @filters; 
  my @dirs;

  my(@files) = get_watch_files(); 
  for my $file (@files) {
    my $dir = dirname($file);
    if ( -d $dir && !grep $_ eq $dir, @dirs ) {
      debug "Watching dir: $dir\n";
      push(@dirs,$dir);
      my $file = basename($file);
      if ( !grep $_ eq $file, @filters ) {
        debug "Adding file filter: $file\n";
        push(@filters,$file);
      }
    }
  }
  # create combined file filters to limit our monitor
  my $filter = '^(' . join("|",@filters) . ')$';

  # return a watcher object
  return my $watcher =
    File::ChangeNotify->instantiate_watcher
      ( directories => [ @dirs ],
        filter      => qr/$filter/, 
      );
}

sub watch {
  my $watcher = get_watcher();
  while ( my @events = $watcher->wait_for_events() ) { 
    my @users;
    for my $event (@events) {
      if($event->path eq "$user_update_file") {
        debug "Reloading user list";
        $watcher = get_watcher();
      } else {
        # if user deleted, file might not exist
        if( -f $event->path) {
          my $username = getpwuid((stat($event->path))[4]);

          if ( !grep $_ eq $username, @users ) {
            push(@users,$username);
          }
        }
      }
    }
    for my $user (@users) {
      my @args = ('u',$user);
      debug "Updating user: $user";
      system 'monkeysphere-authentication', @args;
    }
  }
}

watch();