fusiondirectory-setup 67.37 KiB
#!/usr/bin/perl
########################################################################
#  fusiondirectory-setup
#  Manage fusiondirectory installs from the command line
#  This code is part of FusionDirectory (http://www.fusiondirectory.org/)
#  Copyright (C) 2011-2017  FusionDirectory
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
########################################################################
use strict;
use warnings;
use 5.008;
# used to manage files
use Path::Class;
# used for checking config dirs rights (make the translation for lstat output)
use Fcntl ':mode';
# used to handle ldap connections
use Net::LDAP;
# used to base64 encode
use MIME::Base64;
# used to generate {SSHA} password (for LDAP)
use Digest::SHA;
use Crypt::CBC;
# used to uncompress tar.gz
use Archive::Extract;
# used to copy files
use File::Copy::Recursive qw(rcopy);
#XML parser
use XML::Twig;
# To hide password input
use Term::ReadKey;
use Data::Dumper;
# fd's directory and class.cache file's path declaration
my %vars = (
 fd_home          => "/var/www/fusiondirectory",
 fd_cache         => "/var/cache/fusiondirectory",
 fd_config_dir    => "/etc/fusiondirectory",
 fd_smarty_dir    => "/usr/share/php/smarty3",
 fd_spool_dir     => "/var/spool/fusiondirectory",
 ldap_conf        => "/etc/ldap/ldap.conf",
 config_file      => "fusiondirectory.conf",
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
secrets_file => "fusiondirectory.secrets", locale_dir => "locale", class_cache => "class.cache", locale_cache_dir => "locale", tmp_dir => "tmp", fai_log_dir => "fai", template_dir => "template" ); my ($fd_config,$fd_secrets,$locale_dir,$class_cache,$locale_cache_dir,$tmp_dir,$fai_log_dir,$template_dir); my (@root_config_dirs,@apache_config_dirs,@config_dirs); my @plugin_types = qw(addons admin personal); my $yes_flag = 0; my %classes_hash_result = (); my %i18n_hash_result = (); my $configrdn = "cn=config,ou=fusiondirectory"; my $userrdn = "ou=people"; my $aclrolerdn = "ou=aclroles"; my $grouprdn = "ou=groups"; my $systemrdn = "ou=systems"; my $dnsrdn = "ou=dns"; my $dhcprdn = "ou=dhcp"; my $workstationrdn = "ou=workstations,ou=systems"; my $winstationrdn = "ou=computers,ou=systems"; ################################################################################################################################################# # ask a question send as parameter, and return true if the answer is "yes" sub ask_yn_question { return 1 if ($yes_flag); my ($question) = @_; print ( "$question [Yes/No]?\n" ); while ( my $input = <STDIN> ) { # remove the \n at the end of $input chomp $input; # if user's answer is "yes" if ( lc($input) eq "yes" || lc($input) eq "y") { return 1; # else if he answer "no" } elsif ( lc($input) eq "no" || lc($input) eq "n") { return 0; } } } # function that ask for an user input and do some checks sub ask_user_input { my ($thing_to_ask, $default_answer, $hide_input) = @_; my $answer; if (defined $default_answer) { $thing_to_ask .= " [$default_answer]"; } print $thing_to_ask.":\n"; if (defined $hide_input && $hide_input) { ReadMode('noecho'); } do { if ($answer = <STDIN>) { chomp $answer; $answer =~ s/^\s+|\s+$//g;
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
} else { $answer = ""; } } while (($answer eq "") && (not defined $default_answer)); ReadMode('restore'); if ($answer eq "") { return $default_answer; } return $answer; } # Die on all LDAP error except for «No such object» sub die_on_ldap_errors { my ($mesg) = @_; if (($mesg->code != 0) && ($mesg->code != 32)) { die $mesg->error; } } { my $indice = 0; sub find_free_role_dn { my ($ldap,$base,$prefix) = @_; my ($cn,$dn,$mesg); do { $cn = $prefix.'-'.$indice; $dn = "cn=$cn,$aclrolerdn,$base"; $indice++; $mesg = $ldap->search( base => "$dn", scope => 'base', filter => '(objectClass=*)' ); die_on_ldap_errors($mesg); } while ($mesg->count); return $cn; } } sub create_role { my ($ldap,$base,$cn,$acl) = @_; my %role = ( 'cn' => "$cn", 'objectclass' => [ 'top', 'gosaRole' ], 'gosaAclTemplate' => "0:$acl" ); if (!branch_exists($ldap, "$aclrolerdn,$base")) { create_branch($ldap, $base, $aclrolerdn); } my $role_dn = "cn=$cn,$aclrolerdn,$base"; # Add the administator role object my @options = %role; my $role_add = $ldap->add( $role_dn, attr => \@options ); # send a warning if the ldap's admin's add didn't gone well $role_add->code && die "\n! failed to add LDAP's $role_dn entry - ".$role_add->error_name.": ".$role_add->error_text; return $role_dn; } ###################################################### Password encryption ######################################################################### sub cred_encrypt { my ($input, $password) = @_; my $cipher = Crypt::CBC->new( -key => $password, -cipher => 'Rijndael',
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
-salt => 1, -header => 'salt', ) || die "Couldn't create CBC object"; return $cipher->encrypt_hex($input); } sub cred_decrypt { my ($input, $password) = @_; my $cipher = Crypt::CBC->new( -key => $password, -cipher => 'Rijndael', -salt => 1, -header => 'salt', ) || die "Couldn't create CBC object"; return $cipher->decrypt_hex($input); } sub get_random_string { my ($size) = @_; $size = 32 if !$size; my @chars = ("A".."Z", "a".."z", '.', '/', 0..9); my $string; $string .= $chars[rand @chars] for 1..$size; return $string; } sub encrypt_passwords { if (!-e $fd_config) { die "Cannot find a valid configuration file ($fd_config)!\n"; } if (-e $fd_secrets) { die "There's already a file '$fd_secrets'. Cannot convert your existing fusiondirectory.conf - aborted\n"; } print "Starting password encryption\n"; print "* generating random master key\n"; my $master_key = get_random_string(); print "* creating '$fd_secrets'\n"; my $fp_file = file($fd_secrets); my $fp = $fp_file->openw() or die "! Unable to open '$fd_secrets' in write mode\n"; $fp->print("RequestHeader set FDKEY $master_key\n"); $fp->close or die "! Can't close '$fd_secrets'\n"; chmod 0600, $fd_secrets or die "! Unable to change '$fd_secrets' rights\n"; my $root_uid = getpwnam("root"); my $root_gid = getgrnam("root"); chown $root_uid,$root_gid,$fd_secrets or die "! Unable to change '$fd_secrets' owner\n"; # Move original fusiondirectory.conf out of the way and make it unreadable for the web user print "* creating backup in '$fd_config.orig'\n"; rcopy($fd_config, "$fd_config.orig"); chmod 0600, "$fd_config.orig" or die "! Unable to change '$fd_config.orig' rights\n"; chown $root_uid,$root_gid,"$fd_config.orig" or die "! Unable to change '$fd_config.orig' owner\n"; print "* loading '$fd_config'\n"; my $twig = XML::Twig->new(); # create the twig $twig->parsefile($fd_config); # build it # Locate all passwords inside the fusiondirectory.conf my @locs = $twig->root->first_child('main')->children('location'); foreach my $loc (@locs) { my $ref = $loc->first_child('referral'); print "* encrypting FusionDirectory password for: ".$ref->{'att'}->{'adminDn'}."\n"; $ref->set_att('adminPassword' => cred_encrypt($ref->{'att'}->{'adminPassword'}, $master_key)); } # Save print "* saving modified '$fd_config'\n"; $twig->print_to_file($fd_config, pretty_print => 'indented') or die "Cannot write modified $fd_config - aborted\n"; print "OK\n\n"; # Print reminder print "
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
Please adapt your http fusiondirectory location declaration to include the newly created $fd_secrets. Example: Alias /fusiondirectory /usr/share/fusiondirectory/html <Location /fusiondirectory> php_admin_flag engine on php_admin_flag register_globals off php_admin_flag allow_call_time_pass_reference off php_admin_flag expose_php off php_admin_flag zend.ze1_compatibility_mode off php_admin_flag register_long_arrays off php_admin_value upload_tmp_dir /var/spool/fusiondirectory/ php_admin_value session.cookie_lifetime 0 include /etc/fusiondirectory/fusiondirectory.secrets </Location> Please reload your httpd configuration after you've modified anything.\n"; } ####################################################### class.cache update ######################################################################### # function that scan recursivly a directory to find .inc and . php # then return a hash with class => path to the class file sub get_classes { my ($path) = @_; # if this function has been called without a parameter die ("! function get_classes called without parameter\n") if ( !defined($path) ); # create a "dir" object with the path my $dir = dir ($path) or die ("! Can't open $path\n"); my $contrib_dir = dir($vars{fd_home},"contrib"); if ("$dir" eq "$contrib_dir") { return; } # create an array with the content of $dir my @dir_files = $dir->children; foreach my $file (@dir_files) { # recursive call if $file is a directory if ( -d $file ) { get_classes($file); next; } # only process if $file is a .inc or a .php file if ( ( $file =~ /.*\.inc$/ ) && ( $file !~ /.*smarty.*/ ) ) { # put the entire content of the file pointed by $file in $data my @lines = $file->slurp; # modifing $file, to contains relative path, not complete one $file =~ s/^$vars{fd_home}//; foreach my $line ( @lines ) { # remove \n from the end of each line chomp $line; # only process for lines beginning with "class", and extracting the 2nd word (the class name) if ( $line =~ /^class\s*(\w+).*/ ) { # adding the values (class name and file path) to the hash $classes_hash_result{$1} = $file; } }
351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
} } return %classes_hash_result; } # call get_classes and create /var/cache/fusiondirectory/class.cache sub rescan_classes { # hash that will contain the result of the "get_classes" function my %get_classes_result = get_classes ($vars{fd_home}); # create a "file" object with the $class_cache path my $file_class = file ($class_cache); # create the handler (write mode) for the file previoulsy created my $fhw = $file_class->openw() or die ("! Unable to open $class_cache in write mode\n"); # first lines of class.cache $fhw->print ("<?php\n\t\$class_mapping= array(\n"); # for each $key/$value, writting a new line to $class_cache while ( my($key,$value) = each %get_classes_result ) { $fhw->print ("\t\t\"$key\" => \"$value\",\n"); } # last line of classe.cache $fhw->print ("\t);\n?>"); $fhw->close or die ("! Can't close $class_cache\n"); } ###################################################### Internalisation's update #################################################################################### # function that create .mo files with .po for each language sub get_i18n { my ($path) = @_; # if this function has been called without a parameter die ("! function get_i18n called without parameter" ) if ( !defined($path) ); # create a "dir" object my $dir = dir ($path) or die ("! Can't open $path\n"); # create an array with the content of $dir my @dir_files = $dir->children; foreach my $file (@dir_files) { # recursive call if $file is a directory if (-d $file) { %i18n_hash_result = get_i18n ($file); next; } # if the file's directory is ???/language/fusiondirectory.po if ($file =~ qr{^.*/(\w+)/fusiondirectory.po$}) { # push the file's path in the language (fr/en/es/it...) array (wich is inside the hash pointed by $ref_result push @{$i18n_hash_result{$1}}, $file; } } return %i18n_hash_result; } # call get_i18n with the FusionDirectory's locales's directory and the hash that will contain the result in parameter sub rescan_i18n { # hash that will contain the result of the "get_i18n" function my %get_i18n_result = get_i18n ($locale_dir); while ( my ($lang, $files) = each %get_i18n_result ) {
421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
# directory wich will contain the .mo file for each language my $lang_cache_dir = dir ("$locale_cache_dir/$lang/LC_MESSAGES"); # if $lang_cache_dir doesn't already exists, creating it if ( !-d $lang_cache_dir ) { $lang_cache_dir->mkpath or die ("! Can't create $locale_cache_dir/$lang/LC_MESSAGES"); } # glue .po files's names my $po_files = join(" ", @{$files}); chomp $po_files; # merging .po files system ( "msgcat --use-first ".$po_files.">".$lang_cache_dir."/fusiondirectory.po" ) and die ("! Unable to merge .po files for $lang with msgcat, is it already installed?\n"); # compiling .po files in .mo files system ( "msgfmt -o $lang_cache_dir/fusiondirectory.mo $lang_cache_dir/fusiondirectory.po && rm $lang_cache_dir/fusiondirectory.po" ) and die ("! Unable to compile .mo files with msgfmt, is it already installed?\n"); } } ############################################################# Directories checking ################################################################################### #get the apache user group name sub get_apache_group { my $apache_group = ""; # try to identify the running distribution, if it's not debian or rehat like, script ask for user input if (-e "/etc/debian_version") { $apache_group = "www-data"; } elsif ((-e "/etc/redhat-release") || (-e "/etc/mageia-release")) { $apache_group = "apache"; } elsif (-e "/etc/SuSE-release") { $apache_group = "www"; } elsif (-e "/etc/arch-release") { $apache_group = "http"; } else { print ("! Looks like you are not a Debian, Suse, Redhat or Mageia, I don't know your distribution !\n"); $apache_group = ask_user_input ("What is your apache group?"); } return $apache_group; } #check the rights of a directory or file, creates missing directory if needed sub check_rights { my ($dir,$user,$group,$rights,$create) = @_; my $user_uid = getpwnam ( $user ); my $group_gid = getgrnam ( $group ); # if the current dir exists if (-e $dir) { print("$dir exists…\n"); # retrieve dir's informations my @lstat = lstat ($dir); # extract the owner and the group of the directory my $dir_owner = getpwuid ( $lstat[4] ); my $dir_group = getgrgid ( $lstat[5] ); # extract the dir's rights my $dir_rights = S_IMODE( $lstat[2] ); if ( ($dir_owner ne $user) || ($dir_group ne $group) || ($dir_rights ne $rights) ) { if ( ask_yn_question ("$dir is not set properly, do you want to fix it ?: ") ) { chown ($user_uid,$group_gid,$dir) or die ("! Unable to change $dir owner\n") if ( ($dir_owner ne $user) || ($dir_group ne $group) ); chmod ( $rights, $dir ) or die ("! Unable to change $dir rights\n") if ($dir_rights ne $rights); } else { print ("Skipping...\n"); } } else {
491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
print("Rights on $dir are correct\n"); } } elsif ($create) { if ( ask_yn_question("Directory $dir doesn't exists, do you want to create it ?: ") ) { my $conf_dir = dir ($dir); # create the directory, and change the rights $conf_dir->mkpath (0,$rights); chmod ($rights, $dir); chown ($user_uid,$group_gid,$dir) or die ("Unable to change $dir rights\n"); } else { print ( "Skipping...\n" ); } } else { return 0; } return 1; } # function that check FusionDirectory's directories sub check_directories { my $apache_group = get_apache_group(); # for each config directory foreach my $dir (@config_dirs) { # if $dir is one of the dirs that remains to root if ( grep (/.*$dir.*/, @root_config_dirs) ) { check_rights($dir,"root","root",oct(755),1); # else if $dir is one of the dirs that remains to apache's user group, and the dir's owner is not root or the group is not the apache's user group, modifying owner } elsif ( grep ( /.*$dir.*/, @apache_config_dirs) ) { check_rights($dir,"root",$apache_group,oct(770),1); } } } # function that check FusionDirectory's config file sub check_config { my $apache_group = get_apache_group(); # check config file check_rights($fd_config,"root",$apache_group,oct(640),0) or die 'The config file does not exists!'; } ############################################################# Change install directories ################################################################################# sub write_vars { my $filecontent = <<eof; <?php require_once('variables_common.inc'); /*! \\file * Define common locations and variables * Generated by fusiondirectory-setup */ if (!defined("CONFIG_DIR")) { define ("CONFIG_DIR", "$vars{fd_config_dir}/"); /* FusionDirectory etc path */ } /* Allow setting the config file in the apache configuration e.g. SetEnv CONFIG_FILE fusiondirectory.conf 1.0 */ if (!defined("CONFIG_FILE")) { define ("CONFIG_FILE", "$vars{config_file}"); /* FusionDirectory filename */ } /* Path for smarty3 libraries */
561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
define("SMARTY", "$vars{fd_smarty_dir}"); /* Smarty compile dir */ define ("SPOOL_DIR", "$vars{fd_spool_dir}/"); /* FusionDirectory spool directory */ /* Global cache dir */ define ("CACHE_DIR", "$vars{fd_cache}/"); /* FusionDirectory var directory */ /* Global locale cache dir */ define ("LOCALE_DIR", "$locale_cache_dir/"); /* FusionDirectory locale directory */ /* Global tmp dir */ define ("TEMP_DIR", "$tmp_dir/"); /* FusionDirectory tmp directory */ /* Directory containing the configuration template */ define ("CONFIG_TEMPLATE_DIR", "$template_dir/"); /* FusionDirectory template directory */ /* Directory containing the fai logs */ define ("FAI_LOG_DIR", "$fai_log_dir/"); /* FusionDirectory fai directory */ /* Directory containing the supann files define ("SUPANN_DIR", "$vars{fd_config_dir}/supann/"); /* FusionDirectory supann template directory */ /* name of the class.cache file */ define("CLASS_CACHE", "$vars{class_cache}"); /* name of the class cache */ ?> eof my $variables_path = "$vars{fd_home}/include/variables.inc"; my $variables_file = file ($variables_path); my $vars_file = $variables_file->openw() or die ("! Unable to open $variables_path in write mode\n"); $vars_file->print($filecontent); $vars_file->close or die ("! Can't close $variables_file\n"); } ############################################################# LDAP conformity check ################################################################################# # function that add the FusionDirectory's admin account # return nothing is it a problem? sub add_ldap_admin { my ($base, $ldap, $admindns, $people_entries, $roles) = @_; # Get the configuration to know which attribute must be used in the dn my $mesg = $ldap->search( base => "$base", filter => "(&(objectClass=fusionDirectoryConf)(cn=fusiondirectory))", attrs => ['fdAccountPrimaryAttribute', 'fdForcePasswordDefaultHash', 'fdPasswordDefaultHash'] ); $mesg->code && die $mesg->error; my $attr; if ($mesg->count <= 0) { print "Could not find configuration object, using default value\n"; $attr = 'uid'; } elsif (($mesg->entries)[0]->exists('fdAccountPrimaryAttribute')) { $attr = ($mesg->entries)[0]->get_value('fdAccountPrimaryAttribute'); } else { $attr = 'uid'; } if ($mesg->count > 0) { if (($mesg->entries)[0]->exists('fdForcePasswordDefaultHash') && ($mesg->entries)[0]->exists('fdPasswordDefaultHash')) { if ((($mesg->entries)[0]->get_value('fdForcePasswordDefaultHash') eq 'TRUE') && (($mesg->entries)[0]->get_value('fdPasswordDefaultHash') ne 'ssha')) { warn "Warning: Administator password will be hashed with ssha instead of forced default ".($mesg->entries)[0]->get_value('fdPasswordDefaultHash')."\n"; } } } my $fd_admin_uid = ask_user_input ("Please enter a login for FusionDirectory's admin", "fd-admin"); # Does this user exists? my $dn = "";