#!/usr/bin/perl -w ######################################################################## # BooNE k5login file administration utility # # Chris Green # UCR / BooNE # 2002/02/28 # ######################################################################## ######################################################################## # Preamble ######################################################################## # Nice Perl use strict; # Use the Getopt package use Getopt::Long; # Use the File::Basename package use File::Basename; # Use the Pod::Usage package use Pod::Usage; ######################################################################## # Other definitions ######################################################################## # Hash to store options my %options = (); # Default items my $realm = "FNAL.GOV"; my $file = "$ENV{HOME}/.k5login"; # Other useful variables my $basename = basename($file); my $exit_code = 0; ######################################################################## # Code ######################################################################## # Proces command-line options process_options(); my $check_only = ! (exists $options{add} or exists $options{remove} or exists $options{sort}); # Check we have the file and it has the right permissions verify_k5login(not $check_only); # Check for principals $exit_code = check_entries() if ((exists $options{check}) or $check_only); # Add new principals $exit_code = add_entries() if exists $options{add}; # Remove principals $exit_code = remove_entries() if exists $options{remove}; # Sort entries $exit_code = sort_entries() if exists $options{sort}; # Finish exit($exit_code); 1; ######################################################################## # Functions ######################################################################## ######################################################################## # Function: process_options # # Synopsis: Process command-line options. # # Arguments: none # # Returns: none # # Globals: $file # $basename # %options # # Notes: Makes non-returning calls to pod2usage on help or man options, # or on illegal options. # ######################################################################## sub process_options { # Options specifiers GetOptions(\%options, "check|c:s@", "add|a=s@", "remove|r=s@", "help|h|?", "man|m", "kerberos-realm|k=s" => \$realm, "sort|s", "file|f=s", "user|u=s") or pod2usage(-exitval => 2); # short usage (synopsis only) # Medium usage (options explanation) pod2usage(-verbose => 1) if exists $options{help}; # Full man page (requires working pod2man) pod2usage(-verbose => 2) if exists $options{man}; # If we've changed the filespec, update the short version if (exists $options{file}) { $file = $options{file}; $basename = basename($file); } if (exists $options{user}) { $file = glob "~$options{user}/${basename}"; } return; } ######################################################################## # Function: verify_k5login # # Synopsis: Checks whether $file exists and has correct permissions. # # Arguments: $create_if_necessary # # Returns: none # # Globals: $file # # Notes: Makes non-returning calls to local_exit on fatal errors. # ######################################################################## sub verify_k5login { my $create_if_necessary = shift; # Check the k5login file exists and has the necessary privileges -e "$file" or (! $create_if_necessary and return) or (system("touch $file") == 0 and ((exists $options{user}) ?(system("chown $options{user} $file") == 0) :1) and print "verify: created new file $file\n") or local_exit(2, "verify: $file does not exist and was unable to create!"); my $mode; my $dummy; ($dummy,$dummy,$mode) = stat "$file"; # Check and change file permissions ($mode % 512) == 0600 or ((chmod 0600, "$file") == 1 and print "verify: changed permissions of $file to 600\n") or local_exit(2, "verify: unable to set permissions of $file to 600"); } ######################################################################## # Function: check_entries # # Synopsis: Check for presence or absence of entries in the .k5login # file. # # Arguments: none # # Returns: 0 if all requested entries were found or none specified; # 1 otherwise. # # Globals: $file # $basename # %options # # Notes: If no principals are specified, print all entries and return 0. # ######################################################################## sub check_entries { my $exit_code = 0; open K5LOGIN, "$file" or local_exit(2, "check: unable to open $file for reading"); # Check if we have individual principals specified or we want them all if ($options{check}->[0] ne "") { for (@{$options{check}}) { # If return from check_entry is non-zero, set the exit_code check_entry($_, 1) and $exit_code = 1; } } else { print "check: listing all $basename entries:\n"; while () { chomp; print" $_\n"; } } close K5LOGIN; return $exit_code; } ######################################################################## # Function: check_entry # # Synopsis: Check for an individual entry in the .k5login file # # Arguments: $principal # $print (optional, defaults to 0) # # Returns: 0 if principal found in .k5login, 1 otherwise # # Globals: none # # Notes: Assumes .k5login file is already open for reading. # ######################################################################## sub check_entry { my $principal = qualify_principal(shift); my $print = shift; $print = 0 if ! defined $print; # Rewind the open file to the beginning seek K5LOGIN, 0, 0; while () { chomp; if ($_ eq $principal) { print "check: $principal found\n" if $print; return 0; } } print "check: $principal not found\n" if $print; return 1; } ######################################################################## # Function: add_entries # # Synopsis: Check for presence of entries and add if absent. # # Arguments: none # # Returns: 0 if all entries were added, 1 otherwise # # Globals: $file # $basename # %options # # Notes: Makes non-returning calls to local_exit on fatal errors. # ######################################################################## sub add_entries { my $exit_code = 0; # Open mixed for possible append open K5LOGIN, "+<$file" or local_exit(2, "add: unable to open $file for read/append"); foreach my $principal (qualify_principal(@{$options{add}})) { # Rewind seek K5LOGIN, 0, 0; if (check_entry($principal)) { # Append seek K5LOGIN, 0, 2; print K5LOGIN "$principal\n"; print "add: $principal added to $basename\n"; } else { print "add: $principal already present in $basename\n"; $exit_code = 1; } } close K5LOGIN; return $exit_code; } ######################################################################## # Function: remove_entries # # Synopsis: Remove specified entries from .k5login if present # # Arguments: none # # Returns: 0 # # Globals: $file # $basename # %options # # Notes: Makes non-returning calls to local_exit on fatal errors. # ######################################################################## sub remove_entries { my $exit_code = 0; open K5LOGIN, "$file"; my @all_entries = ; chomp @all_entries; my @remove_entries = qualify_principal(@{$options{remove}}); ####################################################################### # # # Note the nested grep here: for each principal in @all_entries, # quote its metacharacters and then search for it in the # @remove_entries list. If it is there, then the false value of (! # print) will ensure that the principal is ommitted from the vetted # list. @all_entries = grep {my $quoted_principal=quotemeta $_; my $principal = $_; scalar(grep /^$quoted_principal$/, @remove_entries) == 0 or ! print "remove: removing $principal from $basename\n"} @all_entries; # ###################################################################### close K5LOGIN; # Re-write the file with our vetted list open K5LOGIN, ">$file" or local_exit(2, "remove: unable to open $file for writing"); print K5LOGIN join("\n", @all_entries), "\n"; close K5LOGIN; return $exit_code } ######################################################################## # Function: qualify_principal # # Synopsis: Check if the principal is properly qualified, and append # the default realm if not # # Arguments: principal or principals # # Returns: scalar or array of corrected principals # # Globals: $realm # # Notes: # ######################################################################## sub qualify_principal { if ($#_ == 0) { # single argument, return a scalar $_ = shift; return /\@/?$_:"$_\@$realm"; # return a scalar } else { # array, return array return map {/\@/?$_:"$_\@$realm"} @{_}; # return a list } } ######################################################################## # Function: sort_entries # # Synopsis: Sort and re-write the .k5login file # # Arguments: none # # Returns: 0 # # Globals: $file # # Notes: Makes non-returning calls to local_exit on fatal errors. # ######################################################################## sub sort_entries { my $exit_code = 0; print "sort: sorting $basename ... "; open K5LOGIN, "$file"; my @all_entries = sort grep /\@/, ; close K5LOGIN; # Rewrite sorted entries open K5LOGIN, ">$file" or local_exit(2, "\nsort: unable to open $file for writing"); print K5LOGIN @all_entries; close K5LOGIN; print "done\n"; return $exit_code; } ######################################################################## # Function: print_options # # Synopsis: Print processed options # # Arguments: none # # Returns: none # # Globals: $options # # Notes: Debug only. Not currently used. # ######################################################################## sub print_options { # Diagnostics: print received options foreach my $key (keys %options) { print "$key="; SWITCH: { # Check whether the option value is a reference my $ref_option = ref $options{$key}; ! $ref_option && do { print $options{$key}, "\n"; last SWITCH; }; # If it is an array ref, print out the array's values "ARRAY" && do { print join(", ", @{$options{$key}}), "\n"; last SWITCH; }; # Don't know how to deal with anything else, but then we shouldn't # be getting anything else print "\n"; } } } ######################################################################## # Function: local_exit # # Synopsis: Exit with specified exit code, printing a message to STDERR # # Arguments: $exit_code # mesage (optional) # # Returns: none # # Globals: none # # Notes: Does not return. # ######################################################################## sub local_exit { my $code = shift; print STDERR @_, "\n" if ($#_>-1); exit($code); } __END__ ######################################################################## # Plain old documentation ######################################################################## =pod =head1 NAME k5login-admin - check for, add, remove or sort kerberos principals in the current user's L<.k5login(5)|.k5login(5)> file. =head1 SYNOPSIS B B<-h>|B<--help>|B<-?> B B<-m>|B<--man> [The perl functionality required by the B<-m> option is broken in some UPS KIT installations of perl.] B [B<-k>|B<--kerberos-realm> I] [B<-s>|B<--sort>] [B<-f>|B<--file> I] [B<-c>|B<--check> [I]]+ [B<-a>|B<--add> I]+ [B<-r>|B<--remove> I]+ Options marked with B<+> are repeatable and cumulative. =head1 DESCRIPTION I is a tool for easily manipulating L<.k5login(5)|.k5login(5)> files. It will operate on the F<.k5login> file by default, but alternative filenames can be specified if desired with the B<-f> switch. =head1 OPTIONS =over 4 =item B<-h> =item B<--help> =item B<-?> Short help. =item B<-m> =item B<--man> Full man page information. =item B<-k> I =item B<--kerberos-realm> I Append the alternative realm I to unqualified I arguments (B I). =item B<-s> =item B<--sort> Sort the F<.k5login> file alphabetically. =item B<-f> I =item B<--file> I Use the specified file (B F<~/.k5login>). =item B<-c> [I] =item B<--check> [I] If I is unspecified, print out the full contents of the F<.k5login> file. Otherwise, check for the presence of the specified I. Unqualified principals will be qualified according to the realm specified by the B<-k> option if present or its default if not. =item B<-a> I =item B<--add> I Add the specified I to the F<.k5login> file if it does not already exist. Unqualified principals will be qualified according to the realm specified by the B<-k> option if present or its default if not. =item B<-r> I =item B<--remove> I Remove the specified I from the F<.k5login> file. Unqualified principals will be qualified according to the realm specified by the B<-k> option if present or its default if not. =back =head1 RETURN_VALUE =over 4 =item 0 if all operations successful. =item 1 if at least one explicit check or add operation failed (remove and sort operations are always either successful or fatal irrespective of whether the F<.k5login> file is altered). B also exits with code 1 on help, usage or man page requests. =item 2 for fatal errors: file opens, invalid command-line options or other major problems. =back =head1 EXAMPLES =over 4 =item k5login-admin =item k5login-admin -c Equivalent to "cat ~/.k5login". =item k5login-admin -a friend1 -a friend2 -a friend3 -a \ user1@STRANGE.REALM.ORG Add the principals friend1@FNAL.GOV, friend2@FNAL.GOV and user1@STRANGE.REALM.ORG to the k5login file. =item k5login-admin -k OTHER.REALM.ORG -a user3 -a user4 -r \ annoyance@FNAL.GOV Add the principals user3@OTHER.REALM.ORG and user4@OTHER.REALM.ORG and remove the user annoyance@FNAL.GOV. =back =head1 FILES F<.k5login> =head1 SEE ALSO kerberos(1), L<.k5login(5)|.k5login(5)> =head1 AUTHOR Chris Green . =cut