5 # Adapts the installed gsi-openssh environment to the current machine,
6 # performing actions that originally occurred during the package's
7 # 'make install' phase.
9 # Send comments/fixes/suggestions to:
10 # Chase Phillips <cphillip@ncsa.uiuc.edu>
14 # Get user's GPT_LOCATION since we may be installing this using a new(er)
18 $gptpath = $ENV{GPT_LOCATION};
21 # And the old standby..
24 $gpath = $ENV{GLOBUS_LOCATION};
27 die "GLOBUS_LOCATION needs to be set before running this script"
31 # Include standard modules
39 # modify the ld library path for when we call ssh executables
42 $oldldpath = $ENV{LD_LIBRARY_PATH};
43 $newldpath = "$gpath/lib";
44 if (length($oldldpath) > 0)
46 $newldpath .= ":$oldldpath";
48 $ENV{LD_LIBRARY_PATH} = "$newldpath";
51 # i'm including this because other perl scripts in the gpt setup directories
55 if (defined($gptpath))
57 @INC = (@INC, "$gptpath/lib/perl", "$gpath/lib/perl");
61 @INC = (@INC, "$gpath/lib/perl");
64 require Grid::GPT::Setup;
67 # script-centred variable initialization
70 my $globusdir = $gpath;
71 my $myname = "setup-openssh.pl";
74 # Set up path prefixes for use in the path translations
77 $prefix = ${globusdir};
78 $exec_prefix = "${prefix}";
79 $bindir = "${exec_prefix}/bin";
80 $sbindir = "${exec_prefix}/sbin";
81 $sysconfdir = "$prefix/etc/ssh";
82 $localsshdir = "/etc/ssh";
83 $setupdir = "$prefix/setup/gsi_openssh_setup";
86 # standard key types and their root file name mappings
90 "dsa" => "ssh_host_dsa_key",
91 "rsa" => "ssh_host_rsa_key",
92 "rsa1" => "ssh_host_key",
96 # argument specification. we offload some processing work from later functions
97 # to verify correct args by using anon subs in various places.
100 my($interactive, $force, $verbose);
103 'interactive!' => \$interactive,
105 'verbose' => \$verbose,
109 # main execution. This should find its way into a subroutine at some future
113 print "$myname: Configuring package 'gsi_openssh'...\n";
114 print "---------------------------------------------------------------------\n";
115 print "Hi, I'm the setup script for the gsi_openssh package! I will create\n";
116 print "a number of configuration files based on your local system setup. I\n";
117 print "will also attempt to copy or create a number of SSH key pairs for\n";
118 print "this machine. (Loosely, if I find a pair of host keys in /etc/ssh,\n";
119 print "I will copy them into \$GLOBUS_LOCATION/etc/ssh. Otherwise, I will\n";
120 print "generate them for you.)\n";
122 print " Jacobim Mugatu says,\n";
123 print " \t\"Utopian Prime Minister Bad! GSI-OpenSSH Good!\"\n";
130 print " Using the '-force' flag will cause all gsi_openssh_setup files to\n";
131 print " be removed and replaced by new versions! Backup any critical\n";
132 print " SSH configuration files before you choose to continue!\n";
136 $response = query_boolean("Do you wish to continue with the setup package?","y");
137 if ($response eq "n")
140 print "Exiting gsi_openssh setup.\n";
149 $keyhash = determineKeys();
150 runKeyGen($keyhash->{gen});
151 copyKeyFiles($keyhash->{copy});
154 my $metadata = new Grid::GPT::Setup(package_name => "gsi_openssh_setup");
159 print "Additional Notes:\n";
161 print " o I see that you have your GLOBUS_LOCATION environmental variable\n";
164 print " \t\"$gpath\"\n";
166 print " Remember to keep this variable set (correctly) when you want to\n";
167 print " use the executables that came with this package.\n";
169 print " After that you may execute, for example:\n";
171 print " \t\$ . \$GLOBUS_LOCATION/etc/globus-user-env.sh\n";
173 print " to prepare your environment for running the gsi_openssh\n";
174 print " executables.\n";
176 if ( !getPrivilegeSeparation() )
179 print " o For System Administrators:\n";
181 print " If you are going to run the GSI-OpenSSH server, we recommend\n";
182 print " enabling privilege separation. Although this package supports\n";
183 print " this feature, your system appears to require some additional\n";
184 print " configuration.\n";
186 print " To enable privilege separation:\n";
188 print " \tIf the system user 'sshd' does not already exist,\n";
189 print " \tadd a user with that username.\n";
191 print " \tVerify that /var/empty exists, is owned by root,\n";
192 print " \tand has a mode of 0700.\n";
194 print " \tEnable the feature UsePrivilegeSeparation in\n";
195 print " \t\$GLOBUS_LOCATION/etc/ssh/sshd_config.\n";
199 print "For more information about GSI-Enabled OpenSSH, visit:\n";
200 print "<http://www.ncsa.uiuc.edu/Divisions/ACES/GSI/openssh/>\n";
203 # give the user a chance to read all of this output
207 print "Press <return> to continue... ";
211 print "---------------------------------------------------------------------\n";
212 print "$myname: Finished configuring package 'gsi_openssh'.\n";
222 # initialize the PRNG pathname hash
228 # standard prng to executable conversion names
231 addPRNGCommand("\@PROG_LS\@", "ls");
232 addPRNGCommand("\@PROG_NETSTAT\@", "netstat");
233 addPRNGCommand("\@PROG_ARP\@", "arp");
234 addPRNGCommand("\@PROG_IFCONFIG\@", "ifconfig");
235 addPRNGCommand("\@PROG_PS\@", "ps");
236 addPRNGCommand("\@PROG_JSTAT\@", "jstat");
237 addPRNGCommand("\@PROG_W\@", "w");
238 addPRNGCommand("\@PROG_WHO\@", "who");
239 addPRNGCommand("\@PROG_LAST\@", "last");
240 addPRNGCommand("\@PROG_LASTLOG\@", "lastlog");
241 addPRNGCommand("\@PROG_DF\@", "df");
242 addPRNGCommand("\@PROG_SAR\@", "sar");
243 addPRNGCommand("\@PROG_VMSTAT\@", "vmstat");
244 addPRNGCommand("\@PROG_UPTIME\@", "uptime");
245 addPRNGCommand("\@PROG_IPCS\@", "ipcs");
246 addPRNGCommand("\@PROG_TAIL\@", "tail");
248 print "Determining paths for PRNG commands...\n";
250 $paths = determinePRNGPaths();
255 ### getDirectoryPaths( )
257 # return an array ref containing all of the directories in which we should search
258 # for our listing of executable names.
261 sub getDirectoryPaths( )
264 # read in the PATH environmental variable and prepend a set of 'safe'
265 # directories from which to test PRNG commands.
269 $path = "/bin:/usr/bin:/sbin:/usr/sbin:/etc:" . $path;
270 @dirs = split(/:/, $path);
273 # sanitize each directory listed in the array.
279 $tmp =~ s:^\s+|\s+$::g;
286 ### addPRNGCommand( $prng_name, $exec_name )
288 # given a PRNG name and a corresponding executable name, add it to our list of
289 # PRNG commands for which to find on the system.
294 my($prng_name, $exec_name) = @_;
296 prngAddNode($prng_name, $exec_name);
301 # read in ssh_prng_cmds.in, translate the program listings to the paths we have
302 # found on the local system, and then write the output to ssh_prng_cmds.
307 my($fileInput, $fileOutput);
308 my($mode, $uid, $gid);
311 if ( isPresent("/dev/random") && !isForced() )
313 printf("/dev/random found and not forced. Not installing ssh_prng_cmds...\n");
319 print "Fixing paths in ssh_prng_cmds...\n";
321 $fileInput = "$setupdir/ssh_prng_cmds.in";
322 $fileOutput = "$sysconfdir/ssh_prng_cmds";
325 # verify that we are prepared to work with $fileInput
328 if ( !isReadable($fileInput) )
330 printf("Cannot read $fileInput... skipping.\n");
335 # verify that we are prepared to work with $fileOuput
338 if ( !prepareFileWrite($fileOutput) )
344 # Grab the current mode/uid/gid for use later
347 $mode = (stat($fileInput))[2];
348 $uid = (stat($fileInput))[4];
349 $gid = (stat($fileInput))[5];
352 # Open the files for reading and writing, and loop over the input's contents
355 $data = readFile($fileInput);
356 for my $k (keys %$prngcmds)
358 $sub = prngGetExecPath($k);
359 $data =~ s:$k:$sub:g;
361 writeFile($fileOutput, $data);
364 # An attempt to revert the new file back to the original file's
368 chmod($mode, $fileOutput);
369 chown($uid, $gid, $fileOutput);
374 ### determinePRNGPaths( )
376 # for every entry in the PRNG hash, seek out and find the path for the
377 # corresponding executable name.
380 sub determinePRNGPaths
383 my($exec_name, $exec_path);
385 $dirs = getDirectoryPaths();
387 for my $k (keys %$prngcmds)
389 $exec_name = prngGetExecName($k);
390 $exec_path = findExecutable($exec_name, $dirs);
391 prngSetExecPath($k, $exec_path);
397 ### prngAddNode( $prng_name, $exec_name )
399 # add a new node to the PRNG hash
404 my($prng_name, $exec_name) = @_;
407 if (!defined($prngcmds))
413 $node->{prng} = $prng_name;
414 $node->{exec} = $exec_name;
416 $prngcmds->{$prng_name} = $node;
419 ### prngGetExecName( $key )
421 # get the executable name from the prng commands hash named by $key
428 return $prngcmds->{$key}->{exec};
431 ### prngGetExecPath( $key )
433 # get the executable path from the prng commands hash named by $key
440 return $prngcmds->{$key}->{exec_path};
443 ### prngGetNode( $key )
445 # return a reference to the node named by $key
452 return ${$prngcmds}{$key};
455 ### prngSetExecPath( $key, $path )
457 # given a key, set the executable path in that node to $path
462 my($key, $path) = @_;
464 $prngcmds->{$key}->{exec_path} = $path;
467 ### findExecutable( $exec_name, $dirs )
469 # given an executable name, test each possible path in $dirs to see if such
470 # an executable exists.
475 my($exec_name, $dirs) = @_;
479 $test = "$d/$exec_name";
481 if ( isExecutable($test) )
490 ### copyKeyFiles( $copylist )
492 # given an array of keys to copy, copy both the key and its public variant into
493 # the gsi-openssh configuration directory.
499 my($regex, $basename);
503 print "Copying ssh host keys...\n";
505 for my $f (@$copylist)
512 $pubkeyfile = "$f.pub";
514 copyFile("$localsshdir/$keyfile", "$sysconfdir/$keyfile");
515 copyFile("$localsshdir/$pubkeyfile", "$sysconfdir/$pubkeyfile");
523 # return true if the user passed in the force flag. return false otherwise.
528 if ( defined($force) && $force )
538 ### isReadable( $file )
540 # given a file, return true if that file both exists and is readable by the
541 # effective user id. return false otherwise.
548 if ( ( -e $file ) && ( -r $file ) )
558 ### isExecutable( $file )
560 # return true if $file is executable. return false otherwise.
577 ### isWritable( $file )
579 # given a file, return true if that file does not exist or is writable by the
580 # effective user id. return false otherwise.
587 if ( ( ! -e $file ) || ( -w $file ) )
597 ### isPresent( $file )
599 # given a file, return true if that file exists. return false otherwise.
618 # make the gsi-openssh configuration directory if it doesn't already exist.
623 if ( isPresent($sysconfdir) )
625 if ( -d $sysconfdir )
630 die("${sysconfdir} already exists and is not a directory!\n");
633 print "Could not find ${sysconfdir} directory... creating.\n";
634 action("mkdir -p $sysconfdir");
641 # based on a set of key types, triage them to determine if for each key type, that
642 # key type should be copied from the main ssh configuration directory, or if it
643 # should be generated using ssh-keygen.
648 my($keyhash, $keylist);
652 # initialize our variables
658 $keyhash->{gen} = []; # a list of keytypes to generate
659 $keyhash->{copy} = []; # a list of files to copy from the
661 $genlist = $keyhash->{gen};
662 $copylist = $keyhash->{copy};
665 # loop over our keytypes and determine what we need to do for each of them
668 for my $keytype (keys %$keyfiles)
670 $basekeyfile = $keyfiles->{$keytype};
673 # if the key's are already present, we don't need to bother with this rigamarole
676 $gkeyfile = "$sysconfdir/$basekeyfile";
677 $gpubkeyfile = "$sysconfdir/$basekeyfile.pub";
679 if ( isPresent($gkeyfile) && isPresent($gpubkeyfile) )
683 if ( isWritable("$sysconfdir/$basekeyfile") && isWritable("$sysconfdir/$basekeyfile.pub") )
685 action("rm $sysconfdir/$basekeyfile");
686 action("rm $sysconfdir/$basekeyfile.pub");
696 # if we can find a copy of the keys in /etc/ssh, we'll copy them to the user's
700 $mainkeyfile = "$localsshdir/$basekeyfile";
701 $mainpubkeyfile = "$localsshdir/$basekeyfile.pub";
703 if ( isReadable($mainkeyfile) && isReadable($mainpubkeyfile) )
705 push(@$copylist, $basekeyfile);
711 # otherwise, we need to generate the key
714 push(@$genlist, $keytype);
721 ### runKeyGen( $gen_keys )
723 # given a set of key types, generate private and public keys for that key type and
724 # place them in the gsi-openssh configuration directory.
730 my $keygen = "$bindir/ssh-keygen";
732 if (@$gen_keys && -x $keygen)
734 print "Generating ssh host keys...\n";
736 for my $k (@$gen_keys)
738 $keyfile = $keyfiles->{$k};
740 if ( !isPresent("$sysconfdir/$keyfile") )
742 action("$bindir/ssh-keygen -t $k -f $sysconfdir/$keyfile -N \"\"");
750 ### copySSHDConfigFile( )
752 # this subroutine 'edits' the paths in sshd_config to suit them to the current environment
753 # in which the setup script is being run.
756 sub copySSHDConfigFile
758 my($fileInput, $fileOutput);
759 my($mode, $uid, $gid);
761 my($privsep_enabled);
763 print "Fixing paths in sshd_config...\n";
765 $fileInput = "$setupdir/sshd_config.in";
766 $fileOutput = "$sysconfdir/sshd_config";
769 # verify that we are prepared to work with $fileInput
772 if ( !isReadable($fileInput) )
774 printf("Cannot read $fileInput... skipping.\n");
779 # verify that we are prepared to work with $fileOuput
782 if ( !prepareFileWrite($fileOutput) )
788 # check to see whether we should enable privilege separation
791 if ( userExists("sshd") && ( -d "/var/empty" ) && ( getMode("/var/empty") eq "0700" ) )
793 setPrivilegeSeparation(1);
797 setPrivilegeSeparation(0);
800 if ( getPrivilegeSeparation() )
802 $privsep_enabled = "yes";
806 $privsep_enabled = "no";
810 # Grab the current mode/uid/gid for use later
813 $mode = (stat($fileInput))[2];
814 $uid = (stat($fileInput))[4];
815 $gid = (stat($fileInput))[5];
818 # Open the files for reading and writing, and loop over the input's contents
821 $data = readFile($fileInput);
824 # alter the PidFile config
827 $text = "PidFile\t$gpath/var/sshd.pid";
828 $data =~ s:^[\s|#]*PidFile.*$:$text:gm;
831 # set the sftp directive
834 $text = "Subsystem\tsftp\t$gpath/libxec/sftp-server";
835 $data =~ s:^[\s|#]*Subsystem\s+sftp\s+.*$:$text:gm;
838 # set the privilege separation directive
841 $text = "UsePrivilegeSeparation\t${privsep_enabled}";
842 $data =~ s:^[\s|#]*UsePrivilegeSeparation.*$:$text:gm;
845 # dump the modified output to the config file
848 writeFile($fileOutput, $data);
851 # An attempt to revert the new file back to the original file's
855 chmod($mode, $fileOutput);
856 chown($uid, $gid, $fileOutput);
861 ### setPrivilegeSeparation( $value )
863 # set the privilege separation variable to $value
866 sub setPrivilegeSeparation
873 ### getPrivilegeSeparation( )
875 # return the value of the privilege separation variable
878 sub getPrivilegeSeparation
883 ### prepareFileWrite( $file )
885 # test $file to prepare for writing to it.
892 if ( isPresent($file) )
894 printf("$file already exists... ");
898 if ( isWritable($file) )
900 printf("removing.\n");
906 printf("not writable -- skipping.\n");
912 printf("skipping.\n");
920 ### copyConfigFiles( )
922 # subroutine that copies some extra config files to their proper location in
923 # $GLOBUS_LOCATION/etc/ssh.
929 # copy the sshd_config file into the ssh configuration directory and alter
930 # the paths in the file.
933 copySSHDConfigFile();
936 # do straight copies of the ssh_config and moduli files.
939 printf("Copying ssh_config and moduli to their proper location...\n");
941 copyFile("$setupdir/ssh_config", "$sysconfdir/ssh_config");
942 copyFile("$setupdir/moduli", "$sysconfdir/moduli");
945 # copy and alter the SXXsshd script.
948 copySXXScript("$setupdir/SXXsshd.in", "$sbindir/SXXsshd");
951 ### copyFile( $src, $dest )
953 # copy the file pointed to by $src to the location specified by $dest. in the
954 # process observe the rules regarding when the '-force' flag was passed to us.
959 my($src, $dest) = @_;
961 if ( !isReadable($src) )
963 printf("$src is not readable... not creating $dest.\n");
967 if ( !prepareFileWrite($dest) )
972 action("cp $src $dest");
975 ### copySXXScript( $in, $out )
977 # parse the input file, substituting in place the value of GLOBUS_LOCATION, and
978 # write the result to the output file.
985 if ( !isReadable($in) )
987 printf("$in is not readable... not creating $out.\n");
991 if ( !prepareFileWrite($out) )
996 $data = readFile($in);
997 $data =~ s|\@GLOBUS_LOCATION\@|$gpath|g;
998 writeFile($out, $data);
999 action("chmod 755 $out");
1002 ### readFile( $filename )
1004 # reads and returns $filename's contents
1012 open(IN, "$filename") || die "Can't open '$filename': $!";
1021 ### writeFile( $filename, $fileinput )
1023 # create the inputs to the ssl program at $filename, appending the common name to the
1024 # stream in the process
1029 my($filename, $fileinput) = @_;
1032 # test for a valid $filename
1035 if ( !defined($filename) || (length($filename) lt 1) )
1037 die "Filename is undefined";
1041 # verify that we are prepared to work with $filename
1044 if ( !prepareFileWrite($filename) )
1050 # write the output to $filename
1053 open(OUT, ">$filename");
1054 print OUT "$fileinput";
1058 ### action( $command )
1060 # run $command within a proper system() command.
1067 printf "$command\n";
1069 my $result = system("LD_LIBRARY_PATH=\"$gpath/lib:\$LD_LIBRARY_PATH\"; $command 2>&1");
1071 if (($result or $?) and $command !~ m!patch!)
1073 die "ERROR: Unable to execute command: $!\n";
1077 ### query_boolean( $query_text, $default )
1079 # query the user with a string, and expect a response. If the user hits
1080 # 'enter' instead of entering an input, then accept the default response.
1085 my($query_text, $default) = @_;
1086 my($nondefault, $foo, $bar);
1089 # Set $nondefault to the boolean opposite of $default.
1092 if ($default eq "n")
1101 print "${query_text} ";
1102 print "[$default] ";
1105 ($bar) = split //, $foo;
1107 if ( grep(/\s/, $bar) )
1109 # this is debatable. all whitespace means 'default'
1113 elsif ($bar ne $default)
1115 # everything else means 'nondefault'.
1121 # extraneous step. to get here, $bar should be eq to $default anyway.
1129 ### absolutePath( $file )
1131 # converts a given pathname into a canonical path using the abs_path function.
1137 my $home = $ENV{'HOME'};
1138 $file =~ s!~!$home!;
1140 $file =~ s!^\./!$startd/!;
1141 $file = "$startd/$file" if $file !~ m!^\s*/!;
1142 $file = abs_path($file);
1146 ### getMode( $file )
1148 # return a string containing the mode of the given file.
1154 my($tempmode, $mode);
1157 # call stat() to get the mode of the file
1160 $tempmode = (stat($file))[2];
1161 if (length($tempmode) < 1)
1167 # call sprintf to format the mode into a UNIX-like string
1170 $mode = sprintf("%04o", $tempmode & 07777);
1175 ### userExists( $username )
1177 # given a username, return true if the user exists on the system. return false
1187 # retrieve the userid of the user with the given username
1190 $uid = getpwnam($username);
1193 # return true if $uid is defined and has a length greater than 0
1196 if ( defined($uid) and (length($uid) > 0) )