#!/usr/bin/perl -w

require 5.000;
use lib '/usr/local/bin';
# Verfügbar unter: http://www.loescher-online.de/progdata/slutil.pm
use slutil 2016.0111;
use FileHandle;
use English;
use RemoteServer 2016.0111;

######################################################################
### Unterprogramm-Funktionen für interne Zwecke
######################################################################

sub debug;      # Ausgabe von Informationen
sub warning;    # Ausgabe von Warnungen
sub error;      # Ausgabe von Fehlern
sub myexit;     # Exit und Aufräumen
sub loginit;    # Schreibt einen kleinen Header ins LOG-file
sub logdie;     # Schreibt einen Text ins LOG-file und stirbt dann
sub logprint;   # Schreibt einen Text ins LOG-file

######################################################################
### Voreinstellungen
######################################################################

$version = '2022.0307';
$appname = 'SysConf-Client';

$rcfile = "\L$appname\Erc"; # Name des Konfigurationsfiles

# Changelog:
# 0.0.1:
# - Erste Testversion
# 1.0.0:
# - Erste voll funktionsfähige Version
# 1.0.1:
# - Kleine Fehlerkorrektur in RemoteServer.pm
# 1.0.2:
# - Neustart nach QUIT durch SIGUSR1 möglich.
# 1.0.3:
# - Start auch ohne gesetztes $HOME möglich.
# 1.0.4:
# - Restart-Problem behoben
# 1.0.5:
# - Fehler beim Setzen von s-Bits korrigiert in RemoteServer.pm
# - Im Filenamen ist nun auch ":" erlaubt in RemoteServer.pm
# 1.0.6:
# - kleine Anpassungen, um auch Windows unterstützen zu können
# - Mode des Logfiles explizit setzen
# 1.0.7:
# - Konfigurationsfilename nach Variable $rcfile verlagert
# 1.0.8:
# - Neue FILE Option: seccon (Security Context)
# 1.0.9:
# - Versionsangleich an Sysconf Client SSL
# 1.0.10:
# - Englische Meldungen
# 2019.1204:
# - Optimierung bei AUTHORIZED_HOSTS
#

my $TempDir = '/tmp';
if ($osname eq 'MSWin32')
{
  $TempDir = 'c:\temp';
}

# Wohin soll das logging erfolgen?
# $logfile = ">>/tmp/\L$appname\E.log"; # Log in eine Datei
# $logfile = ">&STDERR"; # Log auf STDERR
$logfile = ReadConfigFile_Logfile();

# Exitcodes beim Beenden
$NormalExitCode = 0;
$ErrorExitCode  = 1;

######################################################################
### Log-File und Signal-Handler
######################################################################

# LOG bereits hier starten, denn Konfigurationsfehler sind entscheidend!
open(LOG, ">>$logfile") || die "Can not open logfile '$logfile' for writing!\n";
chmod(0600,$logfile);
select(LOG); $|=1; select(STDOUT); $|=1; # Buffer ausschalten
loginit;

# Signal-Handler installieren
$SIG{HUP}  = \&catch_signal;
$SIG{INT}  = \&catch_signal;
$SIG{QUIT} = \&catch_signal;
$SIG{ABRT} = \&catch_signal;
$SIG{TERM} = \&catch_signal;
if ($osname ne 'MSWin32')
{
  $SIG{USR1} = \&catch_signal_usr1;
}
$SIG{'__WARN__'} = \&catch_warning;

$debug = $FALSE;

while ( (defined $ARGV[0]) && ($ARGV[0] =~ /^-/) )
{
 OPTION:
  {
    if ($ARGV[0] =~ /^-d/)
    {
      $debug = $TRUE; shift @ARGV; last OPTION
    }
    # Sonst Hilfe ausgeben:
    Hilfe();
  }
}


######################################################################
### Hauptprogramm
######################################################################

ReadConfigFile();
debug "PORT:             $port\n";
debug "AUTHORIZED_HOSTS: ",join(' ',@authorized_hosts),"\n";

