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";
198 print "---------------------------------------------------------------------\n";
199 print "$myname: Finished configuring package 'gsi_openssh'.\n";
209 # initialize the PRNG pathname hash
215 # standard prng to executable conversion names
218 addPRNGCommand("\@PROG_LS\@", "ls");
219 addPRNGCommand("\@PROG_NETSTAT\@", "netstat");
220 addPRNGCommand("\@PROG_ARP\@", "arp");
221 addPRNGCommand("\@PROG_IFCONFIG\@", "ifconfig");
222 addPRNGCommand("\@PROG_PS\@", "ps");
223 addPRNGCommand("\@PROG_JSTAT\@", "jstat");
224 addPRNGCommand("\@PROG_W\@", "w");
225 addPRNGCommand("\@PROG_WHO\@", "who");
226 addPRNGCommand("\@PROG_LAST\@", "last");
227 addPRNGCommand("\@PROG_LASTLOG\@", "lastlog");
228 addPRNGCommand("\@PROG_DF\@", "df");
229 addPRNGCommand("\@PROG_SAR\@", "sar");
230 addPRNGCommand("\@PROG_VMSTAT\@", "vmstat");
231 addPRNGCommand("\@PROG_UPTIME\@", "uptime");
232 addPRNGCommand("\@PROG_IPCS\@", "ipcs");
233 addPRNGCommand("\@PROG_TAIL\@", "tail");
235 print "Determining paths for PRNG commands...\n";
237 $paths = determinePRNGPaths();
242 ### getDirectoryPaths( )
244 # return an array ref containing all of the directories in which we should search
245 # for our listing of executable names.
248 sub getDirectoryPaths( )
251 # read in the PATH environmental variable and prepend a set of 'safe'
252 # directories from which to test PRNG commands.
256 $path = "/bin:/usr/bin:/sbin:/usr/sbin:/etc:" . $path;
257 @dirs = split(/:/, $path);
260 # sanitize each directory listed in the array.
266 $tmp =~ s:^\s+|\s+$::g;
273 ### addPRNGCommand( $prng_name, $exec_name )
275 # given a PRNG name and a corresponding executable name, add it to our list of
276 # PRNG commands for which to find on the system.
281 my($prng_name, $exec_name) = @_;
283 prngAddNode($prng_name, $exec_name);
288 # read in ssh_prng_cmds.in, translate the program listings to the paths we have
289 # found on the local system, and then write the output to ssh_prng_cmds.
294 my($fileInput, $fileOutput);
295 my($mode, $uid, $gid);
298 if ( isPresent("/dev/random") && !isForced() )
300 printf("/dev/random found and not forced. Not installing ssh_prng_cmds...\n");
306 print "Fixing paths in ssh_prng_cmds...\n";
308 $fileInput = "$setupdir/ssh_prng_cmds.in";
309 $fileOutput = "$sysconfdir/ssh_prng_cmds";
312 # verify that we are prepared to work with $fileInput
315 if ( !isReadable($fileInput) )
317 printf("Cannot read $fileInput... skipping.\n");
322 # verify that we are prepared to work with $fileOuput
325 if ( !prepareFileWrite($fileOutput) )
331 # Grab the current mode/uid/gid for use later
334 $mode = (stat($fileInput))[2];
335 $uid = (stat($fileInput))[4];
336 $gid = (stat($fileInput))[5];
339 # Open the files for reading and writing, and loop over the input's contents
342 $data = readFile($fileInput);
343 for my $k (keys %$prngcmds)
345 $sub = prngGetExecPath($k);
346 $data =~ s:$k:$sub:g;
348 writeFile($fileOutput, $data);
351 # An attempt to revert the new file back to the original file's
355 chmod($mode, $fileOutput);
356 chown($uid, $gid, $fileOutput);
361 ### determinePRNGPaths( )
363 # for every entry in the PRNG hash, seek out and find the path for the
364 # corresponding executable name.
367 sub determinePRNGPaths
370 my($exec_name, $exec_path);
372 $dirs = getDirectoryPaths();
374 for my $k (keys %$prngcmds)
376 $exec_name = prngGetExecName($k);
377 $exec_path = findExecutable($exec_name, $dirs);
378 prngSetExecPath($k, $exec_path);
384 ### prngAddNode( $prng_name, $exec_name )
386 # add a new node to the PRNG hash
391 my($prng_name, $exec_name) = @_;
394 if (!defined($prngcmds))
400 $node->{prng} = $prng_name;
401 $node->{exec} = $exec_name;
403 $prngcmds->{$prng_name} = $node;
406 ### prngGetExecName( $key )
408 # get the executable name from the prng commands hash named by $key
415 return $prngcmds->{$key}->{exec};
418 ### prngGetExecPath( $key )
420 # get the executable path from the prng commands hash named by $key
427 return $prngcmds->{$key}->{exec_path};
430 ### prngGetNode( $key )
432 # return a reference to the node named by $key
439 return ${$prngcmds}{$key};
442 ### prngSetExecPath( $key, $path )
444 # given a key, set the executable path in that node to $path
449 my($key, $path) = @_;
451 $prngcmds->{$key}->{exec_path} = $path;
454 ### findExecutable( $exec_name, $dirs )
456 # given an executable name, test each possible path in $dirs to see if such
457 # an executable exists.
462 my($exec_name, $dirs) = @_;
466 $test = "$d/$exec_name";
468 if ( isExecutable($test) )
477 ### copyKeyFiles( $copylist )
479 # given an array of keys to copy, copy both the key and its public variant into
480 # the gsi-openssh configuration directory.
486 my($regex, $basename);
490 print "Copying ssh host keys...\n";
492 for my $f (@$copylist)
499 $pubkeyfile = "$f.pub";
501 copyFile("$localsshdir/$keyfile", "$sysconfdir/$keyfile");
502 copyFile("$localsshdir/$pubkeyfile", "$sysconfdir/$pubkeyfile");
510 # return true if the user passed in the force flag. return false otherwise.
515 if ( defined($force) && $force )
525 ### isReadable( $file )
527 # given a file, return true if that file both exists and is readable by the
528 # effective user id. return false otherwise.
535 if ( ( -e $file ) && ( -r $file ) )
545 ### isExecutable( $file )
547 # return true if $file is executable. return false otherwise.
564 ### isWritable( $file )
566 # given a file, return true if that file does not exist or is writable by the
567 # effective user id. return false otherwise.
574 if ( ( ! -e $file ) || ( -w $file ) )
584 ### isPresent( $file )
586 # given a file, return true if that file exists. return false otherwise.
605 # make the gsi-openssh configuration directory if it doesn't already exist.
610 if ( isPresent($sysconfdir) )
612 if ( -d $sysconfdir )
617 die("${sysconfdir} already exists and is not a directory!\n");
620 print "Could not find ${sysconfdir} directory... creating.\n";
621 action("mkdir -p $sysconfdir");
628 # based on a set of key types, triage them to determine if for each key type, that
629 # key type should be copied from the main ssh configuration directory, or if it
630 # should be generated using ssh-keygen.
635 my($keyhash, $keylist);
639 # initialize our variables
645 $keyhash->{gen} = []; # a list of keytypes to generate
646 $keyhash->{copy} = []; # a list of files to copy from the
648 $genlist = $keyhash->{gen};
649 $copylist = $keyhash->{copy};
652 # loop over our keytypes and determine what we need to do for each of them
655 for my $keytype (keys %$keyfiles)
657 $basekeyfile = $keyfiles->{$keytype};
660 # if the key's are already present, we don't need to bother with this rigamarole
663 $gkeyfile = "$sysconfdir/$basekeyfile";
664 $gpubkeyfile = "$sysconfdir/$basekeyfile.pub";
666 if ( isPresent($gkeyfile) && isPresent($gpubkeyfile) )
670 if ( isWritable("$sysconfdir/$basekeyfile") && isWritable("$sysconfdir/$basekeyfile.pub") )
672 action("rm $sysconfdir/$basekeyfile");
673 action("rm $sysconfdir/$basekeyfile.pub");
683 # if we can find a copy of the keys in /etc/ssh, we'll copy them to the user's
687 $mainkeyfile = "$localsshdir/$basekeyfile";
688 $mainpubkeyfile = "$localsshdir/$basekeyfile.pub";
690 if ( isReadable($mainkeyfile) && isReadable($mainpubkeyfile) )
692 push(@$copylist, $basekeyfile);
698 # otherwise, we need to generate the key
701 push(@$genlist, $keytype);
708 ### runKeyGen( $gen_keys )
710 # given a set of key types, generate private and public keys for that key type and
711 # place them in the gsi-openssh configuration directory.
717 my $keygen = "$bindir/ssh-keygen";
719 if (@$gen_keys && -x $keygen)
721 print "Generating ssh host keys...\n";
723 for my $k (@$gen_keys)
725 $keyfile = $keyfiles->{$k};
727 if ( !isPresent("$sysconfdir/$keyfile") )
729 action("$bindir/ssh-keygen -t $k -f $sysconfdir/$keyfile -N \"\"");
737 ### copySSHDConfigFile( )
739 # this subroutine 'edits' the paths in sshd_config to suit them to the current environment
740 # in which the setup script is being run.
743 sub copySSHDConfigFile
745 my($fileInput, $fileOutput);
746 my($mode, $uid, $gid);
748 my($privsep_enabled);
750 print "Fixing paths in sshd_config...\n";
752 $fileInput = "$setupdir/sshd_config.in";
753 $fileOutput = "$sysconfdir/sshd_config";
756 # verify that we are prepared to work with $fileInput
759 if ( !isReadable($fileInput) )
761 printf("Cannot read $fileInput... skipping.\n");
766 # verify that we are prepared to work with $fileOuput
769 if ( !prepareFileWrite($fileOutput) )
775 # check to see whether we should enable privilege separation
778 if ( userExists("sshd") && ( -d "/var/empty" ) && ( getMode("/var/empty") eq "0700" ) )
780 setPrivilegeSeparation(1);
784 setPrivilegeSeparation(0);
787 if ( getPrivilegeSeparation() )
789 $privsep_enabled = "yes";
793 $privsep_enabled = "no";
797 # Grab the current mode/uid/gid for use later
800 $mode = (stat($fileInput))[2];
801 $uid = (stat($fileInput))[4];
802 $gid = (stat($fileInput))[5];
805 # Open the files for reading and writing, and loop over the input's contents
808 $data = readFile($fileInput);
811 # alter the PidFile config
814 $text = "PidFile\t$gpath/var/sshd.pid";
815 $data =~ s:^[\s|#]*PidFile.*$:$text:gm;
818 # set the sftp directive
821 $text = "Subsystem\tsftp\t$gpath/libxec/sftp-server";
822 $data =~ s:^[\s|#]*Subsystem\s+sftp\s+.*$:$text:gm;
825 # set the privilege separation directive
828 $text = "UsePrivilegeSeparation\t${privsep_enabled}";
829 $data =~ s:^[\s|#]*UsePrivilegeSeparation.*$:$text:gm;
832 # dump the modified output to the config file
835 writeFile($fileOutput, $data);
838 # An attempt to revert the new file back to the original file's
842 chmod($mode, $fileOutput);
843 chown($uid, $gid, $fileOutput);
848 ### setPrivilegeSeparation( $value )
850 # set the privilege separation variable to $value
853 sub setPrivilegeSeparation
860 ### getPrivilegeSeparation( )
862 # return the value of the privilege separation variable
865 sub getPrivilegeSeparation
870 ### prepareFileWrite( $file )
872 # test $file to prepare for writing to it.
879 if ( isPresent($file) )
881 printf("$file already exists... ");
885 if ( isWritable($file) )
887 printf("removing.\n");
893 printf("not writable -- skipping.\n");
899 printf("skipping.\n");
907 ### copyConfigFiles( )
909 # subroutine that copies some extra config files to their proper location in
910 # $GLOBUS_LOCATION/etc/ssh.
916 # copy the sshd_config file into the ssh configuration directory and alter
917 # the paths in the file.
920 copySSHDConfigFile();
923 # do straight copies of the ssh_config and moduli files.
926 printf("Copying ssh_config and moduli to their proper location...\n");
928 copyFile("$setupdir/ssh_config", "$sysconfdir/ssh_config");
929 copyFile("$setupdir/moduli", "$sysconfdir/moduli");
932 # copy and alter the SXXsshd script.
935 copySXXScript("$setupdir/SXXsshd.in", "$sbindir/SXXsshd");
938 ### copyFile( $src, $dest )
940 # copy the file pointed to by $src to the location specified by $dest. in the
941 # process observe the rules regarding when the '-force' flag was passed to us.
946 my($src, $dest) = @_;
948 if ( !isReadable($src) )
950 printf("$src is not readable... not creating $dest.\n");
954 if ( !prepareFileWrite($dest) )
959 action("cp $src $dest");
962 ### copySXXScript( $in, $out )
964 # parse the input file, substituting in place the value of GLOBUS_LOCATION, and
965 # write the result to the output file.
972 if ( !isReadable($in) )
974 printf("$in is not readable... not creating $out.\n");
978 if ( !prepareFileWrite($out) )
983 $data = readFile($in);
984 $data =~ s|\@GLOBUS_LOCATION\@|$gpath|g;
985 writeFile($out, $data);
986 action("chmod 755 $out");
989 ### readFile( $filename )
991 # reads and returns $filename's contents
999 open(IN, "$filename") || die "Can't open '$filename': $!";
1008 ### writeFile( $filename, $fileinput )
1010 # create the inputs to the ssl program at $filename, appending the common name to the
1011 # stream in the process
1016 my($filename, $fileinput) = @_;
1019 # test for a valid $filename
1022 if ( !defined($filename) || (length($filename) lt 1) )
1024 die "Filename is undefined";
1028 # verify that we are prepared to work with $filename
1031 if ( !prepareFileWrite($filename) )
1037 # write the output to $filename
1040 open(OUT, ">$filename");
1041 print OUT "$fileinput";
1045 ### action( $command )
1047 # run $command within a proper system() command.
1054 printf "$command\n";
1056 my $result = system("LD_LIBRARY_PATH=\"$gpath/lib:\$LD_LIBRARY_PATH\"; $command 2>&1");
1058 if (($result or $?) and $command !~ m!patch!)
1060 die "ERROR: Unable to execute command: $!\n";
1064 ### query_boolean( $query_text, $default )
1066 # query the user with a string, and expect a response. If the user hits
1067 # 'enter' instead of entering an input, then accept the default response.
1072 my($query_text, $default) = @_;
1073 my($nondefault, $foo, $bar);
1076 # Set $nondefault to the boolean opposite of $default.
1079 if ($default eq "n")
1088 print "${query_text} ";
1089 print "[$default] ";
1092 ($bar) = split //, $foo;
1094 if ( grep(/\s/, $bar) )
1096 # this is debatable. all whitespace means 'default'
1100 elsif ($bar ne $default)
1102 # everything else means 'nondefault'.
1108 # extraneous step. to get here, $bar should be eq to $default anyway.
1116 ### absolutePath( $file )
1118 # converts a given pathname into a canonical path using the abs_path function.
1124 my $home = $ENV{'HOME'};
1125 $file =~ s!~!$home!;
1127 $file =~ s!^\./!$startd/!;
1128 $file = "$startd/$file" if $file !~ m!^\s*/!;
1129 $file = abs_path($file);
1133 ### getMode( $file )
1135 # return a string containing the mode of the given file.
1141 my($tempmode, $mode);
1144 # call stat() to get the mode of the file
1147 $tempmode = (stat($file))[2];
1148 if (length($tempmode) < 1)
1154 # call sprintf to format the mode into a UNIX-like string
1157 $mode = sprintf("%04o", $tempmode & 07777);
1162 ### userExists( $username )
1164 # given a username, return true if the user exists on the system. return false
1174 # retrieve the userid of the user with the given username
1177 $uid = getpwnam($username);
1180 # return true if $uid is defined and has a length greater than 0
1183 if ( defined($uid) and (length($uid) > 0) )