X-Git-Url: http://andersk.mit.edu/gitweb/gssapi-openssh.git/blobdiff_plain/7c96a39934d663d065bb989e87ce6344d87db32a..ed46ef38b08c9b3410cf118f3c68d72fe8a0b4ea:/setup/setup-openssh.pl diff --git a/setup/setup-openssh.pl b/setup/setup-openssh.pl index 16c79c7..d6680a7 100644 --- a/setup/setup-openssh.pl +++ b/setup/setup-openssh.pl @@ -1,32 +1,73 @@ +#!/usr/bin/perl # -# setup-openssh.pl: -# Adapts the installed gsi-ssh environment to the current machine, -# performing actions that originally occurred during the package's -# 'make install' phase. +# setup-openssh.pl # -# Large parts adapted from 'fixpath', a tool found in openssh-3.0.2p1. +# Adapts the installed gsi-openssh environment to the current machine, +# performing actions that originally occurred during the package's +# 'make install' phase. # # Send comments/fixes/suggestions to: # Chase Phillips # +# +# Get user's GPT_LOCATION since we may be installing this using a new(er) +# version of GPT. +# + +$gptpath = $ENV{GPT_LOCATION}; + +# +# And the old standby.. +# + $gpath = $ENV{GLOBUS_LOCATION}; if (!defined($gpath)) { die "GLOBUS_LOCATION needs to be set before running this script" } +# +# Include standard modules +# + +use Getopt::Long; +use Cwd; +use Cwd 'abs_path'; + +# +# modify the ld library path for when we call ssh executables +# + +$oldldpath = $ENV{LD_LIBRARY_PATH}; +$newldpath = "$gpath/lib"; +if (length($oldldpath) > 0) +{ + $newldpath .= ":$oldldpath"; +} +$ENV{LD_LIBRARY_PATH} = "$newldpath"; + # # i'm including this because other perl scripts in the gpt setup directories # do so # -@INC = (@INC, "$gpath/lib/perl"); +if (defined($gptpath)) +{ + @INC = (@INC, "$gptpath/lib/perl", "$gpath/lib/perl"); +} +else +{ + @INC = (@INC, "$gpath/lib/perl"); +} require Grid::GPT::Setup; +# +# script-centred variable initialization +# + my $globusdir = $gpath; -my $setupdir = "$globusdir/setup/globus"; my $myname = "setup-openssh.pl"; # @@ -37,359 +78,1024 @@ $prefix = ${globusdir}; $exec_prefix = "${prefix}"; $bindir = "${exec_prefix}/bin"; $sbindir = "${exec_prefix}/sbin"; -$mandir = "${prefix}/man"; -$mansubdir = "man"; -$libexecdir = "${exec_prefix}/libexec"; -$sysconfdir = "/etc/ssh"; -$piddir = "/var/run"; -$xauth_path = "/usr/bin/X11/xauth"; +$sysconfdir = "$prefix/etc/ssh"; +$localsshdir = "/etc/ssh"; +$setupdir = "$prefix/setup/gsi_openssh_setup"; # -# Backup-related variables +# standard key types and their root file name mappings +# + +my $keyfiles = { + "dsa" => "ssh_host_dsa_key", + "rsa" => "ssh_host_rsa_key", + "rsa1" => "ssh_host_key", + }; + # +# argument specification. we offload some processing work from later functions +# to verify correct args by using anon subs in various places. +# + +my($interactive, $force, $verbose); + +GetOptions( + 'interactive!' => \$interactive, + 'force' => \$force, + 'verbose' => \$verbose, + ) or pod2usage(2); + +# +# miscellaneous initialization functions +# + +setPrivilegeSeparation(0); + +# +# main execution. This should find its way into a subroutine at some future +# point. +# + +print "$myname: Configuring package 'gsi_openssh'...\n"; +print "---------------------------------------------------------------------\n"; +print "Hi, I'm the setup script for the gsi_openssh package! I will create\n"; +print "a number of configuration files based on your local system setup. I\n"; +print "will also attempt to copy or create a number of SSH key pairs for\n"; +print "this machine. (Loosely, if I find a pair of host keys in /etc/ssh,\n"; +print "I will copy them into \$GLOBUS_LOCATION/etc/ssh. Otherwise, I will\n"; +print "generate them for you.)\n"; +print "\n"; +print " Jacobim Mugatu says,\n"; +print " \"Utopian Prime Minister Bad! GSI-OpenSSH Good!\"\n"; +print "\n"; + +if ( isForced() ) +{ + print "WARNING:\n"; + print "\n"; + print " Using the '-force' flag will cause all gsi_openssh_setup files to\n"; + print " be removed and replaced by new versions! Backup any critical\n"; + print " SSH configuration files before you choose to continue!\n"; + print "\n"; +} + +$response = query_boolean("Do you wish to continue with the setup package?","y"); +if ($response eq "n") +{ + print "\n"; + print "Exiting gsi_openssh setup.\n"; + + exit 0; +} + +print "\n"; + +makeConfDir(); +copyPRNGFile(); +$keyhash = determineKeys(); +runKeyGen($keyhash->{gen}); +copyKeyFiles($keyhash->{copy}); +copyConfigFiles(); + +my $metadata = new Grid::GPT::Setup(package_name => "gsi_openssh_setup"); + +$metadata->finish(); + +print "\n"; +print "Additional Notes:\n"; +print "\n"; +print " o I see that you have your GLOBUS_LOCATION environmental variable\n"; +print " set to:\n"; +print "\n"; +print " \"$gpath\"\n"; +print "\n"; +print " Remember to keep this variable set (correctly) when you want to\n"; +print " use the executables that came with this package.\n"; +print "\n"; +print " After that you may execute, for example:\n"; +print "\n"; +print " \$ . \$GLOBUS_LOCATION/etc/globus-user-env.sh\n"; +print "\n"; +print " to prepare your environment for running the gsi_openssh\n"; +print " executables.\n"; + +if ( !getPrivilegeSeparation() ) +{ + print "\n"; + print " o For System Administrators:\n"; + print "\n"; + print " If you are going to run the GSI-OpenSSH server, we recommend\n"; + print " enabling privilege separation. Although this package supports\n"; + print " this feature, your system appears to require some additional\n"; + print " configuration.\n"; + print "\n"; + print " From the file README.privsep, included as a part of the OpenSSH\n"; + print " distribution:\n"; + print "\n"; + print " When privsep is enabled, during the pre-authentication\n"; + print " phase sshd will chroot(2) to \"/var/empty\" and change its\n"; + print " privileges to the \"sshd\" user and its primary group. sshd\n"; + print " is a pseudo-account that should not be used by other\n"; + print " daemons, and must be locked and should contain a \"nologin\"\n"; + print " or invalid shell.\n"; + print "\n"; + print " You should do something like the following to prepare the\n"; + print " privsep preauth environment:\n"; + print "\n"; + print " \# mkdir /var/empty\n"; + print " \# chown root:sys /var/empty\n"; + print " \# chmod 755 /var/empty\n"; + print " \# groupadd sshd\n"; + print " \# useradd -g sshd -c 'sshd privsep' -d /var/empty \\\n"; + print " -s /bin/false sshd\n"; + print "\n"; + print " /var/empty should not contain any files.\n"; +} + +print "\n"; +print " o For more information about GSI-Enabled OpenSSH, visit:\n"; +print " \n"; + +# +# give the user a chance to read all of this output +# + +print "\n"; +print "Press to continue... "; +$trash = ; + +print "---------------------------------------------------------------------\n"; +print "$myname: Finished configuring package 'gsi_openssh'.\n"; + +exit; -$curr_time = time(); -$backupdir = "globus_backup_${curr_time}"; -$confbackupdir = "$backupdir/s1_conf"; -$transbackupdir = "$backupdir/s2_trans"; +# +# subroutines +# +### initPRNGHash( ) # -# We need to make sure it's okay to copy our setup files (if some files are already -# present). If we do copy any files, we backup the old files so the user can (possibly) -# reverse any damage. +# initialize the PRNG pathname hash # -sub test_dirs +sub initPRNGHash( ) { - my $composite; + # + # standard prng to executable conversion names + # - print "\nPreparatory: checking for existence of critical directories\n"; + addPRNGCommand("\@PROG_LS\@", "ls"); + addPRNGCommand("\@PROG_NETSTAT\@", "netstat"); + addPRNGCommand("\@PROG_ARP\@", "arp"); + addPRNGCommand("\@PROG_IFCONFIG\@", "ifconfig"); + addPRNGCommand("\@PROG_PS\@", "ps"); + addPRNGCommand("\@PROG_JSTAT\@", "jstat"); + addPRNGCommand("\@PROG_W\@", "w"); + addPRNGCommand("\@PROG_WHO\@", "who"); + addPRNGCommand("\@PROG_LAST\@", "last"); + addPRNGCommand("\@PROG_LASTLOG\@", "lastlog"); + addPRNGCommand("\@PROG_DF\@", "df"); + addPRNGCommand("\@PROG_SAR\@", "sar"); + addPRNGCommand("\@PROG_VMSTAT\@", "vmstat"); + addPRNGCommand("\@PROG_UPTIME\@", "uptime"); + addPRNGCommand("\@PROG_IPCS\@", "ipcs"); + addPRNGCommand("\@PROG_TAIL\@", "tail"); + + print "Determining paths for PRNG commands...\n"; + + $paths = determinePRNGPaths(); + + return; +} +### getDirectoryPaths( ) +# +# return an array ref containing all of the directories in which we should search +# for our listing of executable names. +# + +sub getDirectoryPaths( ) +{ # - # Remember to put in check for /etc + # read in the PATH environmental variable and prepend a set of 'safe' + # directories from which to test PRNG commands. # + $path = $ENV{PATH}; + $path = "/bin:/usr/bin:/sbin:/usr/sbin:/etc:" . $path; + @dirs = split(/:/, $path); + # - # Test for /etc/ssh + # sanitize each directory listed in the array. # - $composite = $sysconfdir; - if ( ! -d "$composite" ) + @dirs = map { + $tmp = $_; + $tmp =~ s:/+:/:g; + $tmp =~ s:^\s+|\s+$::g; + $tmp; + } @dirs; + + return \@dirs; +} + +### addPRNGCommand( $prng_name, $exec_name ) +# +# given a PRNG name and a corresponding executable name, add it to our list of +# PRNG commands for which to find on the system. +# + +sub addPRNGCommand +{ + my($prng_name, $exec_name) = @_; + + prngAddNode($prng_name, $exec_name); +} + +### copyPRNGFile( ) +# +# read in ssh_prng_cmds.in, translate the program listings to the paths we have +# found on the local system, and then write the output to ssh_prng_cmds. +# + +sub copyPRNGFile +{ + my($fileInput, $fileOutput); + my($mode, $uid, $gid); + my($data); + + if ( isPresent("$sysconfdir/ssh_prng_cmds") && !isForced() ) { - print "Could not find directory: '${composite}'.. creating.\n"; - mkdir($composite, 16877); - # 16877 should be 755, or drwxr-xr-x + printf("ssh_prng_cmds found and not forced. Not installing ssh_prng_cmds...\n"); + return; } + initPRNGHash(); + + print "Fixing paths in ssh_prng_cmds...\n"; + + $fileInput = "$setupdir/ssh_prng_cmds.in"; + $fileOutput = "$sysconfdir/ssh_prng_cmds"; + # - # Test for /etc/ssh/globus_backup_ + # verify that we are prepared to work with $fileInput # - $composite = "$sysconfdir/$backupdir"; - if ( ! -d "${composite}" ) + if ( !isReadable($fileInput) ) { - print "Could not find directory: '${composite}'.. creating.\n"; - mkdir($composite, 16877); + printf("Cannot read $fileInput... skipping.\n"); + return; } # - # Test for /etc/ssh/globus_backup_/s1_conf + # verify that we are prepared to work with $fileOuput # - $composite = "$sysconfdir/$confbackupdir"; - if ( ! -d "${composite}" ) + if ( !prepareFileWrite($fileOutput) ) { - print "Could not find directory: '${composite}'.. creating.\n"; - mkdir($composite, 16877); + return; } # - # Test for /etc/ssh/globus_backup_/s2_trans + # Grab the current mode/uid/gid for use later # - $composite = "$sysconfdir/$transbackupdir"; - if ( ! -d "${composite}" ) + $mode = (stat($fileInput))[2]; + $uid = (stat($fileInput))[4]; + $gid = (stat($fileInput))[5]; + + # + # Open the files for reading and writing, and loop over the input's contents + # + + $data = readFile($fileInput); + for my $k (keys %$prngcmds) { - print "Could not find directory: '${composite}'.. creating.\n"; - mkdir($composite, 16877); + $sub = prngGetExecPath($k); + $data =~ s:$k:$sub:g; } + writeFile($fileOutput, $data); + + # + # An attempt to revert the new file back to the original file's + # mode/uid/gid + # + + chmod($mode, $fileOutput); + chown($uid, $gid, $fileOutput); return 0; } -sub copy_setup_files +### determinePRNGPaths( ) +# +# for every entry in the PRNG hash, seek out and find the path for the +# corresponding executable name. +# + +sub determinePRNGPaths { - my $response; + my(@paths, @dirs); + my($exec_name, $exec_path); - print "\nStage 1: Copying configuration files into '${sysconfdir}'..\n"; + $dirs = getDirectoryPaths(); - $response = "y"; - if ( -e "${sysconfdir}/ssh_config" ) + for my $k (keys %$prngcmds) { - $response = query_boolean("${sysconfdir}/ssh_config already exists. Overwrite?", "n"); - if ($response eq "y") - { - action("cp ${sysconfdir}/ssh_config ${sysconfdir}/${confbackupdir}/ssh_config"); - } + $exec_name = prngGetExecName($k); + $exec_path = findExecutable($exec_name, $dirs); + prngSetExecPath($k, $exec_path); } - if ($response eq "y") + return; +} + +### prngAddNode( $prng_name, $exec_name ) +# +# add a new node to the PRNG hash +# + +sub prngAddNode +{ + my($prng_name, $exec_name) = @_; + my($node); + + if (!defined($prngcmds)) { - action("cp ${globusdir}/setup/globus/ssh_config ${sysconfdir}/ssh_config"); + $prngcmds = {}; } - # - # Reset response for our new query - # + $node = {}; + $node->{prng} = $prng_name; + $node->{exec} = $exec_name; + + $prngcmds->{$prng_name} = $node; +} + +### prngGetExecName( $key ) +# +# get the executable name from the prng commands hash named by $key +# + +sub prngGetExecName +{ + my($key) = @_; - $response = "y"; - if ( -e "${sysconfdir}/sshd_config" ) + return $prngcmds->{$key}->{exec}; +} + +### prngGetExecPath( $key ) +# +# get the executable path from the prng commands hash named by $key +# + +sub prngGetExecPath +{ + my($key) = @_; + + return $prngcmds->{$key}->{exec_path}; +} + +### prngGetNode( $key ) +# +# return a reference to the node named by $key +# + +sub prngGetNode +{ + my($key) = @_; + + return ${$prngcmds}{$key}; +} + +### prngSetExecPath( $key, $path ) +# +# given a key, set the executable path in that node to $path +# + +sub prngSetExecPath +{ + my($key, $path) = @_; + + $prngcmds->{$key}->{exec_path} = $path; +} + +### findExecutable( $exec_name, $dirs ) +# +# given an executable name, test each possible path in $dirs to see if such +# an executable exists. +# + +sub findExecutable +{ + my($exec_name, $dirs) = @_; + + for my $d (@$dirs) { - $response = query_boolean("${sysconfdir}/sshd_config already exists. Overwrite?", "n"); - if ($response eq "y") + $test = "$d/$exec_name"; + + if ( isExecutable($test) ) { - action("cp ${sysconfdir}/sshd_config ${sysconfdir}/${confbackupdir}/sshd_config"); + return $test; } } - if ($response eq "y") - { - action("cp ${globusdir}/setup/globus/sshd_config ${sysconfdir}/sshd_config"); - } + return "undef"; +} - # - # Reset response for our new query - # +### copyKeyFiles( $copylist ) +# +# given an array of keys to copy, copy both the key and its public variant into +# the gsi-openssh configuration directory. +# + +sub copyKeyFiles +{ + my($copylist) = @_; + my($regex, $basename); - $response = "y"; - if ( -e "${sysconfdir}/moduli" ) + if (@$copylist) { - $response = query_boolean("${sysconfdir}/moduli already exists. Overwrite?", "n"); - if ($response eq "y") + print "Copying ssh host keys...\n"; + + for my $f (@$copylist) { - action("cp ${sysconfdir}/moduli ${sysconfdir}/${confbackupdir}/moduli"); + $f =~ s:/+:/:g; + + if (length($f) > 0) + { + $keyfile = "$f"; + $pubkeyfile = "$f.pub"; + + copyFile("$localsshdir/$keyfile", "$sysconfdir/$keyfile"); + copyFile("$localsshdir/$pubkeyfile", "$sysconfdir/$pubkeyfile"); + } } } +} - if ($response eq "y") +### isForced( ) +# +# return true if the user passed in the force flag. return false otherwise. +# + +sub isForced +{ + if ( defined($force) && $force ) { - action("cp ${globusdir}/setup/globus/moduli ${sysconfdir}/moduli"); + return 1; + } + else + { + return 0; } } -sub runkeygen +### isReadable( $file ) +# +# given a file, return true if that file both exists and is readable by the +# effective user id. return false otherwise. +# + +sub isReadable { - print "\nStage 2: Generating ssh host keys..\n"; + my($file) = @_; - if ( ! -d "${sysconfdir}" ) + if ( ( -e $file ) && ( -r $file ) ) { - print "Could not find ${sysconfdir} directory... creating\n"; - mkdir($sysconfdir, 16877); - # 16877 should be 755, or drwxr-xr-x + return 1; } + else + { + return 0; + } +} + +### isExecutable( $file ) +# +# return true if $file is executable. return false otherwise. +# + +sub isExecutable +{ + my($file) = @_; - if ( -e "${sysconfdir}/ssh_host_key" ) + if ( -x $file ) { - print "${sysconfdir}/ssh_host_key already exists, skipping.\n"; + return 1; } else { - # if $sysconfdir/ssh_host_key doesn't exist.. - action("$bindir/ssh-keygen -t rsa1 -f $sysconfdir/ssh_host_key -N \"\""); + return 0; } +} + +### isWritable( $file ) +# +# given a file, return true if that file does not exist or is writable by the +# effective user id. return false otherwise. +# - if ( -e "${sysconfdir}/ssh_host_dsa_key" ) +sub isWritable +{ + my($file) = @_; + + if ( ( ! -e $file ) || ( -w $file ) ) { - print "${sysconfdir}/ssh_host_dsa_key already exists, skipping.\n"; + return 1; } else { - # if $sysconfdir/ssh_host_dsa_key doesn't exist.. - action("$bindir/ssh-keygen -t dsa -f $sysconfdir/ssh_host_dsa_key -N \"\""); + return 0; } +} + +### isPresent( $file ) +# +# given a file, return true if that file exists. return false otherwise. +# + +sub isPresent +{ + my($file) = @_; - if ( -e "${sysconfdir}/ssh_host_rsa_key" ) + if ( -e $file ) { - print "${sysconfdir}/ssh_host_rsa_key already exists, skipping.\n"; + return 1; } else { - # if $sysconfdir/ssh_host_rsa_key doesn't exist.. - action("$bindir/ssh-keygen -t rsa -f $sysconfdir/ssh_host_rsa_key -N \"\""); + return 0; } +} - return 0; +### makeConfDir( ) +# +# make the gsi-openssh configuration directory if it doesn't already exist. +# + +sub makeConfDir +{ + if ( isPresent($sysconfdir) ) + { + if ( -d $sysconfdir ) + { + return; + } + + die("${sysconfdir} already exists and is not a directory!\n"); + } + + print "Could not find ${sysconfdir} directory... creating.\n"; + action("mkdir -p $sysconfdir"); + + return; } -sub fixpaths +### determineKeys( ) +# +# based on a set of key types, triage them to determine if for each key type, that +# key type should be copied from the main ssh configuration directory, or if it +# should be generated using ssh-keygen. +# + +sub determineKeys { - my $g, $h; + my($keyhash, $keylist); + my($count); # - # Set up path translations for the installation files + # initialize our variables # - %def = ( - "/etc/ssh_config" => "${sysconfdir}/ssh_config", - "/etc/ssh_known_hosts" => "${sysconfdir}/ssh_known_hosts", - "/etc/sshd_config" => "${sysconfdir}/sshd_config", - "/usr/libexec" => "${libexecdir}", - "/etc/shosts.equiv" => "${sysconfdir}/shosts.equiv", - "/etc/ssh_host_key" => "${sysconfdir}/ssh_host_key", - "/etc/ssh_host_dsa_key" => "${sysconfdir}/ssh_host_dsa_key", - "/etc/ssh_host_rsa_key" => "${sysconfdir}/ssh_host_rsa_key", - "/var/run/sshd.pid" => "${piddir}/sshd.pid", - "/etc/moduli" => "${sysconfdir}/moduli", - "/etc/sshrc" => "${sysconfdir}/sshrc", - "/usr/X11R6/bin/xauth" => "${xauth_path}", - "/usr/bin:/bin:/usr/sbin:/sbin" => "/usr/bin:/bin:/usr/sbin:/sbin:${bindir}", - "(/path/to/scp.real)" => "${bindir}/scp.real", - "(/path/to/ssh)" => "${bindir}/ssh", - "(/path/to/sftp.real)" => "${bindir}/sftp.real", - "(/path/to/sshd.real)" => "${sbindir}/sshd.real", - "(/path/to/ssh_config)" => "${sysconfdir}/ssh_config", - "(/path/to/sshd_config)" => "${sysconfdir}/sshd_config", - ); + $count = 0; - # - # Files on which to perform path translations - # + $keyhash = {}; + $keyhash->{gen} = []; # a list of keytypes to generate + $keyhash->{copy} = []; # a list of files to copy from the - %files = ( - "${bindir}/scp" => 0, - "${bindir}/sftp" => 0, - "${sbindir}/sshd" => 0, - "${sysconfdir}/ssh_config" => 1, - "${sysconfdir}/sshd_config" => 1, - "${sysconfdir}/moduli" => 1, - "${mandir}/${mansubdir}1/scp.1" => 0, - "${mandir}/${mansubdir}1/ssh-add.1" => 0, - "${mandir}/${mansubdir}1/ssh-agent.1" => 0, - "${mandir}/${mansubdir}1/ssh-keygen.1" => 0, - "${mandir}/${mansubdir}1/ssh-keyscan.1" => 0, - "${mandir}/${mansubdir}1/ssh.1" => 0, - "${mandir}/${mansubdir}8/sshd.8" => 0, - "${mandir}/${mansubdir}8/sftp-server.8" => 0, - "${mandir}/${mansubdir}1/sftp.1" => 0, - ); + $genlist = $keyhash->{gen}; + $copylist = $keyhash->{copy}; - print "\nStage 3: Translating strings in config and man files...\n"; + # + # loop over our keytypes and determine what we need to do for each of them + # - for my $f (keys %files) + for my $keytype (keys %$keyfiles) { - $f =~ /(.*\/)*(.*)$/; + $basekeyfile = $keyfiles->{$keytype}; # - # we really should create a random filename and make sure that it - # doesn't already exist (based off current time_t or something) + # if the key's are already present, we don't need to bother with this rigamarole # - $g = "$f.tmp"; + $gkeyfile = "$sysconfdir/$basekeyfile"; + $gpubkeyfile = "$sysconfdir/$basekeyfile.pub"; - # - # get the filename for $f and place it in $h. - # - - $h = $f; - $h =~ s#^.*/##; + if ( isPresent($gkeyfile) && isPresent($gpubkeyfile) ) + { + if ( isForced() ) + { + if ( isWritable("$sysconfdir/$basekeyfile") && isWritable("$sysconfdir/$basekeyfile.pub") ) + { + action("rm $sysconfdir/$basekeyfile"); + action("rm $sysconfdir/$basekeyfile.pub"); + } + else + { + next; + } + } + } # - # Grab the current mode/uid/gid for use later + # if we can find a copy of the keys in /etc/ssh, we'll copy them to the user's + # globus location # - $mode = (stat($f))[2]; - $uid = (stat($f))[4]; - $gid = (stat($f))[5]; + $mainkeyfile = "$localsshdir/$basekeyfile"; + $mainpubkeyfile = "$localsshdir/$basekeyfile.pub"; - # - # Move $f into a .tmp file for the translation step - # - - $result = system("mv $f $g 2>&1"); - if ($result or $?) + if ( isReadable($mainkeyfile) && isReadable($mainpubkeyfile) ) { - die "ERROR: Unable to execute command: $!\n"; + push(@$copylist, $basekeyfile); + $count++; + next; } # - # Create a backup of this file if it's flagged + # otherwise, we need to generate the key # - if ($files{$f} == 1) + push(@$genlist, $keytype); + $count++; + } + + return $keyhash; +} + +### runKeyGen( $gen_keys ) +# +# given a set of key types, generate private and public keys for that key type and +# place them in the gsi-openssh configuration directory. +# + +sub runKeyGen +{ + my($gen_keys) = @_; + my $keygen = "$bindir/ssh-keygen"; + + if (@$gen_keys && -x $keygen) + { + print "Generating ssh host keys...\n"; + + for my $k (@$gen_keys) { - $result = system("cp $f ${sysconfdir}/${transbackupdir}/$h 2>&1"); + $keyfile = $keyfiles->{$k}; - if ($result or $?) + if ( !isPresent("$sysconfdir/$keyfile") ) { - die "ERROR: Unable to execute command: $!\n"; + action("$bindir/ssh-keygen -t $k -f $sysconfdir/$keyfile -N \"\""); } } + } + + return 0; +} - open(IN, "<$g") || die ("$0: input file $g missing!\n"); - open(OUT, ">$f") || die ("$0: unable to open output file $f!\n"); +### copySSHDConfigFile( ) +# +# this subroutine 'edits' the paths in sshd_config to suit them to the current environment +# in which the setup script is being run. +# - while () - { - for $s (keys(%def)) - { - s#$s#$def{$s}#; - } # for $s - print OUT "$_"; - } # while +sub copySSHDConfigFile +{ + my($fileInput, $fileOutput); + my($mode, $uid, $gid); + my($line, $newline); + my($privsep_enabled); - close(OUT); - close(IN); + print "Fixing paths in sshd_config...\n"; - # - # Remove the old .tmp file - # + $fileInput = "$setupdir/sshd_config.in"; + $fileOutput = "$sysconfdir/sshd_config"; + + # + # verify that we are prepared to work with $fileInput + # + + if ( !isReadable($fileInput) ) + { + printf("Cannot read $fileInput... skipping.\n"); + return; + } + + # + # verify that we are prepared to work with $fileOuput + # + + if ( !prepareFileWrite($fileOutput) ) + { + return; + } + + # + # check to see whether we should enable privilege separation + # + + if ( userExists("sshd") && ( -d "/var/empty" ) && ( getOwnerID("/var/empty") eq 0 ) ) + { + setPrivilegeSeparation(1); + } + else + { + setPrivilegeSeparation(0); + } + + if ( getPrivilegeSeparation() ) + { + $privsep_enabled = "yes"; + } + else + { + $privsep_enabled = "no"; + } + + # + # Grab the current mode/uid/gid for use later + # + + $mode = (stat($fileInput))[2]; + $uid = (stat($fileInput))[4]; + $gid = (stat($fileInput))[5]; + + # + # Open the files for reading and writing, and loop over the input's contents + # + + $data = readFile($fileInput); + + # + # alter the PidFile config + # + + $text = "PidFile\t$gpath/var/sshd.pid"; + $data =~ s:^[\s|#]*PidFile.*$:$text:gm; + + # + # set the sftp directive + # + + $text = "Subsystem\tsftp\t$gpath/libxec/sftp-server"; + $data =~ s:^[\s|#]*Subsystem\s+sftp\s+.*$:$text:gm; + + # + # set the privilege separation directive + # + + $text = "UsePrivilegeSeparation\t${privsep_enabled}"; + $data =~ s:^[\s|#]*UsePrivilegeSeparation.*$:$text:gm; + + # + # dump the modified output to the config file + # - $result = system("rm $g 2>&1"); + writeFile($fileOutput, $data); - if ($result or $?) + # + # An attempt to revert the new file back to the original file's + # mode/uid/gid + # + + chmod($mode, $fileOutput); + chown($uid, $gid, $fileOutput); + + return 0; +} + +### setPrivilegeSeparation( $value ) +# +# set the privilege separation variable to $value +# + +sub setPrivilegeSeparation +{ + my($value) = @_; + + $privsep = $value; +} + +### getPrivilegeSeparation( ) +# +# return the value of the privilege separation variable +# + +sub getPrivilegeSeparation +{ + return $privsep; +} + +### prepareFileWrite( $file ) +# +# test $file to prepare for writing to it. +# + +sub prepareFileWrite +{ + my($file) = @_; + + if ( isPresent($file) ) + { + printf("$file already exists... "); + + if ( isForced() ) { - die "ERROR: Unable to execute command: $!\n"; + if ( isWritable($file) ) + { + printf("removing.\n"); + action("rm $file"); + return 1; + } + else + { + printf("not writable -- skipping.\n"); + return 0; + } + } + else + { + printf("skipping.\n"); + return 0; } + } - # - # An attempt to revert the new file back to the original file's - # mode/uid/gid - # + return 1; +} - chmod($mode, $f); - chown($uid, $gid, $f); - } # for $f +### copyConfigFiles( ) +# +# subroutine that copies some extra config files to their proper location in +# $GLOBUS_LOCATION/etc/ssh. +# - print "complete.\n"; +sub copyConfigFiles +{ + # + # copy the sshd_config file into the ssh configuration directory and alter + # the paths in the file. + # - return 0; + copySSHDConfigFile(); + + # + # do straight copies of the ssh_config and moduli files. + # + + printf("Copying ssh_config and moduli to their proper location...\n"); + + copyFile("$setupdir/ssh_config", "$sysconfdir/ssh_config"); + copyFile("$setupdir/moduli", "$sysconfdir/moduli"); + + # + # copy and alter the SXXsshd script. + # + + copySXXScript("$setupdir/SXXsshd.in", "$sbindir/SXXsshd"); } -print "\n$myname: Configuring package 'gsi_openssh'..\n"; -print "NOTE: Run this as root for the intended effect.\n"; +### copyFile( $src, $dest ) +# +# copy the file pointed to by $src to the location specified by $dest. in the +# process observe the rules regarding when the '-force' flag was passed to us. +# -test_dirs(); -copy_setup_files(); -runkeygen(); -fixpaths(); +sub copyFile +{ + my($src, $dest) = @_; -my $metadata = new Grid::GPT::Setup(package_name => "gsi_openssh_setup"); + if ( !isReadable($src) ) + { + printf("$src is not readable... not creating $dest.\n"); + return; + } -$metadata->finish(); + if ( !prepareFileWrite($dest) ) + { + return; + } -print "$myname: Finished configuring package 'gsi_openssh'.\n"; + action("cp $src $dest"); +} + +### copySXXScript( $in, $out ) +# +# parse the input file, substituting in place the value of GLOBUS_LOCATION, and +# write the result to the output file. +# + +sub copySXXScript +{ + my($in, $out) = @_; + my($tmpgpath); + + if ( !isReadable($in) ) + { + printf("$in is not readable... not creating $out.\n"); + return; + } + + if ( !prepareFileWrite($out) ) + { + return; + } + # + # clean up any junk in the globus path variable + # + + $tmpgpath = $gpath; + $tmpgpath =~ s:/+:/:g; + $tmpgpath =~ s:([^/]+)/$:\1:g; + + # + # read in the script, substitute globus location, then write it back out + # + + $data = readFile($in); + $data =~ s|\@GLOBUS_LOCATION\@|$tmpgpath|g; + writeFile($out, $data); + action("chmod 755 $out"); +} + +### readFile( $filename ) # -# Just need a minimal action() subroutine for now.. +# reads and returns $filename's contents +# + +sub readFile +{ + my($filename) = @_; + my($data); + + open(IN, "$filename") || die "Can't open '$filename': $!"; + $/ = undef; + $data = ; + $/ = "\n"; + close(IN); + + return $data; +} + +### writeFile( $filename, $fileinput ) +# +# create the inputs to the ssl program at $filename, appending the common name to the +# stream in the process +# + +sub writeFile +{ + my($filename, $fileinput) = @_; + + # + # test for a valid $filename + # + + if ( !defined($filename) || (length($filename) lt 1) ) + { + die "Filename is undefined"; + } + + # + # verify that we are prepared to work with $filename + # + + if ( !prepareFileWrite($filename) ) + { + return; + } + + # + # write the output to $filename + # + + open(OUT, ">$filename"); + print OUT "$fileinput"; + close(OUT); +} + +### action( $command ) +# +# run $command within a proper system() command. # sub action { - my ($command) = @_; + my($command) = @_; printf "$command\n"; - my $result = system("$command 2>&1"); + my $result = system("LD_LIBRARY_PATH=\"$gpath/lib:\$LD_LIBRARY_PATH\"; $command 2>&1"); if (($result or $?) and $command !~ m!patch!) { @@ -397,10 +1103,16 @@ sub action } } +### query_boolean( $query_text, $default ) +# +# query the user with a string, and expect a response. If the user hits +# 'enter' instead of entering an input, then accept the default response. +# + sub query_boolean { - my ($query_text, $default) = @_; - my $nondefault, $foo, $bar; + my($query_text, $default) = @_; + my($nondefault, $foo, $bar); # # Set $nondefault to the boolean opposite of $default. @@ -421,11 +1133,120 @@ sub query_boolean $foo = ; ($bar) = split //, $foo; - if ($bar ne $nondefault) + if ( grep(/\s/, $bar) ) + { + # this is debatable. all whitespace means 'default' + + $bar = $default; + } + elsif ($bar ne $default) + { + # everything else means 'nondefault'. + + $bar = $nondefault; + } + else { + # extraneous step. to get here, $bar should be eq to $default anyway. + $bar = $default; } return $bar; } +### absolutePath( $file ) +# +# converts a given pathname into a canonical path using the abs_path function. +# + +sub absolutePath +{ + my($file) = @_; + my $home = $ENV{'HOME'}; + $file =~ s!~!$home!; + my $startd = cwd(); + $file =~ s!^\./!$startd/!; + $file = "$startd/$file" if $file !~ m!^\s*/!; + $file = abs_path($file); + return $file; +} + +### getOwnerID( $file ) +# +# return the uid containing the owner ID of the given file. +# + +sub getOwnerID +{ + my($file) = @_; + my($uid); + + # + # call stat() to get the mode of the file + # + + $uid = (stat($file))[4]; + + return $uid; +} + +### getMode( $file ) +# +# return a string containing the mode of the given file. +# + +sub getMode +{ + my($file) = @_; + my($tempmode, $mode); + + # + # call stat() to get the mode of the file + # + + $tempmode = (stat($file))[2]; + if (length($tempmode) < 1) + { + return ""; + } + + # + # call sprintf to format the mode into a UNIX-like string + # + + $mode = sprintf("%04o", $tempmode & 07777); + + return $mode; +} + +### userExists( $username ) +# +# given a username, return true if the user exists on the system. return false +# otherwise. +# + +sub userExists +{ + my($username) = @_; + my($uid); + + # + # retrieve the userid of the user with the given username + # + + $uid = getpwnam($username); + + # + # return true if $uid is defined and has a length greater than 0 + # + + if ( defined($uid) and (length($uid) > 0) ) + { + return 1; + } + else + { + return 0; + } +}