#! /usr/bin/perl -w
#
# cern-config-users - Create user accounts, provide root access, add printers, ... 
# based on the LANdb information of the device
#

use strict;
use diagnostics;
use SOAP::Lite;# +trace => 'debug';
use Getopt::Long;
use Pod::Usage;
use Data::Dumper;
use Net::LDAP;
use Net::LDAP::Control::Paged;
use Net::LDAP::Constant qw(LDAP_CONTROL_PAGED);
use Sys::Hostname;
use File::Temp;

sub getDeviceInfoFromNameMAC($);
sub GetUserInfo($);
sub RootEmail($);
sub RootK5login(@);
sub UpdateFile($);
sub UserAdd(@);
sub SudoAccess(@);
sub SshAuthorizedKeys(@);
sub AddPrinter(@);

#
# Parse the options
#
if (not @ARGV){
    print STDOUT "Usage: cern-config-users <options>. See: cern-config-users --help for more information\n";
    exit 1;
}
my @savearg = @ARGV;

my $debug = my $dryrun = my $verbose = my $reset = my $force = 0;
my $devicename = my $tmpdir = undef;

my %opts = (debug   => \$debug,
            dryrun  => \$dryrun,
            verbose => \$verbose,
            reset   => \$reset);
    
my $rc = GetOptions(\%opts,
                    "debug","dryrun","reset","verbose","force","help",
                    "setup-root-mail-forward",
                    "setup-root-k5login",
                    "setup-root-ssh-authorizedkeys",
                    "setup-user-accounts",
                    "setup-sudo-access",
                    "setup-printers",
                    "setup-all",
                    "include-secondary-accounts",
                    "include-service-accounts",
                    "use-devicename=s" => \$devicename,
		    "tmpdir=s" => \$tmpdir,
                   );

pod2usage(-verbose=> 2) unless $rc;
pod2usage(-verbose=> 2) if $opts{help};

$verbose = 1 if $debug;

if ($< != 0){
    print STDOUT "[WARN] Running as non-root, so running in dryrun mode\n" unless $dryrun;
    $dryrun = 1;
}
map {$opts{$_} = 1} qw(setup-root-mail-forward setup-root-k5login setup-root-ssh-authorizedkeys setup-user-accounts setup-sudo-access setup-printers) if $opts{"setup-all"};

$tmpdir = File::Temp::tempdir() if $dryrun and not defined $tmpdir;

print STDOUT "[INFO] Running \"$0 @savearg\" at ".scalar(localtime())."\n";
print STDOUT "[INFO]\n";
print STDOUT "[INFO] To be configured:\n";
print STDOUT "[INFO]    - root e-mail forwarding\n"    if $opts{"setup-root-mail-forward"};
print STDOUT "[INFO]    - Kerberized root access\n"    if $opts{"setup-root-k5login"};
print STDOUT "[INFO]    - sudo root access root\n"     if $opts{"setup-sudo-access"};
print STDOUT "[INFO]    - root authorized SSH keys\n"  if $opts{"setup-root-ssh-authorizedkeys"};
print STDOUT "[INFO]    - AFS accounts\n"              if $opts{"setup-user-accounts"};
print STDOUT "[INFO]    - printers\n"                  if $opts{"setup-printers"};

if ($reset){
    print STDOUT "[INFO]\n";
    print STDOUT "[INFO] Note: existing configuration will be re-written\n";
}
print STDOUT "[INFO]\n";
print STDOUT "[INFO] Note: secondary accounts ".($opts{"include-secondary-accounts"} ? "WILL" : "WILL NOT")." be considered,\n";
print STDOUT "[INFO]       service accounts ".($opts{"include-service-accounts"} ? "WILL" : "WILL NOT")." be considered.\n\n";

#print "\$debug = $debug\n\$dryrun = $dryrun\n".Dumper(\%opts);exit;

#
# Get information from Landb
#
print STDOUT "[VERBOSE] Getting device and user information from LANdb and ActiveDirectory\n" if $verbose;

my $client = SOAP::Lite
    ->uri("http://network.cern.ch/NetworkService")
    #->on_fault(sub{my ($soap,$res) = @_;
    #               print STDERR "[ERROR] Fatal SOAP error: ".(ref $res ? $res->faultstring : $soap->transport->status)."\n";
    #               exit 1;
    #           })
    ->proxy("https://network.cern.ch/sc/soap/soap.fcgi?v=4", keep_alive=>1)
    ->xmlschema('http://www.w3.org/2001/XMLSchema');