# Logfile-Unterfunktionen in das Remote-Modul einhängen
RemoteServer::SetLoggingAndExitFunctions(
					 exit     => \&myexit,
					 logprint => \&logprint,
					 debug    => \&debug,
					 warning  => \&warning,
					 error    => \&error,
					 logdie   => \&logdie
					);

logprint "Server-protocol-version: ",$RemoteServer::VERSION,"\n";
$server = RemoteServer::new($port,@authorized_hosts);

# STDIN, STDOUT und STDERR schliessen, da man sysconf-client sonst so starten
# müsste: sysconf-client </dev/null >/dev/null 2>/dev/null
# So gehts nicht:
#close(STDIN);
#close(STDOUT);
#close(STDERR);

$server->Run();

exit;

######################################################################
### Unterprogramme
######################################################################

sub TesteDateiBesitzer
{
  # Parameter: Voller Pfad einer Datei
  # Kein Returnwert. (Bei Gefahr sofort Abbruch.)
  #
  # Es wird überprüft, ob eine Konfigurationsdatei nur für den Menschen
  # schreibbar ist, der auch sysconf ausführt.
  # Sonst könnte irgendjemand die Konfigurationsfiles verändern und Root
  # lässt das dann aufs System los.
  #
  my $file = shift;
  # FIXME: Wie prüft man unter Windows die Datei-Berechtigungen?
  if ($osname ne 'MSWin32')
  {
    my $fmode = (stat($file))[2] & 07777;
    if ( ($fmode & ~0644) > 0 ) # mehr Rechte als "-rw-r--r--"
    {
      logdie "Security-hole: the file '$file' is writable for other users!\n";
    }
  }
  unless (-o $file)
  {
    logdie "Security-hole: you are not owner of '$file'\n";
  }
}


sub ReadConfigFile
{
  # Setzen von globalen Parametern aus dem Konfigurationsfile
  # Parameter: -
  # Return:    -
  #
  my $file = '.' .$slash. $rcfile;
  my $auth_hosts = '';
  my $homedir = GetHomeDir();
  $file = $homedir .$slash. '.' . $rcfile unless -r $file;
  $file = "/etc/$rcfile" unless -r $file;
  logdie "Can not read '.${slash}$rcfile' or '".$homedir."${slash}.$rcfile' or /etc/$rcfile'!\n" unless -r $file;
  logprint "Using configurationfile '$file'\n";

  TesteDateiBesitzer($file);

  my $fh = FileHandle->new();
  open($fh, $file);
  while(<$fh>)
  {
    next if /^\#/; # Kommentare überspringen
    $port             = $1     if /^PORT\s*=\s*(.+)/i;
    $auth_hosts       = $1     if /^AUTHORIZED_HOSTS\s*=\s*(.+)/i;
    next;
  }
  close $fh;
  logdie "No 'PORT' in '$file' defined!\n"             if $port       eq '';
  logdie "No 'AUTHORIZED_HOSTS' in '$file' defined!\n" if $auth_hosts eq '';
  @authorized_hosts = split(/\s/,$auth_hosts);
  logprint "AUTHORIZED_HOSTS: '",join("','",@authorized_hosts),"'\n";
}


sub ReadConfigFile_Logfile
{
  # Versucht einen eventuellen LOGFILE-Eintrag aus sysconfrc zu lesen.
  # Ohne Fehlerbehandlung, da diese Funktion sehr früh aufgerufen wird.
  #
  # Parameter: -
  # Return:    Filename des Logfiles
  #
  my $logfile = $TempDir.$slash."\L$appname\E.log"; # Standardeinstellung
  my $homedir = GetHomeDir();

  # Wenn nicht als root aufgerufen, dann Usernamen in den Logfilenamen
  if ($UID != 0)
  {
    my $login = (getpwuid($UID))[0] || $UID;
    $logfile = $TempDir.$slash."\L$appname\E.$login.log";
  }

  my $file = '.' .$slash. $rcfile;
  $file = $homedir . $slash . '.' . $rcfile unless -r $file;
  $file = "/etc/$rcfile" unless -r $file;

  if(-r $file)
  {
    my $fh = FileHandle->new();
    open($fh, $file);
    while(<$fh>)
    {
      next if /^\#/; # Kommentare überspringen
      $logfile = $1 if /^LOGFILE\s*=\s*(.+)/i;
    }
    close $fh;
  }

  return $logfile;
}

