#!/usr/bin/perl

########## Confixx(R) 3.0 Professional ############
####### Copyright SWsoft, Inc. 2004-2005 ##########
#### http://www.sw-soft.com - info@sw-soft.com ####

use File::Basename;
use XML::DOM;

# sub scponlyConfigure
#     -configure scponly-support in Confixx
#
# parameters:
#   $ptrBin - pointer to %bin
#   $userHomeDir - directory of user's home
#   $userPrefix - user's home prefix
#   $userConfixx - name of Confixx-user
#
# return ($scponly_shell,$scponly_chroot)
#
# $scponly_shell - path to scponly-shell
# $scponly_chroot - path to chroot tree

sub scponlyConfigure {
  my $ptrBin = shift;
  unless(ref($ptrBin)=~/HASH/){
    unshift @_,$ptrBin;
    $ptrBin=\%bin;
  }
  my $userHomeDir=shift;
  my $userPrefix=shift;
  my @reserverdNames=@_;

  unless ($userHomeDir){
    $userHomeDir = $user_homeDir;
  }
  unless ($userPrefix){
    $userPrefix = $user_prefix || 'web';
  }
  unless (@reserverdNames){
    push @reserverdNames,'confixx';
    push @reserverdNames,'empty';
  }

  my $chroot = &YesNoQuestion( &ltext('install_scponly_chroot'),'j' );
  my ($name,$dir);

  if ($chroot){
#
# where is ldd
#
    $bin_ldd=$ptrBin->{'ldd'};
    unless (-x $bin_ldd) {
      $bin_ldd=&getBin('ldd');
      unless(-x $bin_ldd) {
	$bin_ldd = &BinQuestion( &ltext('install_bin_path','ldd'), '/usr/bin/ldd');
      }
    }
    $ptrBin->{'ldd'}=$bin_ldd;
  

#
# dir's name of chroot tree
#
    my $rsrvd;
    while (!$name) {
      $name= $scponly_chroot||'chroot';
      $name = &Question( &ltext('install_scponly_root_dir',$user_homeDir) , $name);
      if ($name=~/\/([^\/]+)\/*$/){
	$name=$1;
      }
      $name =~ tr/A-Z/a-z/;
      if ($userPrefix && ($name=~/^$userPrefix/)){
	print &ltext('install_scponly_bad_root_dir',$userPrefix);
	$name='';
	next;
      }
      foreach $rsrvd (@reserverdNames){
	if ($rsrvd eq $name){
	  print &ltext('install_scponly_bad_root_dir',$rsrvd);
	  $name='';
	  last;
	}
      }
    }

    $scponly_chroot=$name;
    $name='scponlyc';
    $dir='/usr/local/sbin';
  }else{
    $scponly_chroot='';
    $name='scponly';
    $dir='/usr/local/bin';
  }


#
# where is scponly-shell
#
  
  if((!-x $scponly_shell) && (-x $ptrBin->{'scponly'})){
    $scponly_shell=$ptrBin->{'scponly'};
  }
  if ((-x $scponly_shell) &&  ( basename($scponly_shell) ne $name)){
    $scponly_shell='';
  }
  unless(-x $scponly_shell){
    $scponly_shell=&getBin($name,1,$dir);
  }
  unless (-x $scponly_shell){
    $scponly_shell = &BinQuestion( &ltext('install_bin_path',$name), "$dir/$name");
  }

  $ptrBin->{'scponly'} =$scponly_shell;

  return ($scponly_shell,$scponly_chroot);
}

#
# sub scponlyMakeChroot
#                       - create tamplate for the chrooted user's home 
#
# parameters:
#    $chroot - path to the tamplate dir
#
# return:
#    1 - success
#    0 - fail
#

sub scponlyMakeChroot {
  my $chroot = shift || "$user_homeDir/$scponly_chroot";
  my $chroot_install="chroot_install.pl";
  my $ptrBin = shift || \%bin;

  unless ($chroot){
    return 0;
  }
  $wDir=dirname($0);
  if (-x "$wDir/subs/chroot_install.pl"){
    $chroot_install="$wDir/subs/chroot_install.pl";
  }
  unless (-x $chroot_install){
    return 0;
  }


  do $chroot_install; # load library

  &RecCreateDir($chroot,0755);
  if (&parseChrootConfig("$wDir/subs/confixx_chroot.conf",$chroot,$ptrBin,['scponly','shell'])){
    return 1;
  }

#
# list of directories from scponly-*.*/setup_chroot.sh
#
  my @scpTree = ('/usr','/usr/bin','/usr/sbin','/usr/local','/usr/local/lib','/usr/local/sbin','/usr/local/bin',
		'/lib','/usr/lib','/usr/lib/ssh','/usr/libexec','/usr/libexec/openssh','/bin','/etc');

# create tree
  foreach my $dir (@scpTree){
    &RecCreateDir("$chroot$dir",0755);
  }
  undef @scpTree;

#
# list of binaries from  scponly-*.*/config.h
#
  my @scpChrottedBins=();
  my @scpBins = ('sh','ls','scp','rm','ln','mv','chmod','chown','chgrp','mkdir','rmdir',
		'pwd','groups','id','echo');
  my $fullName;

  foreach $name (@scpBins){
    if (-x $ptrBin->{$name}){
      $fullName = $ptrBin->{$name};
    }else{
      $fullName = &getBin($name);
    }
    if (-x $fullName){
      push @scpChrottedBins,$fullName;
    }
  }
  
  &chrootGo(\@scpChrottedBins,$chroot,$ptrBin,1);
  

#
# list of libraries  from scponly-*.*/setup_chroot.sh
#
  @scpChrottedBins =();
  if (opendir(DIR,"/lib")){
    while (my $lib = readdir(DIR)){
      if($lib =~/^libnss_compat/){
	push @scpChrottedBins,"/lib/$lib";
      }
    }
    closedir (DIR);
  }
  if (@scpChrottedBins){
    &chrootGo(\@scpChrottedBins,$chroot,$ptrBin,1);
  }

  return 1;
}

sub parseChrootConfig {
  my $config=shift;
  return 0 unless (-T $config);
  my $chroot=shift;
  return 0 unless (-d $chroot);
  my $ptrBin=shift;
  unless(ref($ptrBin)=~/HASH/){
    unshift @_,$ptrBin;
    $ptrBin=\%bin;
  }
  my $ptrSecs = shift;
  unless (ref($ptrSecs)=~/ARRAY/){
    unshift @_,$ptrSecs;
    $ptrSecs=\@_;
  }
  my %Secs;
  my $allSections=1;
  foreach my $sec (@{$ptrSecs}){
    $Secs{$sec}=1;
    $allSection=0;
  }
  my $parser = new XML::DOM::Parser;
  my $xmlRoot = $parser->parsefile($config);
  return 0 unless $xmlRoot;
  my ($kid,$xmlConfixx);
  foreach $kid ($xmlRoot->getChildNodes){
    next unless ($kid->getNodeType==ELEMENT_NODE);
    if ($kid->getNodeName() eq 'confixx'){
      $xmlConfixx=$kid;
      last;
    }
  }
  unless ($xmlConfixx){
    print STDERR "Error: scponly_install: No 'confixx' node found\n";
    return 0;
  }
  $xmlRoot=undef;
  foreach $kid ($xmlConfixx->getChildNodes){
    next unless ($kid->getNodeType==ELEMENT_NODE);
    if ($kid->getNodeName() eq 'chroot'){
      $xmlRoot=$kid;
      last;
    }
  }
  unless ($xmlRoot){
    print STDERR "Error: scponly_install: No 'confixx/chroot' node found\n";
    return 0;
  }

  my @actSecs;
  foreach $kid ($xmlRoot->getChildNodes){
    next unless ($kid->getNodeType==ELEMENT_NODE);
    $nodeName = $kid->getNodeName();
    if ($allSection || $Secs{$nodeName}){
      push @actSecs,$kid;
    }
  }

  my ($name,$nodeName,@files,$retPtr,$xmlSec,$fullName,$ptr,$opt,%noexec,$file,$mode);

  for $xmlSec (@actSecs) {
    print 'Prepare node: ',$xmlSec->getNodeName,"\n";
    @files=(); # clean array of files
	
    foreach $kid ($xmlSec->getChildNodes){
      next unless ($kid->getNodeType==ELEMENT_NODE);
      $nodeName = $kid->getNodeName();
      if ($nodeName eq 'dir'){

	$retPtr = &prepareDir($kid,$chroot,'');

	if (ref($retPtr)=~/ARRAY/ && @{$retPtr}){
	  push @files,@{$retPtr};
	}
      }elsif($nodeName eq 'file'){
	$name=$kid->getAttribute('name');
	
	$fullName='';
	next unless $name;
	if ($name=~/^\$(\S+)\$$/){
	  $ptr=$1;
	  $fullName=$$ptr;
	}
	$opt=$kid->getAttribute('optional');
	if ((-x $name || -B $name) && ($name=~/^\//)){
	  $fullName = $name;
	}else{  
	  unless ($opt && ($name=~/^\//)){
  	    if (-x $ptrBin->{$name}){
		$fullName = $ptrBin->{$name};
	    }
	    unless(-x $fullName || -B $fullName){
		$fullName = &getBin($name);
	    }	
	  }
	}  
	if (-x $fullName || -B $fullName){
	  push @files,$fullName;
	}else{
	 unless($opt){
		print STDERR "file '$name' not found\n";
	  }
	}
      }
    }

    if (@files){
	  %noexec=();
	  foreach $file (@files){
		next if (-x $file);
		$mode=(stat($file))[2];
		$mode &= 07777;	
		$noexec{$file}=$mode;
#
# add executablity
#	
		$mode |= 0111;
		chmod $mode,$file;
	  }
#
# copy files to chroot-tree
#

      &chrootGo(\@files,$chroot,$ptrBin,1);

#
# restore mode	  
#
	  while (($file,$mode)=each(%noexec)){
		chmod $mode,$file;
		chmod $mode,"$chroot$file";
	  }
	  %noexec=();
    }
  }
  return 1;
}

#
# recursive sub prepareDir
#
# create dir's tree
#
# return pointer to array of funded files
#
sub prepareDir ($$){
  my ($xmlDir,$chroot,$prefix)=@_;

  my $nameDir=$xmlDir->getAttribute('name');
  return undef unless($nameDir);

  $nameDir = '/'.$nameDir unless $nameDir=~/^\//;
  my $mode=$xmlDir->getAttribute('mode') || 0755;
  my (@files,$retPtr);

  $prefix.=$nameDir;
  print "Create dir: $chroot$prefix\n";
  &RecCreateDir("$chroot$prefix",$mode);
  my ($kid,$nodeName,$name,$pat,$path);
  foreach my $kid ($xmlDir->getChildNodes){
    next unless ($kid->getNodeType==ELEMENT_NODE);
    $nodeName = $kid->getNodeName();
    if ($nodeName eq 'dir'){

      $retPtr = &prepareDir($kid,$chroot,$prefix);
      if (ref($retPtr)=~/ARRAY/ && @{$retPtr} ){
	push @files,@{$retPtr};
      }

    }elsif($nodeName eq 'file'){
      $name = $kid->getAttribute('name');
      next unless $name;
      $path="$prefix/$name";
      if ($name=~/^([^\*]+)\*/){
	$pat=$1;
	if (opendir(DIR,$prefix)){
	  while ($name=readdir(DIR)){
	    $path="$prefix/$name";
	    if (-f $path && $name=~/^$pat/){
	      push @files,$path;
	    }
	  }
	  closedir(DIR);
	}
      }elsif(-f $path){
	push @files,$path;
      }
    }
  }
  if (@files){
    return \@files;
  }else{
    return undef;
  }
}

# 
# checkShells - check if has /etc/shells entry for scponly
#               add entry if necessary
#
# arguments: - <path-to-scponly>
#            - [path-to-shells] - default /etc/shells
#
# return     - 0 - fail: path-to-scponly is not executable
#              1    
sub checkShells {
  my ($pathToShell,$pathToConfig) = @_;
  unless(-x $pathToShell){
	print SDTERR  "Error: '$pathToShell' is not executable\n";
	return 0;
  }
  
  $pathToConfig||='/etc/shells';
  my $found=0;
  my $lastLF=1;
  if(open (CONFIG,"<$pathToConfig")){
	
	while (<CONFIG>){

	  if(!$found && /^\Q$pathToShell\E\n?$/){
		$found=1;
	  }
	  $lastLF=/\n$/?1:0;
	}
	close(CONFIG);
  }
  
  unless($found){
	if(open(CONFIG,">>$pathToConfig")){
	  print CONFIG "\n" unless $lastLF;
	  print CONFIG $pathToShell,"\n";
	  close(CONFIG);
	}else{
	  print STDERR "Error open file '$pathToConfig' - $!\n";
	  return 0;
	}
  }
  return 1;
}

1;