undef my $call;
if (defined $devicename){
    $call = getDeviceInfoFromNameMAC($devicename) || exit -1;
}else{
    $call = $client->getMyDeviceInfo();
    $devicename = (split(/\./,hostname()))[0];
    if ($call->fault) {
	$call = getDeviceInfoFromNameMAC($devicename) || exit -1;
    }
}

my $bu = $call->result;

#print Dumper($bu) if $debug;exit;

my $DeviceName = undef;
my %MainUser = my %Responsible = my @building = ();
if ($bu->{DeviceName}){
    $DeviceName = $bu->{DeviceName};
}
if (not $DeviceName){
    print STDERR "[ERROR] Failure to retrieve device information from LANdb\n";
    exit -1;
}
if ($bu->{UserPerson}){
    %MainUser = %{$bu->{UserPerson}};
}
if ($bu->{ResponsiblePerson}){
    %Responsible = %{$bu->{ResponsiblePerson}};
}
if (not %Responsible){
    print STDERR "[ERROR] Failure to retrieve Responsible for device $DeviceName from LANdb\n";
    exit -1;
}
if ($bu->{Location}->{Building}){
    my $building = $bu->{Location}->{Building};
    $building =~ s/^0+//;
    push(@building,$building) if $building;
}

print STDOUT "[INFO] Device ".uc($devicename || hostname())." is registered with:\n";
print STDOUT "[INFO]    - Responsible:                     ".$Responsible{FirstName}." ".$Responsible{Name}." (".$Responsible{Email}.")\n";
print STDOUT "[INFO]    - Main user:                       ".$MainUser{FirstName}   ." ".$MainUser{Name}   ." (".$MainUser{Email}.   ")\n" if %MainUser;
print STDOUT "[INFO]    - Physical location:               Building $building[0]\n" if @building;

# is 'Responsible' the same as the 'main user'?
if (exists $MainUser{FirstName} and exists $MainUser{Name}){
    if ($Responsible{FirstName} eq $MainUser{FirstName} and $Responsible{Name} eq $MainUser{Name}){
        print STDOUT "[DEBUG] Ignoring \"MainUser\", as it is the same as the \"Responsible\"\n" if $debug;
        %MainUser = ();
    }
}

#print Dumper(\%MainUser);
#print Dumper(\%Responsible);  exit;

#
# Get usernames and their buildings from XLDAP
#
my %resp = GetUserInfo(\%Responsible);
@{$Responsible{sAMAccountName}} = @{$resp{username}};
push(@building,@{$resp{building}}) if exists $resp{building};

if (%MainUser){
    my %user = GetUserInfo(\%MainUser);
    @{$MainUser{sAMAccountName}}    = @{$user{username}};
    push(@building,@{$user{building}}) if exists $user{building};
}

@building = sort keys %{{map{$_=>1} @building}};

print STDOUT "[INFO] User information:\n";
print STDOUT "[INFO]    - Accounts for \"Responsible\":      @{$Responsible{sAMAccountName}}\n";
print STDOUT "[INFO]    - Accounts for \"Main user\":        @{$MainUser{sAMAccountName}}\n"if %MainUser;
print STDOUT "[INFO]    - Users have offices in buildings: @building\n" if @building;
print STDOUT "\n";


#
# Root e-mails to be forwarded to Responsible
#
if ($opts{"setup-root-mail-forward"}){
    print STDOUT "[VERBOSE] Configuring root e-mail forwarding to the LANdb responsible\n" if $verbose;
    RootEmail($Responsible{Email});
}

#
# Root access granted to Responsible
#
if ($opts{"setup-root-k5login"}){
    print STDOUT "[VERBOSE] Configuring root .k5login for the LANdb responsible\n" if $verbose;
    RootK5login(@{$Responsible{sAMAccountName}});
}

#
# Create accounts for Responsible and MainUser
#
if ($opts{"setup-user-accounts"}){
    print STDOUT "[VERBOSE] Adding AFS accounts of LANdb responsible and mainuser\n" if $verbose;
    my @account = @{$Responsible{sAMAccountName}};
    if (exists $MainUser{sAMAccountName}){
        push(@account,@{$MainUser{sAMAccountName}});
    }
    @account = sort keys %{{map{$_=>1} @account}};

    UserAdd(@account);
}

