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 exitDie "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($prompt, $force, $verbose);
105 'prompt!' => \$prompt,
107 'verbose' => \$verbose,
111 # miscellaneous initialization functions
114 setPrivilegeSeparation(0);
117 # main execution. This should find its way into a subroutine at some future
121 print "$myname: Configuring package 'gsi_openssh'...\n";
122 print "---------------------------------------------------------------------\n";
123 print "Hi, I'm the setup script for the gsi_openssh package! I will create\n";
124 print "a number of configuration files based on your local system setup. I\n";
125 print "will also attempt to copy or create a number of SSH key pairs for\n";
126 print "this machine. (Loosely, if I find a pair of host keys in /etc/ssh,\n";
127 print "I will copy them into \$GLOBUS_LOCATION/etc/ssh. Otherwise, I will\n";
128 print "generate them for you.)\n";
135 print " Using the '-force' flag will cause all gsi_openssh_setup files to\n";
136 print " be removed and replaced by new versions! Backup any critical\n";
137 print " SSH configuration files before you choose to continue!\n";
141 $response = query_boolean("Do you wish to continue with the setup package?","y");
142 if ($response eq "n")
145 print "Exiting gsi_openssh setup.\n";
154 $keyhash = determineKeys();
155 runKeyGen($keyhash->{gen});
156 copyKeyFiles($keyhash->{copy});
159 my $metadata = new Grid::GPT::Setup(package_name => "gsi_openssh_setup");
164 print "Additional Notes:\n";
166 print " o I see that you have your GLOBUS_LOCATION environmental variable\n";
169 print " \"$gpath\"\n";
171 print " Remember to keep this variable set (correctly) when you want to\n";
172 print " use the executables that came with this package.\n";
174 print " After that you may execute, for example:\n";
176 print " \$ . \$GLOBUS_LOCATION/etc/globus-user-env.sh\n";
178 print " to prepare your environment for running the gsi_openssh\n";
179 print " executables.\n";
181 print " o I recommend you review and customize to your liking the contents of\n";
183 print " \$GLOBUS_LOCATION/etc/ssh\n";
185 print " \"I can only show you the door. You have to walk through it.\"\n";
187 if ( !getPrivilegeSeparation() )
190 print " o For System Administrators:\n";
192 print " If you are going to run the GSI-OpenSSH server, we recommend\n";
193 print " enabling privilege separation. Although this package supports\n";
194 print " this feature, your system appears to require some additional\n";
195 print " configuration.\n";
197 print " From the file README.privsep, included as a part of the OpenSSH\n";
198 print " distribution:\n";
200 print " When privsep is enabled, during the pre-authentication\n";
201 print " phase sshd will chroot(2) to \"/var/empty\" and change its\n";
202 print " privileges to the \"sshd\" user and its primary group. sshd\n";
203 print " is a pseudo-account that should not be used by other\n";
204 print " daemons, and must be locked and should contain a \"nologin\"\n";
205 print " or invalid shell.\n";
207 print " You should do something like the following to prepare the\n";
208 print " privsep preauth environment:\n";
210 print " \# mkdir /var/empty\n";
211 print " \# chown root:sys /var/empty\n";
212 print " \# chmod 755 /var/empty\n";
213 print " \# groupadd sshd\n";
214 print " \# useradd -g sshd -c 'sshd privsep' -d /var/empty \\\n";
215 print " -s /bin/false sshd\n";
217 print " /var/empty should not contain any files.\n";
221 print " o For more information about GSI-Enabled OpenSSH, visit:\n";
222 print " <http://grid.ncsa.uiuc.edu/ssh/>\n";
225 # give the user a chance to read all of this output
231 print "Press <return> to continue... ";
235 print "---------------------------------------------------------------------\n";
236 print "$myname: Finished configuring package 'gsi_openssh'.\n";
246 # initialize the PRNG pathname hash
252 # standard prng to executable conversion names
255 addPRNGCommand("\@PROG_LS\@", "ls");
256 addPRNGCommand("\@PROG_NETSTAT\@", "netstat");
257 addPRNGCommand("\@PROG_ARP\@", "arp");
258 addPRNGCommand("\@PROG_IFCONFIG\@", "ifconfig");
259 addPRNGCommand("\@PROG_PS\@", "ps");
260 addPRNGCommand("\@PROG_JSTAT\@", "jstat");
261 addPRNGCommand("\@PROG_W\@", "w");
262 addPRNGCommand("\@PROG_WHO\@", "who");
263 addPRNGCommand("\@PROG_LAST\@", "last");
264 addPRNGCommand("\@PROG_LASTLOG\@", "lastlog");
265 addPRNGCommand("\@PROG_DF\@", "df");
266 addPRNGCommand("\@PROG_SAR\@", "sar");
267 addPRNGCommand("\@PROG_VMSTAT\@", "vmstat");
268 addPRNGCommand("\@PROG_UPTIME\@", "uptime");
269 addPRNGCommand("\@PROG_IPCS\@", "ipcs");
270 addPRNGCommand("\@PROG_TAIL\@", "tail");
272 print "Determining paths for PRNG commands...\n";
274 $paths = determinePRNGPaths();
279 ### getDirectoryPaths( )
281 # return an array ref containing all of the directories in which we should search
282 # for our listing of executable names.
285 sub getDirectoryPaths( )
288 # read in the PATH environmental variable and prepend a set of 'safe'
289 # directories from which to test PRNG commands.
293 $path = "/bin:/usr/bin:/sbin:/usr/sbin:/etc:" . $path;
294 @dirs = split(/:/, $path);
297 # sanitize each directory listed in the array.
303 $tmp =~ s:^\s+|\s+$::g;
310 ### addPRNGCommand( $prng_name, $exec_name )
312 # given a PRNG name and a corresponding executable name, add it to our list of
313 # PRNG commands for which to find on the system.
318 my($prng_name, $exec_name) = @_;
320 prngAddNode($prng_name, $exec_name);
325 # read in ssh_prng_cmds.in, translate the program listings to the paths we have
326 # found on the local system, and then write the output to ssh_prng_cmds.
331 my($fileInput, $fileOutput);
332 my($mode, $uid, $gid);
335 if ( isPresent("$sysconfdir/ssh_prng_cmds") && !isForced() )
337 printf("ssh_prng_cmds found and not forced. Not installing ssh_prng_cmds...\n");
343 print "Fixing paths in ssh_prng_cmds...\n";
345 $fileInput = "$setupdir/ssh_prng_cmds.in";
346 $fileOutput = "$sysconfdir/ssh_prng_cmds";
349 # verify that we are prepared to work with $fileInput
352 if ( !isReadable($fileInput) )
354 printf("Cannot read $fileInput... skipping.\n");
359 # verify that we are prepared to work with $fileOuput
362 if ( !prepareFileWrite($fileOutput) )
368 # Grab the current mode/uid/gid for use later
371 $mode = (stat($fileInput))[2];
372 $uid = (stat($fileInput))[4];
373 $gid = (stat($fileInput))[5];
376 # Open the files for reading and writing, and loop over the input's contents
379 $data = readFile($fileInput);
380 for my $k (keys %$prngcmds)
382 $sub = prngGetExecPath($k);
383 $data =~ s:$k:$sub:g;
385 writeFile($fileOutput, $data);
388 # An attempt to revert the new file back to the original file's
392 chmod($mode, $fileOutput);
393 chown($uid, $gid, $fileOutput);
398 ### determinePRNGPaths( )
400 # for every entry in the PRNG hash, seek out and find the path for the
401 # corresponding executable name.
404 sub determinePRNGPaths
407 my($exec_name, $exec_path);
409 $dirs = getDirectoryPaths();
411 for my $k (keys %$prngcmds)
413 $exec_name = prngGetExecName($k);
414 $exec_path = findExecutable($exec_name, $dirs);
415 prngSetExecPath($k, $exec_path);
421 ### prngAddNode( $prng_name, $exec_name )
423 # add a new node to the PRNG hash
428 my($prng_name, $exec_name) = @_;
431 if (!defined($prngcmds))
437 $node->{prng} = $prng_name;
438 $node->{exec} = $exec_name;
440 $prngcmds->{$prng_name} = $node;
443 ### prngGetExecName( $key )
445 # get the executable name from the prng commands hash named by $key
452 return $prngcmds->{$key}->{exec};
455 ### prngGetExecPath( $key )
457 # get the executable path from the prng commands hash named by $key
464 return $prngcmds->{$key}->{exec_path};
467 ### prngGetNode( $key )
469 # return a reference to the node named by $key
476 return ${$prngcmds}{$key};
479 ### prngSetExecPath( $key, $path )
481 # given a key, set the executable path in that node to $path
486 my($key, $path) = @_;
488 $prngcmds->{$key}->{exec_path} = $path;
491 ### findExecutable( $exec_name, $dirs )
493 # given an executable name, test each possible path in $dirs to see if such
494 # an executable exists.
499 my($exec_name, $dirs) = @_;
503 $test = "$d/$exec_name";
505 if ( isExecutable($test) )
514 ### copyKeyFiles( $copylist )
516 # given an array of keys to copy, copy both the key and its public variant into
517 # the gsi-openssh configuration directory.
523 my($regex, $basename);
527 print "Copying ssh host keys...\n";
529 for my $f (@$copylist)
536 $pubkeyfile = "$f.pub";
538 copyFile("$localsshdir/$keyfile", "$sysconfdir/$keyfile");
539 copyFile("$localsshdir/$pubkeyfile", "$sysconfdir/$pubkeyfile");
547 # return true if the user passed in the force flag. return false otherwise.
552 if ( defined($force) && $force )
562 ### isReadable( $file )
564 # given a file, return true if that file both exists and is readable by the
565 # effective user id. return false otherwise.
572 if ( ( -e $file ) && ( -r $file ) )
582 ### isExecutable( $file )
584 # return true if $file is executable. return false otherwise.
601 ### isWritable( $file )
603 # given a file, return true if that file does not exist or is writable by the
604 # effective user id. return false otherwise.
611 if ( ( ! -e $file ) || ( -w $file ) )
621 ### isPresent( $file )
623 # given a file, return true if that file exists. return false otherwise.
642 # make the gsi-openssh configuration directory if it doesn't already exist.
647 if ( isPresent($sysconfdir) )
649 if ( -d $sysconfdir )
654 print("${sysconfdir} already exists and is not a directory!\n");
658 print "Could not find ${sysconfdir} directory... creating.\n";
659 action("mkdir -p $sysconfdir");
666 # based on a set of key types, triage them to determine if for each key type, that
667 # key type should be copied from the main ssh configuration directory, or if it
668 # should be generated using ssh-keygen.
673 my($keyhash, $keylist);
677 # initialize our variables
683 $keyhash->{gen} = []; # a list of keytypes to generate
684 $keyhash->{copy} = []; # a list of files to copy from the
686 $genlist = $keyhash->{gen};
687 $copylist = $keyhash->{copy};
690 # loop over our keytypes and determine what we need to do for each of them
693 for my $keytype (keys %$keyfiles)
695 $basekeyfile = $keyfiles->{$keytype};
698 # if the key's are already present, we don't need to bother with this rigamarole
701 $gkeyfile = "$sysconfdir/$basekeyfile";
702 $gpubkeyfile = "$sysconfdir/$basekeyfile.pub";
704 if ( isPresent($gkeyfile) && isPresent($gpubkeyfile) )
708 if ( isWritable("$sysconfdir/$basekeyfile") && isWritable("$sysconfdir/$basekeyfile.pub") )
710 action("rm $sysconfdir/$basekeyfile");
711 action("rm $sysconfdir/$basekeyfile.pub");
721 # if we can find a copy of the keys in /etc/ssh, we'll copy them to the user's
725 $mainkeyfile = "$localsshdir/$basekeyfile";
726 $mainpubkeyfile = "$localsshdir/$basekeyfile.pub";
728 if ( isReadable($mainkeyfile) && isReadable($mainpubkeyfile) )
730 push(@$copylist, $basekeyfile);
736 # otherwise, we need to generate the key
739 push(@$genlist, $keytype);
746 ### runKeyGen( $gen_keys )
748 # given a set of key types, generate private and public keys for that key type and
749 # place them in the gsi-openssh configuration directory.
755 my $keygen = "$bindir/ssh-keygen";
757 if (@$gen_keys && -x $keygen)
759 print "Generating ssh host keys...\n";
761 for my $k (@$gen_keys)
763 $keyfile = $keyfiles->{$k};
765 if ( !isPresent("$sysconfdir/$keyfile") )
767 action("$bindir/ssh-keygen -t $k -f $sysconfdir/$keyfile -N \"\"");
775 ### copySSHDConfigFile( )
777 # this subroutine 'edits' the paths in sshd_config to suit them to the current environment
778 # in which the setup script is being run.
781 sub copySSHDConfigFile
783 my($fileInput, $fileOutput);
784 my($mode, $uid, $gid);
786 my($privsep_enabled);
788 print "Fixing paths in sshd_config...\n";
790 $fileInput = "$setupdir/sshd_config.in";
791 $fileOutput = "$sysconfdir/sshd_config";
794 # verify that we are prepared to work with $fileInput
797 if ( !isReadable($fileInput) )
799 printf("Cannot read $fileInput... skipping.\n");
804 # verify that we are prepared to work with $fileOuput
807 if ( !prepareFileWrite($fileOutput) )
813 # check to see whether we should enable privilege separation
816 if ( userExists("sshd") && ( -d "/var/empty" ) && ( getOwnerID("/var/empty") eq 0 ) )
818 setPrivilegeSeparation(1);
822 setPrivilegeSeparation(0);
825 if ( getPrivilegeSeparation() )
827 $privsep_enabled = "yes";
831 $privsep_enabled = "no";
835 # Grab the current mode/uid/gid for use later
838 $mode = (stat($fileInput))[2];
839 $uid = (stat($fileInput))[4];
840 $gid = (stat($fileInput))[5];
843 # Open the files for reading and writing, and loop over the input's contents
846 $data = readFile($fileInput);
849 # # alter the PidFile config
852 # $text = "PidFile\t$gpath/var/sshd.pid";
853 # $data =~ s:^[\s|#]*PidFile.*$:$text:gm;
856 # set the sftp directive
859 $text = "Subsystem\tsftp\t$gpath/libexec/sftp-server";
860 $data =~ s:^[\s|#]*Subsystem\s+sftp\s+.*$:$text:gm;
863 # set the privilege separation directive
866 $text = "UsePrivilegeSeparation\t${privsep_enabled}";
867 $data =~ s:^[\s|#]*UsePrivilegeSeparation.*$:$text:gm;
870 # dump the modified output to the config file
873 writeFile($fileOutput, $data);
876 # An attempt to revert the new file back to the original file's
880 chmod($mode, $fileOutput);
881 chown($uid, $gid, $fileOutput);
886 ### setPrivilegeSeparation( $value )
888 # set the privilege separation variable to $value
891 sub setPrivilegeSeparation
898 ### getPrivilegeSeparation( )
900 # return the value of the privilege separation variable
903 sub getPrivilegeSeparation
908 ### prepareFileWrite( $file )
910 # test $file to prepare for writing to it.
917 if ( isPresent($file) )
919 printf("$file already exists... ");
923 if ( isWritable($file) )
925 printf("removing.\n");
931 printf("not writable -- skipping.\n");
937 printf("skipping.\n");
945 ### copyConfigFiles( )
947 # subroutine that copies some extra config files to their proper location in
948 # $GLOBUS_LOCATION/etc/ssh.
954 # copy the sshd_config file into the ssh configuration directory and alter
955 # the paths in the file.
958 copySSHDConfigFile();
961 # do straight copies of the ssh_config and moduli files.
964 printf("Copying ssh_config and moduli to their proper location...\n");
966 copyFile("$setupdir/ssh_config", "$sysconfdir/ssh_config");
967 copyFile("$setupdir/moduli", "$sysconfdir/moduli");
970 # copy and alter the SXXsshd script.
973 copySXXScript("$setupdir/SXXsshd.in", "$sbindir/SXXsshd");
976 ### copyFile( $src, $dest )
978 # copy the file pointed to by $src to the location specified by $dest. in the
979 # process observe the rules regarding when the '-force' flag was passed to us.
984 my($src, $dest) = @_;
986 if ( !isReadable($src) )
988 printf("$src is not readable... not creating $dest.\n");
992 if ( !prepareFileWrite($dest) )
997 action("cp $src $dest");
1000 ### copySXXScript( $in, $out )
1002 # parse the input file, substituting in place the value of GLOBUS_LOCATION, and
1003 # write the result to the output file.
1011 if ( !isReadable($in) )
1013 printf("$in is not readable... not creating $out.\n");
1017 if ( !prepareFileWrite($out) )
1023 # clean up any junk in the globus path variable
1027 $tmpgpath =~ s:/+:/:g;
1028 $tmpgpath =~ s:([^/]+)/$:\1:g;
1031 # read in the script, substitute globus location, then write it back out
1034 $data = readFile($in);
1035 $data =~ s|\@GLOBUS_LOCATION\@|$tmpgpath|g;
1036 writeFile($out, $data);
1037 action("chmod 755 $out");
1040 ### readFile( $filename )
1042 # reads and returns $filename's contents
1050 open(IN, "$filename") || exitDie "Can't open '$filename': $!";
1059 ### writeFile( $filename, $fileinput )
1061 # create the inputs to the ssl program at $filename, appending the common name to the
1062 # stream in the process
1067 my($filename, $fileinput) = @_;
1070 # test for a valid $filename
1073 if ( !defined($filename) || (length($filename) lt 1) )
1075 exitDie "Filename is undefined";
1079 # verify that we are prepared to work with $filename
1082 if ( !prepareFileWrite($filename) )
1088 # write the output to $filename
1091 open(OUT, ">$filename");
1092 print OUT "$fileinput";
1096 ### action( $command )
1098 # run $command within a proper system() command.
1105 printf "$command\n";
1107 my $result = system("LD_LIBRARY_PATH=\"$gpath/lib:\$LD_LIBRARY_PATH\"; $command 2>&1");
1109 if (($result or $?) and $command !~ m!patch!)
1111 exitDie "ERROR: Unable to execute command: $!\n";
1115 ### exitDie( $error )
1117 # a horribly named method meant to look like die but only exit, thereby not causing
1118 # gpt-postinstall to croak.
1129 ### query_boolean( $query_text, $default )
1131 # query the user with a string, and expect a response. If the user hits
1132 # 'enter' instead of entering an input, then accept the default response.
1137 my($query_text, $default) = @_;
1138 my($nondefault, $foo, $bar);
1142 print "Prompt suppressed. Continuing...\n";
1147 # Set $nondefault to the boolean opposite of $default.
1150 if ($default eq "n")
1159 print "${query_text} ";
1160 print "[$default] ";
1163 ($bar) = split //, $foo;
1165 if ( grep(/\s/, $bar) )
1167 # this is debatable. all whitespace means 'default'
1175 elsif ($bar ne $default)
1177 # everything else means 'nondefault'.
1183 # extraneous step. to get here, $bar should be eq to $default anyway.
1191 ### absolutePath( $file )
1193 # converts a given pathname into a canonical path using the abs_path function.
1199 my $home = $ENV{'HOME'};
1200 $file =~ s!~!$home!;
1202 $file =~ s!^\./!$startd/!;
1203 $file = "$startd/$file" if $file !~ m!^\s*/!;
1204 $file = abs_path($file);
1208 ### getOwnerID( $file )
1210 # return the uid containing the owner ID of the given file.
1219 # call stat() to get the mode of the file
1222 $uid = (stat($file))[4];
1227 ### getMode( $file )
1229 # return a string containing the mode of the given file.
1235 my($tempmode, $mode);
1238 # call stat() to get the mode of the file
1241 $tempmode = (stat($file))[2];
1242 if (length($tempmode) < 1)
1248 # call sprintf to format the mode into a UNIX-like string
1251 $mode = sprintf("%04o", $tempmode & 07777);
1256 ### userExists( $username )
1258 # given a username, return true if the user exists on the system. return false
1268 # retrieve the userid of the user with the given username
1271 $uid = getpwnam($username);
1274 # return true if $uid is defined and has a length greater than 0
1277 if ( defined($uid) and (length($uid) > 0) )