#!/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 2001.0405;
use FileHandle;
use English;
use RemoteServer 2005.0705;

######################################################################
### 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 = '1.0.5';
$appname = 'SysConf-Client';

# 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
#

# 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 "Kann Logfile '$logfile' nicht schreiben!\n";
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;
$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-Protokoll-Version: ",$RemoteServer::VERSION,"\n";
$server = RemoteServer::new($port,@authorized_hosts);

# STDIN, STDOUT und STDERR schließen, 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äßt das dann aufs System los.
  #
  my $file = shift;
  my $fmode = (stat($file))[2] & 07777;
  if ( ($fmode & ~0644) > 0 ) # mehr Rechte als "-rw-r--r--"
  {
    logdie "Sicherheitslücke: Das File '$file' ist für andere Benutzer schreibbar!\n
"
  }
  unless (-o $file)
  {
    logdie "Sicherheitslücke: Sie sind nicht Besitzer von '$file'\n"
  }
}


sub ReadConfigFile
{
  # Setzen von globalen Parametern aus dem Konfigurationsfile
  # Parameter: -
  # Return:    -
  #
  my $file = './sysconf-clientrc';
  my $auth_hosts = '';
#  $file = "$ENV{HOME}/.sysconf-clientrc" unless -r $file;
  $file = (getpwuid($>))[7] . '/.sysconf-clientrc' unless -r $file;
  $file = '/etc/sysconf-clientrc' unless -r $file;
  logdie "Kann weder './sysconf-clientrc' noch '$ENV{HOME}/.sysconf-clientrc' noch /etc/sysconf-clientrc' lesen!\n" unless -r $file;
  debug "Verwende Konfigurationsfile '$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 "Kein 'PORT' in '$file' definiert!\n" if $port eq '';
  logdie "Kein 'AUTHORIZED_HOSTS' in '$file' definiert!\n" if $auth_hosts eq '';
  @authorized_hosts = split(/\s/,$auth_hosts);
}


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 = "/tmp/\L$appname\E.log"; # Standardeinstellung

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

  my $file = "./sysconf-clientrc";
#  $file = "$ENV{HOME}/.sysconf-clientrc" unless -r $file;
#  my $tmp = (getpwuid($>))[7];
  $file = (getpwuid($>))[7] . '/.sysconf-clientrc' unless -r $file;
  $file = '/etc/sysconf-clientrc' 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 schließen, ...
  my $temp = '';
  my $error = defined $_[0] ? shift : $NormalExitCode;
  logprint("$appname PID $$ Ende um ",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 = (getpwuid($<))[0] || 'unknown';
  logprint("\n---\n\n$appname $version PID $$ mit Perl $]\nStart um ",date,
           " durch ",$login,"\n");
}


sub catch_signal
{
  my $signame = shift;
  logdie "Ende von $appname wegen Signal SIG$signame.\n";
}


sub catch_signal_usr1
{
  my $signame = shift;
  logprint "Empfang von SIG$signame. Es wird nach QUIT ein Neustart ausgelöst.\n";
  RemoteServer::SetRestartAfterQuit();
}


sub catch_warning
{
  # Abfangen von Laufzeit-Warnungen
  my $warnung = shift;
  error "INTERNAL: $warnung     (Interne Warnungen deuten auf einen ",
  "möglichen internen Programm-Fehler\n",
  "     hin oder auf eine fehlerhafte Eingabe, die nicht abgefangen ",
  "wurde!)\n";
}


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


sub Kopf
{
  my $head = "$appname $version   -   von 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 sysconf-client entnehmen Sie bitte
der Anleitung von sysconf.

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

PORT:             Auf diesem Port lauscht der sysconf-client.
AUTHORIZED_HOSTS: Von diesen Hosts ist eine Verbindung zulässig.

Optionale Einträge:
LOGFILE:          Voller Pfad des Logfiles (Standard: /tmp/sysconf-client.log)

Beispiel:
PORT=3333
AUTHORIZED_HOSTS=sysconf-master.test.de 10.33.22.11 loopback
LOGFILE=/var/log/sysconf-client.log

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

