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 # miscellaneous initialization functions
112 setPrivilegeSeparation(0);
115 # main execution. This should find its way into a subroutine at some future
119 print "$myname: Configuring package 'gsi_openssh'...\n";
120 print "---------------------------------------------------------------------\n";
121 print "Hi, I'm the setup script for the gsi_openssh package! I will create\n";
122 print "a number of configuration files based on your local system setup. I\n";
123 print "will also attempt to copy or create a number of SSH key pairs for\n";
124 print "this machine. (Loosely, if I find a pair of host keys in /etc/ssh,\n";
125 print "I will copy them into \$GLOBUS_LOCATION/etc/ssh. Otherwise, I will\n";
126 print "generate them for you.)\n";
128 print " Jacobim Mugatu says,\n";
129 print " \"Utopian Prime Minister Bad! GSI-OpenSSH Good!\"\n";
136 print " Using the '-force' flag will cause all gsi_openssh_setup files to\n";
137 print " be removed and replaced by new versions! Backup any critical\n";
138 print " SSH configuration files before you choose to continue!\n";
142 $response = query_boolean("Do you wish to continue with the setup package?","y");
143 if ($response eq "n")
146 print "Exiting gsi_openssh setup.\n";
155 $keyhash = determineKeys();
156 runKeyGen($keyhash->{gen});
157 copyKeyFiles($keyhash->{copy});
160 my $metadata = new Grid::GPT::Setup(package_name => "gsi_openssh_setup");
165 print "Additional Notes:\n";
167 print " o I see that you have your GLOBUS_LOCATION environmental variable\n";
170 print " \"$gpath\"\n";
172 print " Remember to keep this variable set (correctly) when you want to\n";
173 print " use the executables that came with this package.\n";
175 print " After that you may execute, for example:\n";
177 print " \$ . \$GLOBUS_LOCATION/etc/globus-user-env.sh\n";
179 print " to prepare your environment for running the gsi_openssh\n";
180 print " executables.\n";
182 print " o I recommend you review and customize to your liking the contents of\n";
184 print " \$GLOBUS_LOCATION/etc/ssh\n";
186 print " \"I can only show you the door. You have to walk through it.\"\n";
188 if ( !getPrivilegeSeparation() )
191 print " o For System Administrators:\n";
193 print " If you are going to run the GSI-OpenSSH server, we recommend\n";
194 print " enabling privilege separation. Although this package supports\n";
195 print " this feature, your system appears to require some additional\n";
196 print " configuration.\n";
198 print " From the file README.privsep, included as a part of the OpenSSH\n";
199 print " distribution:\n";
201 print " When privsep is enabled, during the pre-authentication\n";
202 print " phase sshd will chroot(2) to \"/var/empty\" and change its\n";
203 print " privileges to the \"sshd\" user and its primary group. sshd\n";
204 print " is a pseudo-account that should not be used by other\n";
205 print " daemons, and must be locked and should contain a \"nologin\"\n";
206 print " or invalid shell.\n";
208 print " You should do something like the following to prepare the\n";
209 print " privsep preauth environment:\n";
211 print " \# mkdir /var/empty\n";
212 print " \# chown root:sys /var/empty\n";
213 print " \# chmod 755 /var/empty\n";
214 print " \# groupadd sshd\n";
215 print " \# useradd -g sshd -c 'sshd privsep' -d /var/empty \\\n";
216 print " -s /bin/false sshd\n";
218 print " /var/empty should not contain any files.\n";
222 print " o For more information about GSI-Enabled OpenSSH, visit:\n";
223 print " <http://www.ncsa.uiuc.edu/Divisions/ACES/GSI/openssh/>\n";
226 # give the user a chance to read all of this output
230 print "Press <return> to continue... ";
233 print "---------------------------------------------------------------------\n";
234 print "$myname: Finished configuring package 'gsi_openssh'.\n";
244 # initialize the PRNG pathname hash
250 # standard prng to executable conversion names
253 addPRNGCommand("\@PROG_LS\@", "ls");
254 addPRNGCommand("\@PROG_NETSTAT\@", "netstat");
255 addPRNGCommand("\@PROG_ARP\@", "arp");
256 addPRNGCommand("\@PROG_IFCONFIG\@", "ifconfig");
257 addPRNGCommand("\@PROG_PS\@", "ps");
258 addPRNGCommand("\@PROG_JSTAT\@", "jstat");
259 addPRNGCommand("\@PROG_W\@", "w");
260 addPRNGCommand("\@PROG_WHO\@", "who");
261 addPRNGCommand("\@PROG_LAST\@", "last");
262 addPRNGCommand("\@PROG_LASTLOG\@", "lastlog");
263 addPRNGCommand("\@PROG_DF\@", "df");
264 addPRNGCommand("\@PROG_SAR\@", "sar");
265 addPRNGCommand("\@PROG_VMSTAT\@", "vmstat");
266 addPRNGCommand("\@PROG_UPTIME\@", "uptime");
267 addPRNGCommand("\@PROG_IPCS\@", "ipcs");
268 addPRNGCommand("\@PROG_TAIL\@", "tail");
270 print "Determining paths for PRNG commands...\n";
272 $paths = determinePRNGPaths();
277 ### getDirectoryPaths( )
279 # return an array ref containing all of the directories in which we should search
280 # for our listing of executable names.
283 sub getDirectoryPaths( )
286 # read in the PATH environmental variable and prepend a set of 'safe'
287 # directories from which to test PRNG commands.
291 $path = "/bin:/usr/bin:/sbin:/usr/sbin:/etc:" . $path;
292 @dirs = split(/:/, $path);
295 # sanitize each directory listed in the array.
301 $tmp =~ s:^\s+|\s+$::g;
308 ### addPRNGCommand( $prng_name, $exec_name )
310 # given a PRNG name and a corresponding executable name, add it to our list of
311 # PRNG commands for which to find on the system.
316 my($prng_name, $exec_name) = @_;
318 prngAddNode($prng_name, $exec_name);
323 # read in ssh_prng_cmds.in, translate the program listings to the paths we have
324 # found on the local system, and then write the output to ssh_prng_cmds.
329 my($fileInput, $fileOutput);
330 my($mode, $uid, $gid);
333 if ( isPresent("$sysconfdir/ssh_prng_cmds") && !isForced() )
335 printf("ssh_prng_cmds found and not forced. Not installing ssh_prng_cmds...\n");
341 print "Fixing paths in ssh_prng_cmds...\n";
343 $fileInput = "$setupdir/ssh_prng_cmds.in";
344 $fileOutput = "$sysconfdir/ssh_prng_cmds";
347 # verify that we are prepared to work with $fileInput
350 if ( !isReadable($fileInput) )
352 printf("Cannot read $fileInput... skipping.\n");
357 # verify that we are prepared to work with $fileOuput
360 if ( !prepareFileWrite($fileOutput) )
366 # Grab the current mode/uid/gid for use later
369 $mode = (stat($fileInput))[2];
370 $uid = (stat($fileInput))[4];
371 $gid = (stat($fileInput))[5];
374 # Open the files for reading and writing, and loop over the input's contents
377 $data = readFile($fileInput);
378 for my $k (keys %$prngcmds)
380 $sub = prngGetExecPath($k);
381 $data =~ s:$k:$sub:g;
383 writeFile($fileOutput, $data);
386 # An attempt to revert the new file back to the original file's
390 chmod($mode, $fileOutput);
391 chown($uid, $gid, $fileOutput);
396 ### determinePRNGPaths( )
398 # for every entry in the PRNG hash, seek out and find the path for the
399 # corresponding executable name.
402 sub determinePRNGPaths
405 my($exec_name, $exec_path);
407 $dirs = getDirectoryPaths();
409 for my $k (keys %$prngcmds)
411 $exec_name = prngGetExecName($k);
412 $exec_path = findExecutable($exec_name, $dirs);
413 prngSetExecPath($k, $exec_path);
419 ### prngAddNode( $prng_name, $exec_name )
421 # add a new node to the PRNG hash
426 my($prng_name, $exec_name) = @_;
429 if (!defined($prngcmds))
435 $node->{prng} = $prng_name;
436 $node->{exec} = $exec_name;
438 $prngcmds->{$prng_name} = $node;
441 ### prngGetExecName( $key )
443 # get the executable name from the prng commands hash named by $key
450 return $prngcmds->{$key}->{exec};
453 ### prngGetExecPath( $key )
455 # get the executable path from the prng commands hash named by $key
462 return $prngcmds->{$key}->{exec_path};
465 ### prngGetNode( $key )
467 # return a reference to the node named by $key
474 return ${$prngcmds}{$key};
477 ### prngSetExecPath( $key, $path )
479 # given a key, set the executable path in that node to $path
484 my($key, $path) = @_;
486 $prngcmds->{$key}->{exec_path} = $path;
489 ### findExecutable( $exec_name, $dirs )
491 # given an executable name, test each possible path in $dirs to see if such
492 # an executable exists.
497 my($exec_name, $dirs) = @_;
501 $test = "$d/$exec_name";
503 if ( isExecutable($test) )
512 ### copyKeyFiles( $copylist )
514 # given an array of keys to copy, copy both the key and its public variant into
515 # the gsi-openssh configuration directory.
521 my($regex, $basename);
525 print "Copying ssh host keys...\n";
527 for my $f (@$copylist)
534 $pubkeyfile = "$f.pub";
536 copyFile("$localsshdir/$keyfile", "$sysconfdir/$keyfile");
537 copyFile("$localsshdir/$pubkeyfile", "$sysconfdir/$pubkeyfile");
545 # return true if the user passed in the force flag. return false otherwise.
550 if ( defined($force) && $force )
560 ### isReadable( $file )
562 # given a file, return true if that file both exists and is readable by the
563 # effective user id. return false otherwise.
570 if ( ( -e $file ) && ( -r $file ) )
580 ### isExecutable( $file )
582 # return true if $file is executable. return false otherwise.
599 ### isWritable( $file )
601 # given a file, return true if that file does not exist or is writable by the
602 # effective user id. return false otherwise.
609 if ( ( ! -e $file ) || ( -w $file ) )
619 ### isPresent( $file )
621 # given a file, return true if that file exists. return false otherwise.
640 # make the gsi-openssh configuration directory if it doesn't already exist.
645 if ( isPresent($sysconfdir) )
647 if ( -d $sysconfdir )
652 die("${sysconfdir} already exists and is not a directory!\n");
655 print "Could not find ${sysconfdir} directory... creating.\n";
656 action("mkdir -p $sysconfdir");
663 # based on a set of key types, triage them to determine if for each key type, that
664 # key type should be copied from the main ssh configuration directory, or if it
665 # should be generated using ssh-keygen.
670 my($keyhash, $keylist);
674 # initialize our variables
680 $keyhash->{gen} = []; # a list of keytypes to generate
681 $keyhash->{copy} = []; # a list of files to copy from the
683 $genlist = $keyhash->{gen};
684 $copylist = $keyhash->{copy};
687 # loop over our keytypes and determine what we need to do for each of them
690 for my $keytype (keys %$keyfiles)
692 $basekeyfile = $keyfiles->{$keytype};
695 # if the key's are already present, we don't need to bother with this rigamarole
698 $gkeyfile = "$sysconfdir/$basekeyfile";
699 $gpubkeyfile = "$sysconfdir/$basekeyfile.pub";
701 if ( isPresent($gkeyfile) && isPresent($gpubkeyfile) )
705 if ( isWritable("$sysconfdir/$basekeyfile") && isWritable("$sysconfdir/$basekeyfile.pub") )
707 action("rm $sysconfdir/$basekeyfile");
708 action("rm $sysconfdir/$basekeyfile.pub");
718 # if we can find a copy of the keys in /etc/ssh, we'll copy them to the user's
722 $mainkeyfile = "$localsshdir/$basekeyfile";
723 $mainpubkeyfile = "$localsshdir/$basekeyfile.pub";
725 if ( isReadable($mainkeyfile) && isReadable($mainpubkeyfile) )
727 push(@$copylist, $basekeyfile);
733 # otherwise, we need to generate the key
736 push(@$genlist, $keytype);
743 ### runKeyGen( $gen_keys )
745 # given a set of key types, generate private and public keys for that key type and
746 # place them in the gsi-openssh configuration directory.
752 my $keygen = "$bindir/ssh-keygen";
754 if (@$gen_keys && -x $keygen)
756 print "Generating ssh host keys...\n";
758 for my $k (@$gen_keys)
760 $keyfile = $keyfiles->{$k};
762 if ( !isPresent("$sysconfdir/$keyfile") )
764 action("$bindir/ssh-keygen -t $k -f $sysconfdir/$keyfile -N \"\"");
772 ### copySSHDConfigFile( )
774 # this subroutine 'edits' the paths in sshd_config to suit them to the current environment
775 # in which the setup script is being run.
778 sub copySSHDConfigFile
780 my($fileInput, $fileOutput);
781 my($mode, $uid, $gid);
783 my($privsep_enabled);
785 print "Fixing paths in sshd_config...\n";
787 $fileInput = "$setupdir/sshd_config.in";
788 $fileOutput = "$sysconfdir/sshd_config";
791 # verify that we are prepared to work with $fileInput
794 if ( !isReadable($fileInput) )
796 printf("Cannot read $fileInput... skipping.\n");
801 # verify that we are prepared to work with $fileOuput
804 if ( !prepareFileWrite($fileOutput) )
810 # check to see whether we should enable privilege separation
813 if ( userExists("sshd") && ( -d "/var/empty" ) && ( getOwnerID("/var/empty") eq 0 ) )
815 setPrivilegeSeparation(1);
819 setPrivilegeSeparation(0);
822 if ( getPrivilegeSeparation() )
824 $privsep_enabled = "yes";
828 $privsep_enabled = "no";
832 # Grab the current mode/uid/gid for use later
835 $mode = (stat($fileInput))[2];
836 $uid = (stat($fileInput))[4];
837 $gid = (stat($fileInput))[5];
840 # Open the files for reading and writing, and loop over the input's contents
843 $data = readFile($fileInput);
846 # alter the PidFile config
849 $text = "PidFile\t$gpath/var/sshd.pid";
850 $data =~ s:^[\s|#]*PidFile.*$:$text:gm;
853 # set the sftp directive
856 $text = "Subsystem\tsftp\t$gpath/libexec/sftp-server";
857 $data =~ s:^[\s|#]*Subsystem\s+sftp\s+.*$:$text:gm;
860 # set the privilege separation directive
863 $text = "UsePrivilegeSeparation\t${privsep_enabled}";
864 $data =~ s:^[\s|#]*UsePrivilegeSeparation.*$:$text:gm;
867 # dump the modified output to the config file
870 writeFile($fileOutput, $data);
873 # An attempt to revert the new file back to the original file's
877 chmod($mode, $fileOutput);
878 chown($uid, $gid, $fileOutput);
883 ### setPrivilegeSeparation( $value )
885 # set the privilege separation variable to $value
888 sub setPrivilegeSeparation
895 ### getPrivilegeSeparation( )
897 # return the value of the privilege separation variable
900 sub getPrivilegeSeparation
905 ### prepareFileWrite( $file )
907 # test $file to prepare for writing to it.
914 if ( isPresent($file) )
916 printf("$file already exists... ");
920 if ( isWritable($file) )
922 printf("removing.\n");
928 printf("not writable -- skipping.\n");
934 printf("skipping.\n");
942 ### copyConfigFiles( )
944 # subroutine that copies some extra config files to their proper location in
945 # $GLOBUS_LOCATION/etc/ssh.
951 # copy the sshd_config file into the ssh configuration directory and alter
952 # the paths in the file.
955 copySSHDConfigFile();
958 # do straight copies of the ssh_config and moduli files.
961 printf("Copying ssh_config and moduli to their proper location...\n");
963 copyFile("$setupdir/ssh_config", "$sysconfdir/ssh_config");
964 copyFile("$setupdir/moduli", "$sysconfdir/moduli");
967 # copy and alter the SXXsshd script.
970 copySXXScript("$setupdir/SXXsshd.in", "$sbindir/SXXsshd");
973 ### copyFile( $src, $dest )
975 # copy the file pointed to by $src to the location specified by $dest. in the
976 # process observe the rules regarding when the '-force' flag was passed to us.
981 my($src, $dest) = @_;
983 if ( !isReadable($src) )
985 printf("$src is not readable... not creating $dest.\n");
989 if ( !prepareFileWrite($dest) )
994 action("cp $src $dest");
997 ### copySXXScript( $in, $out )
999 # parse the input file, substituting in place the value of GLOBUS_LOCATION, and
1000 # write the result to the output file.
1008 if ( !isReadable($in) )
1010 printf("$in is not readable... not creating $out.\n");
1014 if ( !prepareFileWrite($out) )
1020 # clean up any junk in the globus path variable
1024 $tmpgpath =~ s:/+:/:g;
1025 $tmpgpath =~ s:([^/]+)/$:\1:g;
1028 # read in the script, substitute globus location, then write it back out
1031 $data = readFile($in);
1032 $data =~ s|\@GLOBUS_LOCATION\@|$tmpgpath|g;
1033 writeFile($out, $data);
1034 action("chmod 755 $out");
1037 ### readFile( $filename )
1039 # reads and returns $filename's contents
1047 open(IN, "$filename") || die "Can't open '$filename': $!";
1056 ### writeFile( $filename, $fileinput )
1058 # create the inputs to the ssl program at $filename, appending the common name to the
1059 # stream in the process
1064 my($filename, $fileinput) = @_;
1067 # test for a valid $filename
1070 if ( !defined($filename) || (length($filename) lt 1) )
1072 die "Filename is undefined";
1076 # verify that we are prepared to work with $filename
1079 if ( !prepareFileWrite($filename) )
1085 # write the output to $filename
1088 open(OUT, ">$filename");
1089 print OUT "$fileinput";
1093 ### action( $command )
1095 # run $command within a proper system() command.
1102 printf "$command\n";
1104 my $result = system("LD_LIBRARY_PATH=\"$gpath/lib:\$LD_LIBRARY_PATH\"; $command 2>&1");
1106 if (($result or $?) and $command !~ m!patch!)
1108 die "ERROR: Unable to execute command: $!\n";
1112 ### query_boolean( $query_text, $default )
1114 # query the user with a string, and expect a response. If the user hits
1115 # 'enter' instead of entering an input, then accept the default response.
1120 my($query_text, $default) = @_;
1121 my($nondefault, $foo, $bar);
1124 # Set $nondefault to the boolean opposite of $default.
1127 if ($default eq "n")
1136 print "${query_text} ";
1137 print "[$default] ";
1140 ($bar) = split //, $foo;
1142 if ( grep(/\s/, $bar) )
1144 # this is debatable. all whitespace means 'default'
1148 elsif ($bar ne $default)
1150 # everything else means 'nondefault'.
1156 # extraneous step. to get here, $bar should be eq to $default anyway.
1164 ### absolutePath( $file )
1166 # converts a given pathname into a canonical path using the abs_path function.
1172 my $home = $ENV{'HOME'};
1173 $file =~ s!~!$home!;
1175 $file =~ s!^\./!$startd/!;
1176 $file = "$startd/$file" if $file !~ m!^\s*/!;
1177 $file = abs_path($file);
1181 ### getOwnerID( $file )
1183 # return the uid containing the owner ID of the given file.
1192 # call stat() to get the mode of the file
1195 $uid = (stat($file))[4];
1200 ### getMode( $file )
1202 # return a string containing the mode of the given file.
1208 my($tempmode, $mode);
1211 # call stat() to get the mode of the file
1214 $tempmode = (stat($file))[2];
1215 if (length($tempmode) < 1)
1221 # call sprintf to format the mode into a UNIX-like string
1224 $mode = sprintf("%04o", $tempmode & 07777);
1229 ### userExists( $username )
1231 # given a username, return true if the user exists on the system. return false
1241 # retrieve the userid of the user with the given username
1244 $uid = getpwnam($username);
1247 # return true if $uid is defined and has a length greater than 0
1250 if ( defined($uid) and (length($uid) > 0) )