#
# Setup sudo access for LANdb responsible
#
if ($opts{"setup-sudo-access"}){
    print STDOUT "[VERBOSE] Granting sudo-privileges to LANdb responsible\n" if $verbose;
    SudoAccess(@{$Responsible{sAMAccountName}});
}

#
# Add SSH keys of LANdb responsible to /root/.ssh/authorized_keys
#
if ($opts{"setup-root-ssh-authorizedkeys"}){
    print STDOUT "[VERBOSE] Installing SSH keys of LANdb responsible\n" if $verbose;
    SshAuthorizedKeys(@{$resp{sshkeys}});
}

#
# Add printers
#
if ($opts{"setup-printers"}){
    print STDOUT "[VERBOSE] Adding printers\n" if $verbose;
    if (@building){
        AddPrinter(@building);
    }else{
        print STDERR "[ERROR] Could not determine building where this device is located\n";
    }
}

#
# Done
#
print STDOUT "[INFO] Done.\n";

exit 0;

################################################
#
# Get Landb info using client's MAC address
#
################################################
sub getDeviceInfoFromNameMAC($){

    my $devicename = shift @_;
    undef my $call;

    # get MAC addresses
    my @mac = ();
    if (open(IFCONFIG,"/sbin/ifconfig |")){
        while (<IFCONFIG>){
            if (/HWaddr (\S+)/){
                push(@mac,$1);
            }
	    if (/ether (\S+)/){
                push(@mac,$1);
            }

        }
        close(IFCONFIG);
    }
    if (not @mac){
        print STDERR "[ERROR] Failure to retrieve MAC addresses from this device\n";
        return;
    }

    my $err = 1;
    for my $mac (@mac){
        #print ">>  $devicename,$mac\n";
        $call = $client->getDeviceInfoFromNameMAC($devicename,$mac);
        if (not $call->fault) {
            $err = 0;
            last;
        }
    }
    if ($err){
        print STDERR "[ERROR] Failure to retrieve device information from LANdb: " . $call->faultstring . "\n";
        return;
    }

    return $call;

}

################################################
#
# Add SSH keys of LANdb responsible to /root/.ssh/authorized_keys
#
################################################
sub SshAuthorizedKeys(@){
    my @sshkeys = sort @_;
    if (not @sshkeys){
        print STDERR "[VERBOSE] SshAuthorizedKeys: no SSH keys specified\n" if $verbose;
        return -1;
    }

    my $DotSshDir = "/root/.ssh";
    $DotSshDir = "$tmpdir/.ssh" if $dryrun;

    if ($verbose){
	print STDOUT "[VERBOSE] Setting up $DotSshDir/authorized_keys for keys: @sshkeys\n";
	map {print STDOUT "[VERBOSE]   $_\n"} @sshkeys;
	print "\n";
    }

    # Create /root/.ssh
    if (not -d $DotSshDir){
	mkdir $DotSshDir,0700;
	system("/sbin/restorecon",($debug ? ("-v",$DotSshDir) : $DotSshDir)) if -x "/sbin/restorecon";
    }

    # Ensure the relevant SSH keys are in the file
    my $rc = UpdateFile({file => "$DotSshDir/authorized_keys", line => [@sshkeys]});
    print STDOUT "[INFO] Updated $DotSshDir/authorized_keys\n\n";

    return $rc;

}