######################################################################
### Debug, Logging, Exit, ...
######################################################################


sub myexit
{
  # Diese Funktion macht einen normalen exit() mit dem übergebenen
  # Exitcode
  # und erledigt vorher noch Aufräum-Arbeiten, wie LOG-file schliessen, ...
  my $temp = '';
  my $error = defined $_[0] ? shift : $NormalExitCode;
  logprint("$appname PID $$ end at ",date,"\n");
  close(LOG);
  exit $error;
}


sub logprint
{
  # Schreibt einen Text ins LOG-file
  print LOG @_;
}


sub debug
{
  # Es werden Debug-Informationen erzeugt
  logprint("DEBUG: ",@_) if $debug;
}


sub warning
{
  # Parameter: Fehlermeldung
  # Return:    -
  #
  logprint("WARN: ",@_);
}


sub error
{
  # Parameter: Fehlermeldung
  # Return:    -
  #
  logprint("ERROR: ",@_);
}


sub logdie
{
  # Schreibt einen Text ins LOG-file und stirbt dann
  logprint("FATAL: ",@_);
  myexit($ErrorExitCode);
}


sub loginit
{
  # Schreibt einen kleinen Header ins LOG-file
  my $login = getlogin || (getpwuid($<))[0] || 'unknown';
  my $uid   = $<;
  my ($gid) = split(' ',$();
  logprint("\n---\n\n$appname $version PID $$ with Perl $]\nStart at ",date,
           " by ",$login,"(UID:$uid,GID:$gid)\n");
}


sub catch_signal
{
  my $signame = shift;
  logdie "End of $appname due to signal SIG$signame.\n";
}


sub catch_signal_usr1
{
  my $signame = shift;
  logprint "Caught SIG$signame. Restarting after QUIT.\n";
  RemoteServer::SetRestartAfterQuit();
}


sub catch_warning
{
  # Abfangen von Laufzeit-Warnungen
  my $warnung = shift;
  error "INTERNAL: $warnung (Internal warnings are likely to be internal program-errors or wrong input, which were not handled correctly!)\n";
}


######################################################################
### Kopf und Hilfe
######################################################################


sub Kopf
{
  my $head = "$appname $version   -   by Stephan Löscher";
  return "\n$head\n" . '~' x length($head) . "\n";
}


sub Hilfe
{
  printumlautepaged
  Kopf().
"Syntax: \L$appname\E optionen

Mit \L$appname\E wird ein Dämon-Prozess gestartet, der die Sysconf-Betankung
ohne rsh oder ssh zulässt.

Optionen:
-d: Debug-Informationen ins Logfile schreiben

Durch das Signal USR1 wird nach QUIT ein Neustart von \L$appname\E
ausgelöst.

Details zur Konfiguration von sysconf und $appname entnehmen Sie bitte
der Anleitung von sysconf.

Im Konfigurationsfile './$rcfile' oder '\$HOME/.$rcfile'
oder '/etc/$rcfile' müssen diese Werte definiert werden:

PORT:             Auf diesem Port lauscht der $appname.
AUTHORIZED_HOSTS: Von diesen Hosts ist eine Verbindung zulässig.

Optionale Einträge:
LOGFILE:          Voller Pfad des Logfiles (Standard: $TempDir${slash}\L$appname\E.log)

Beispiel:
PORT=3333
AUTHORIZED_HOSTS=sysconf-master.test.de 10.33.22.11 loopback
LOGFILE=/var/log/\L$appname\E.log

";
  logprint "Es wird nur Hilfe ausgegeben.\n";
  myexit;
}