################################################
#
# Give sudo privileges to list of accounts
#
################################################
sub SudoAccess(@){
    my @account = sort @_;
    if (not @account){
        print STDERR "[ERROR] SudoAccess: no accounts specified, giving up\n";
        return -1;
    }

    # make sure accounts exist on the system
    my @todo = ();
    for (@account){
        push(@todo,$_) if getpwnam($_);
    }
    return 0 unless @todo;

    # which sudoers file? Note: SLC5 does not have /etc/sudoers.d by default
    my $sudoers = ( -d "/etc/sudoers.d" ? "/etc/sudoers.d/cern-config-users" : "/etc/sudoers");

    if ($sudoers eq "/etc/sudoers.d/cern-config-users"){
	$sudoers = "$tmpdir/cern-config-users" if $dryrun;

	# write the sudoers file
	if (not open(SUDOERS,"> $sudoers")){
	    print STDERR "[ERROR] SudoAccess: cannot open \"$sudoers\" for writing, giving up\n";
	    return -1;
	}
	chmod 0440, $sudoers;
	
	print SUDOERS "## This file is controlled by the cern-config-users script, do no edit!\n";
	map {printf SUDOERS "%-10s     ALL=(ALL)       ALL\n",$_} @todo;
	close SUDOERS;
	print STDOUT "[INFO] Updated $sudoers\n\n";
	
	# set SElinux context
	system("/sbin/restorecon",($debug ? ("-v",$sudoers) : $sudoers)) if -x "/sbin/restorecon";

    }else{
	$sudoers = "$tmpdir/cern-config-users" if $dryrun;

	my $MSGSTART = "\n## The following lines have been added automatically, do no edit!\n\n";
	my $MSGSTOP  = "\n## End of automatically added lines!\n";

	my $pretext = my $posttext = my $oldtext = "";
    
	# read current sudoers file, store additions from previous run
	if (open(F,$sudoers)){
	    my $tmp = $/;
	    undef $/;
	    my $file = <F>;
	    $/ = $tmp;
	    close(F);
	    if ($file =~ /$MSGSTART(.*)$MSGSTOP/s){
		$pretext = $`;
		$posttext = $';
                $oldtext = $1;
            }else{
                $pretext = $file;
            }
        }

        # update sudoers file
        if (open(F,"> $sudoers")){
            # should older entries be kept?
            if (not $reset){
                @todo = sort keys %{{map{$_=>1} (map {($_) = split} split("\n",$oldtext),@todo)}}; # beautifull, isn't it?
            }
            print F $pretext . $MSGSTART;
            map {printf F "%-10s     ALL=(ALL)       ALL\n",$_} @todo;
            print F $MSGSTOP . $posttext;
            close(F);
            print STDOUT "[INFO] Updated $sudoers\n\n" ;
        }else{
            print STDERR "[ERROR] Could not update $sudoers\n\n";
            return 1;
        }
    }

    return 0;
}

################################################
#
# Update /root/.k5login
#
################################################
sub RootK5login(@){
    my @account = sort @_;
    if (not @account){
        print STDERR "[ERROR] RootK5login: no accounts specified, giving up\n";
        return -1;
    }
    map {$_ .= "\@CERN.CH"} @account;

    my $DotK5login = "/root/.k5login";
    $DotK5login = "$tmpdir/DotK5login" if $dryrun;
    print STDOUT "[VERBOSE] Setting up $DotK5login for accounts: @account\n" if $verbose;

    my $rc = UpdateFile({file => $DotK5login, line => [@account]});
    print STDOUT "[INFO] Updated $DotK5login for accounts: @account\n\n" ;
    return $rc;
}

################################################
#
# Create user accounts
#
################################################
sub UserAdd(@){
    my @account = @_;
    if (not @account){
        print STDERR "[ERROR] UserAdd: no accounts specified, giving up\n";
        return -1;
    }

    # which command to use?
    my @useraddcern = ("/usr/sbin/useraddcern");
    #@useraddcern = ("/usr/sbin/ccdbuser") if not -x $useraddcern[0]; # Old SLC4 box?
    if (not -x $useraddcern[0]){
        print STDERR "[ERROR] No command found to create user accounts on the machine\n";
        return -1;
    }
    unshift(@useraddcern,qw(/bin/echo [DRYRUN])) if $dryrun;

    # should existing AFS accounts be removed?
    if ($reset){
        print STDOUT "[VERBOSE] Removing existing AFS accounts\n" if $verbose;
        my @todelete = ();
        if (open(FILE,"/etc/passwd")){
            while(my $line = <FILE>){
                chomp($line);
                my ($username,$homedir) = (split(/:/,$line))[0,5];
                if (substr($homedir,0,18) eq "/afs/cern.ch/user/"){
                    push(@todelete,$username) unless grep {$_ eq $username} @account;
                }
            }
            close(FILE);
        }
        if (@todelete){
            my $reply = "y";
            if (not $force){
                print STDOUT "[WARN] The following AFS accounts will be deleted from this system:\n";
                print STDOUT "[WARN]\n";
                print STDOUT "[WARN]      @todelete\n";
                print STDOUT "[WARN]\n";
                print STDOUT "[WARN] Are you sure you want to continue? [y/N] ";
                chomp($reply = <STDIN>);
            }
            if ($reply eq "y"){
                my $userdel = "/usr/sbin/userdel";
                $userdel = "/bin/echo [DRYRUN ] $userdel" if $dryrun;
                for my $account (sort @todelete){
                    if (system("$userdel $account") == 0){
                        print STDOUT "[INFO] Removed account \"$account\"\n";
                    }else{
                        print STDERR "[WARNING] Failed to remove account \"$account\"\n";
                    }
                }
            }
        }
    }

    # create the AFS accounts
    my @done = my @failed = my @skip = ();
    for my $account (@account){
        if (getpwnam($account)){
            print STDOUT "[VERBOSE] Account \"$account\" already exists\n" if $verbose;
            push(@skip,$account);
        }else{
            if (system(@useraddcern,$account) == 0){
                print STDOUT "[INFO] Added AFS account \"$account\"\n";
                push(@done,$account);
            }else{
                print STDERR "[WARNING] Failed to create account \"$account\"\n";
                push(@failed,$account);
            }
        }
    }

    print STDOUT "[INFO] Created AFS accounts @done\n" if @done;
    print STDOUT "[INFO] AFS accounts @skip already present\n" if @skip;
    print STDOUT "[INFO] Failed to create AFS accounts @failed\n" if @failed;
    print STDOUT "\n";

    return 0;
}

################################################
#
# Forward root e-mails
#
################################################
sub RootEmail($){
    my $address = shift @_;
    if (not $address){
        print STDERR "[ERROR] RootEmail: no e-mail address specified, giving up\n";
        return -1;
    }

    my $DotForward = "/root/.forward";
    $DotForward = "$tmpdir/DotForward" if $dryrun;
    print STDOUT "[VERBOSE] Setting up $DotForward, using $address\n" if $verbose;

    my $rc = UpdateFile({file => $DotForward, line => [$address]});
    print STDOUT "[INFO] Updated $DotForward, using $address\n\n";
    return $rc;
}

################################################
#
# Add printers
#
################################################
sub AddPrinter(@){
    my @building = sort {$a <=> $b} @_;
    if (not @building){
        print STDERR "[ERROR] AddPrinter: no building specified, giving up\n";
        return -1;
    }

    # what command to use?
    my @lpadmincern = ("/usr/sbin/lpadmincern");
    if (not -x $lpadmincern[0]){
        print STDERR "[ERROR] No command found to add printers to the machine\n";
        return -1;
    }
    
    unshift(@lpadmincern,qw(/bin/echo [DRYRUN])) if $dryrun;

    print STDOUT "[VERBOSE] Adding printers in building @building\n" if $verbose;

    # add printers
    for my $building (@building){
        if (system(@lpadmincern,"--add","--building","$building") == 0){
            print STDOUT "[INFO] Added printers for building $building\n";
        }else{
            print STDERR "[ERROR] Failed to add printers for building $building\n" if $verbose;
        }
    }
    print STDOUT "\n";

    return 0;
}

################################################
#
# Resolve account names from CCID or E-group names
#
################################################
sub GetUserInfo($){
    my $arg = shift @_;
    my %user = %$arg;

    # define search string
    undef my $search;
    if (uc($user{FirstName}) eq "E-GROUP"){
        #$user{Name} = "cern-staff"; # XXX
        $search = "memberOf:1.2.840.113556.1.4.1941:=CN=" .$user{Name} . ",OU=e-groups,OU=Workgroups,DC=cern,DC=ch";
    }else{
        $search = "employeeID=" . $user{CCID};
    }
    print STDERR "[DEBUG] \$search = \"$search\"\n" if $debug;

    # set up LDAP connection
    my $ldapserver = "xldap.cern.ch";
    my $ldap = Net::LDAP->new ( $ldapserver , version => 3, multihomed => 1, timeout => 15 ) or die "[ERROR] Failure to connect to LDAP: $@\n";
    my $page = Net::LDAP::Control::Paged->new( size => 1000 );
    my $mesg = $ldap->bind or die "[ERROR] Failure binding to LDAP: $@\n";

    my @args = (base    => "ou=users,ou=organic units,dc=cern,dc=ch",
                scope   => "sub",
                filter  => "$search",
                attrs   => [qw(sAMAccountName physicalDeliveryOfficeName employeeType altSecurityIdentities)],
                control => [$page],
               );
    my $cookie;
    my @username = my @building = my @sshkeys = ();

    while (1) {
        # Perform search
        my $mesg = $ldap->search(@args);
	
        # Only continue on LDAP_SUCCESS
        $mesg->code and last;

        # Process the result
        my @entries = $mesg->entries;
        printf STDERR "[DEBUG] LDAP return: %d entries.\n",scalar(@entries) if $debug;

        foreach my $entr (@entries) {
            # is account active?
            #my $status = $entr->get_value("userAccountControl");
            #if (not $status & 512){
            #    print STDOUT "[VERBOSE] Ignoring non-active account \"" . $entr->get_value("sAMAccountName") . "\"\n" if $verbose;
            #    next;
            #}

            # is this a primary, secondary, service account?
            my $employeeType = $entr->get_value("employeeType") || "";
	    print STDERR "[DEBUG] \$employeeType = \"$employeeType\"\n" if $debug;
	    if ($employeeType eq "Primary"){
		print STDOUT "[VERBOSE] Handling primary account \"" . $entr->get_value("sAMAccountName") . "\"\n" if $verbose;
	    } elsif ($employeeType eq "Secondary"){
		if ($opts{"include-secondary-accounts"}){
		    print STDOUT "[VERBOSE] Handling secondary account \"" . $entr->get_value("sAMAccountName") . "\"\n" if $verbose;
		}else{
		    print STDOUT "[VERBOSE] Ignoring secondary account \"" . $entr->get_value("sAMAccountName") . "\"\n" if $verbose;
		    next;
		}
	    } elsif ($employeeType eq "Service" or $employeeType eq "CRA Service Provider"){
		if ($opts{"include-service-accounts"}){
		    print STDOUT "[VERBOSE] Handling service account \"" . $entr->get_value("sAMAccountName") . "\"\n" if $verbose;
		}else{
		    print STDOUT "[VERBOSE] Ignoring service account \"" . $entr->get_value("sAMAccountName") . "\"\n" if $verbose;
		    next;
		}
	    } else {
		print STDOUT "[VERBOSE] Ignoring account type \"$employeeType\" for account \"" . $entr->get_value("sAMAccountName") . "\"\n" if $verbose;
		next;
	    }

            # username
            my $username = $entr->get_value("sAMAccountName");
            if ($username){
                print STDERR "[DEBUG] Username = $username\n" if $debug;
                push(@username,$username);
            }

	    # SSH public keys
	    if ($username){
		my @altSecurityIdentities = sort grep {/^SSH:/} $entr->get_value("altSecurityIdentities");
		map {s/^SSH:(ssh-(dss|rsa))\ (\S+)(\ (.*))?/$1 $3 $username/} @altSecurityIdentities;
		push(@sshkeys,@altSecurityIdentities);
	    }

            # building
            my $building = $entr->get_value("physicalDeliveryOfficeName");
            if ($building){
                $building =~ s/^Bld\s+//i;
                $building = (split(/\s+/,$building))[0];
                if ($building =~ /^\d+$/){
                    print STDERR "[DEBUG] Building = $building\n" if $debug;
                    push(@building,$building) unless grep {$_ eq $building} @building;
                }
            }
        }

        # Get cookie from paged control
        my ($resp) = $mesg->control( LDAP_CONTROL_PAGED ) or last;
        $cookie    = $resp->cookie or last;
    
        # Set cookie in paged control
        $page->cookie($cookie);
    }

    if ($cookie) {
        # We had an abnormal exit, so let the server know we do not want any more
        $page->cookie($cookie);
        $page->size(0);
        $ldap->search( @args );
    }
    
    my %data = (username => [sort @username],
                building => [sort @building],
		sshkeys  => [sort @sshkeys],
	);

    return %data;

}

################################################
#
# Append lines to a file, avoid duplicates
#
################################################
sub UpdateFile($){
    my $href = shift @_;
    my %input = %$href;
    #print Dumper(\%input);
    return -1 unless exists $input{file} and exists $input{line};
    
    my $file = $input{file};
    my @line = @{$input{line}};
    #print "$file = $file\n\@line = @line\n";exit;
    
    # force re-write of the file?
    if ($reset){
        print STDOUT "[VERBOSE] Re-writing $file from scratch\n" if $verbose;
        unlink $file;
    }

    # store contents of existing file (if any), ignoring leading and trailing whitespace on each line
    my %found = ();
    if (open(FILE,$file)){
        while(<FILE>){
            chomp;
            s/\s+$//; # trim leading whitespace
            s/^\s+//; # trim trailing whitespace
            $found{$_}++;
        }
        close(FILE);
    }

    # anything to be done?
    my @addline = ();
    for (@line){
        push(@addline,$_) unless exists $found{$_};
    }
    if (not @addline){
        print STDERR "[DEBUG] No lines to add to file $file\n" if $debug;
        return 0;
    }

    # append missing lines
    if (open(FILE,">> $file")){
        map {print FILE "$_\n"} @addline;
        close(FILE);
        print STDOUT "[DEBUG] $file updated\n" if $debug;
    }else{
        print STDERR "[ERROR] Could not update $file: $!\n";
        return -1;
    }

    # restore SElinux context
    system("/sbin/restorecon",($debug ? ("-v",$file) : $file)) if -x "/sbin/restorecon";

    return 0;

}

__END__

=pod

=head1 NAME

cern-config-users - Create user accounts, provide root access, add printers, ...
based on the LANdb information of the device 

=head1 DESCRIPTION

The cern-config-users script can be used to set up root mail forwarding, grant root
privileges through Kerberos and sudo configuration, add AFS accounts and add printers.

It gets the "Responsible", "Main User" and "Location" field from LANdb, and configures
the machine according to the specified options. It includes support for (nested)
e-groups.

=head1 SYNOPSIS

=over 2

    cern-config-users [--help]
  
    cern-config-users [--setup-root-mail-forward]
                      [--setup-root-k5login]
                      [--setup-user-accounts]
                      [--setup-sudo-access]
                      [--setup-root-ssh-authorizedkeys]
                      [--setup-printers]
                      [--setup-all]
                      [--include-secondary-accounts]
                      [--include-service-accounts]
                      [--use-devicename <device name>]
                      [--debug] [--dryrun] [--reset] [--verbose] [--force]
              
=back

=head1 OPTIONS

=over 4

=item B<--help>

Shows this help description

=item B<--setup-root-mail-forward>

Forward root e-mails to the LANdb responsible by updating /root/.forward

=item B<--setup-root-k5login>

Configure /root/.k5login to allow Kerberized root access for the 
LANdb responsible

=item B<--setup-sudo-access>

Configure /ets/sudoers to allow sudo root access for the 
LANdb responsible

=item B<--setup-root-ssh-authorizedkeys>

Add the public SSH keys of the LANdb responsible to /root/.ssh/authorized_keys

These keys are retrieved for the Cern LDAP server.

=item B<--setup-user-accounts>

Add AFS accounts for the LANdb responsible and main users.

=item B<--setup-printers>

Add printers in the building(s) where the machine resides and where
the LANdb responsible and main users have their offices.

=item B<--setup-all>

Configure all of the above

=item B<--include-secondary-accounts>

Consider secondary accounts of LANdb responsible and main users as well
as primary accounts. By default, only primary accounts will be considered.

=item B<--include-service-accounts>

Consider service accounts of LANdb responsible and main users as well
as primary accounts. By default, only primary accounts will be considered.

=item B<--use-devicename <device name>>

Get LANdb information for the specified device name. The device name will
be used together with the MAC address(es) of the machine to authenticate
against LANdb.

This option is useful for devices that do not have static IP addresses (like 
laptops).

=item B<--verbose> 

Print verbose output

=item B<--debug> 

Print debug output

=item B<--dryrun> 

Dummy run - don't do anything

=item B<--reset> 

Ignore locally made changes, and rewrite configuration from scratch

=item B<--force> 

Assume 'yes' is the answer to questions asked

=back

=head1 EXAMPLES

    cern-config-users --setup-all
    cern-config-users --setup-root-mail-forward --force
    cern-config-users --setup-user-accounts --include-secondary-accounts --verbose

=head1 AUTHOR

Jan van Eldik <Jan.van.Eldik@cern.ch>

=head1 KNOWN BUGS

Expansion of E-groups can take long time (about 10 seconds).

=cut
      
