#!/usr/bin/perl -w

######################################################################
###
###   IMPORTANT!
###   Please read the warranty and legal notice 
###   at the end of this file!
###
######################################################################

require 5.000;
use lib '/usr/local/bin',"$ENV{HOME}/bin",'/usr/stud/loescher/bin';
use lib 'd:/bin','c:/mydos','c:/bin';
use FileHandle;
use File::Copy;
use slutil;

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

$version = '3.1';
$appname = 'ppp';

$konfigdir   = '/root/ppp';
$sysconfdir  = '/root/sysconf-for-ppp';
$speed       = '115200';
$compression = 'bsdcomp 15,15';
# erst bei neuer SuSE: $compression = 'bsdcomp 15,15 deflate 15,15';
$MaximaleWartezeit = 50;
$MaximaleVersuche  = 10;
$idle_timeout      = 15*60; # 15 Minuten
# Ports, die gefiltert werden:
$active_filter     = "active-filter 'not ((port (135 or 137 or 138 or 139 or 445)) or (inbound and (tcp[13] & 2 !=0)) or (outbound and (tcp[13] & 4 !=0)) or (icmp[0] =3))'";
$maximale_verbindungszeit = 5*60*60 ; # 5 Stunden. "Zwangstrennung"
$lockfile = '/var/lock/ppp-on.lock';

$temp = (localtime())[6];
$feiertag = (($temp==6)||($temp==0)||$feiertag) ? $TRUE : $FALSE;

$ENV{PATH} = '/sbin:/usr/sbin:/root/bin:/usr/local/sbin:'.
             '/usr/local/bin:/usr/sl:/bin/:/usr/bin:/opt/bin';

$SIG{HUP}  = \&catch_signal;   #1 : Hangup detected on controlling terminal
$SIG{INT}  = \&catch_signal;   #2 : Interrupt from keyboard
$SIG{QUIT} = \&catch_signal;   #3 : Quit from keyboard
$SIG{ABRT} = \&catch_signal;   #6 : Abort signal from abort(3)
$SIG{TERM} = \&catch_signal;   #15: Termination signal

select(STDERR); $|=1; select(STDOUT); $|=1; # Ausgaben nicht puffern

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

&Hilfe if ($#ARGV<0);

$provider   = shift || die "Bitte als Parameter den Provider angeben!\n";

unless (-d "$konfigdir/$provider")
{
  die "Das Verzeichnis '$konfigdir/$provider' existiert nicht!\n";
}

print "Einwahl über $provider\n\n";

Einstellungen($konfigdir,$provider);

die "Kann Device '$modem' nicht lesen!\n"         unless -r $modem;
die "Kann Device auf '$modem' nicht schreiben!\n" unless -w $modem;

$chatscript = "$konfigdir/$provider/chat";
if (-e "$konfigdir/$provider/chat.disconnect")
{
  $disconnect = "disconnect '/usr/sbin/chat -V -f ".
  "$konfigdir/$provider/chat.disconnect 2>/tmp/ppp-chat-disconnect.out'";
}
$options = ReadFile("$konfigdir/$provider/pppd-options") || '';
chomp $options;
$options =~ s/\n/ /g; # Einen Einzeiler produzieren.

# System konfigurieren
print "System wird konfiguriert...\n";
copy("$konfigdir/$provider/variables.txt",
     "$sysconfdir/variables/localhost.var") ||
     die "Fehler beim Kopieren von $konfigdir/$provider/variables.txt".
         "nach $sysconfdir/variables/localhost.var\n";
chdir "$sysconfdir";
system("sysconf -w2 update ppp localhost >/dev/null");

if ( ($?>>8) != 0 )
{
  die "\nSysconf meldet einen Fehler!
Bitte schauen Sie in das LOG-File /tmp/sysconf.log\n\n";
}

$versuche = 0;

# system("stty $speed cs8 pass8 crtscts <$modem");
system("stty -F $modem $speed cs8 pass8 crtscts");

print "Anwahlversuch...\n";
ANWAHL:
system("pppd lock $compression debug crtscts defaultroute ".
       "netmask 255.255.255.0 $modem $speed asyncmap 0 usepeerdns $options ".
       "idle $idle_timeout $active_filter ".
       "maxconnect $maximale_verbindungszeit ".
       "connect '/usr/sbin/chat -v -f $chatscript' $disconnect &");

# Warten bis PPP-Verbindung aktiv ist
$wartezeit = 0;
$next_free_ppp_device = FindNextFreePPPDevice();
$status = `netstat -i | grep $next_free_ppp_device`;
while (! $status)
{
  print "Noch keine PPP-Verbindung.\n";
  if ($wartezeit > $MaximaleWartezeit)
  {
    if ($versuche > $MaximaleVersuche)
    { die "Maximale Zahl der Anwahlversuche erreicht!\n" };
    print "Starte neuen Anwahlversuch...\n";
    $versuche++;
    goto ANWAHL;
  }
  sleep 2;
  $wartezeit += 2;
  $status = `netstat -i | grep $next_free_ppp_device`;
}
print "\nPPP-Verbindung steht: $next_free_ppp_device!\n\n";

# Kosten in Logfile schreiben
$tmp = Kosten($provider);
open(FH, ">>/var/log/ppp-sl.log") || warn "Kann /var/log/ppp-sl.log nicht zum Schreiben öffnen!\n";
print FH "Provider: $provider\n";
print FH "Einwahl: $daten{$provider}->{einwahl}\n";
print FH "Kosten: $tmp\n";
close FH;

# Für ppp-off das Device vermerken
open(FH, ">/tmp/ppp.device") || warn "Kann /tmp/ppp.device nicht zum Schreiben öffnen!\n";
print FH "$next_free_ppp_device\n";
close FH;


# Lock-File erstellen, um ein "ppp-off" zu verhindern!
system("touch $lockfile");


# WWWOffle Online schalten
$wwwoffle = 0;
system("runs wwwoffled > /dev/null");
if ( ($?>>8) == 0 )
{
  $wwwoffle = 1;
  print "Schalte WWWOffle Online...\n";
  system("wwwoffle -online");
}


StartePPP_UP_Skript($konfigdir,$provider);


if ($uhr_stellen)
{
  # Uhr stellen
  # Es bieten sich die Rechner ptbtime1.ptb.de und ptbtime2.ptb.de an.
  if (-x '/etc/init.d/chronyd')
  {
    print "Stelle chrony auf online...\n";
    open (FH, "|chronyc");
    print FH "password yxcmnb\nonline\n";
    close FH;
  }
  else
  {
    system("/usr/sbin/netdate -v $ENV{PPP_NETDATE_RECHNER}");
    # Alternative: getdate -adjust 1 240 rechner
    system("clock -wu");
  }
}


if ($mail_empfangen)
{
  # Mails empfangen
  print "Mail empfangen...\n";

  @fetchmails = glob("/home/*/.fetchmailrc");
  foreach $pfad (@fetchmails)
  {
    $pfad =~ m!/home/(.+)/.fetchmailrc!;
    $account = $1;
    print "Account: '$account'\n";
    system("su - $account -c fetchmail");
  }
}


if ($mail_senden)
{
  # Mail versenden
  system("mailq");
  system("sendmail -q");
  system("mailq");
}


if ($news_senden)
{
  # News versenden
  print "News versenden...\n";
#  system("sendnews");
}


if ($news_empfangen)
{
  # News empfangen
  print "News empfangen...\n";
#  $suckdir = '/var/lib/news/suck';
#  unless (-d $suckdir)
#  {
#    mkdir $suckdir, 0775;
#    chown( (getpwnam('news'))[2], (getpwnam('news'))[3], $suckdir);
#  }
#  system("ErstelleSuckNewsrc $suckdir `/bin/ls /home/*/.newsrc`");
#  chown( (getpwnam('news'))[2], (getpwnam('news'))[3], "$suckdir/sucknewsrc");
#  system("su - news -c getnews");
}


if ($wwwoffle)
{
#  print "Soll WWWOffle die angeforderten Seiten holen? (J/N) ";
#  chomp($input = <>);
#  if ($input =~ /^[jy]/i)
#  {
    system("wwwoffle -fetch");
#  }
}


# Lock-File wieder entfernen.
unlink $lockfile;


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

sub catch_signal
{
  my $signame = shift;
  print "Ende von $appname wegen Signal SIG$signame.\n";
  undef %SIG; # Keine weiteren Signale
  print "Entferne Lockfile...\n";
  unlink $lockfile if (-e $lockfile);
  if ($wwwoffle)
  {
    print "WWWOffle ist noch online.\n";
#    print " Soll er auf 'offline' gestellt werden? (J/N) ";
#    my $input = readkey();
#    if ($input =~ /^[jy]$/i)
#    {
#      print "Schalte WWWOffle Offline...\n";
      system("wwwoffle -offline");
#    }
  }
  exit;
}


sub Einstellungen
{
  # Es werden die Einstellungen aus $konfigdir/PROVIDER.conf gelesen

  # Default:
  $modem             = '/dev/modem';
  $mail_senden       = 0;
  $mail_empfangen    = 0;
  $news_senden       = 0;
  $news_empfangen    = 0;
  $uhr_stellen       = 0;

  my $konfigdir = shift;
  my $provider  = shift;
  my $conf = ReadFile("$konfigdir/$provider/conf") ||
  die "Bitte ein Konfigurationsfile '$konfigdir/$provider/conf' anlegen!\n";
  eval $conf;

  print "Modem-Device:      $modem\n";
  print "Mail senden:       $mail_senden\n";
  print "Mail empfangen:    $mail_empfangen\n";
  print "News senden:       $news_senden\n";
  print "News empfangen:    $news_empfangen\n";
  print "Uhr stellen:       $uhr_stellen\n";
}


sub StartePPP_UP_Skript
{
  # Parameter: Konfigurationsverzeichnis, Providername
  # Return:    -
  # Startet das Skript $konfigdir/$provider/ppp-up, wenn es existiert
  #

  my $konfigdir = shift;
  my $provider  = shift;
  my $skript    = "$konfigdir/$provider/ppp-up";

  if (-x $skript)
  {
    warn "Starte $skript\n";
    system($skript);
  }
}


sub ReadFile
{
  # Parameter: Name des Files
  # Return:    Inhalt des Files in einem Scalar
  #
  my $file = shift;
  my $fh = new FileHandle;
  my $inhalt;
  open($fh, $file) || return undef;
  {
    local $/ = undef; # In einem Stück einlesen
    $inhalt = <$fh>;
  }
  close $fh;
  return $inhalt;
}


sub FindNextFreePPPDevice
{
  # Es wird das nächste unbenutzte PPP-Device "pppX" zurückgeliefert.
  #
  my $max = -1;
  foreach (`netstat -i`)
  {
    if (/ppp(\d+)/)
    {
      $max = $1 if $1 > $max;
    }
  }
  return 'ppp'.($max+1);
}


sub Kosten
{
  # Einlesen und Berechnung der Kosten
  # Parameter: Providername
  # Return:    Kosten in Ct./Min.

  my $provider = shift;
  my $file = "$konfigdir/$provider/kosten.txt";
  my $error = 'Err:Kost';
  my $zeit;
  my $kosten;

  return $error unless -r $file;

  my $fh = FileHandle->new();
  open($fh, $file);

  my $zeile = <$fh>;
  unless ($zeile =~ /^Einwahl\s+([\d\.]+)$/)
  {
    warn "Die Datei '$file' beginnt nicht mit 'Einwahl' in der ersten Zeile!\n";
    $daten{$provider}->{einwahl} = 0;
  }
  else
  {
    $daten{$provider}->{einwahl} = $1;
    $zeile = <$fh>;
  }

  unless ($zeile =~ /Werktags/i)
  {
    warn "Die Datei '$file' hat in der zweiten Zeile nicht 'Werktags' stehen!\n";
    return $error;
  }
  # Werte für Werktage einlesen
  while(<$fh>)
  {
    last if /Wochenende/i;

    /^\s*(\d+)\s*:\s*(\d+[,.]*\d*)/;
    $zeit   = $1;
    $kosten = $2;
    $kosten =~ s/,/./g;
    $daten{$provider}->{werktags}->{$zeit} = $kosten;
  }

  # Werte für Wochenende einlesen
  while(<$fh>)
  {
    /^\s*(\d+)\s*:\s*(\d+[,.]*\d*)/;
    $zeit   = $1;
    $kosten = $2;
    $kosten =~ s/,/./g;
    $daten{$provider}->{wochenende}->{$zeit} = $kosten;
  }
  return ZeitRechnung($provider);
}


sub ZeitRechnung
{
  # Kostenberechnung
  # Parameter: Providername
  # Return:    Kosten in Ct./Min.

  my $error = 'Err:Zeit';
  my $provider  = shift;

  my $stunde    = (localtime())[2];
  my $wochentag = (localtime())[6];

  if ( ($wochentag == 6) || ($wochentag == 0) || $feiertag )
  {
    return $daten{$provider}->{wochenende}->{$stunde} || $error;
  }
  else
  {
    return $daten{$provider}->{werktags}->{$stunde} || $error;
  }
}


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


sub Hilfe
{
  printumlautepaged
  Kopf().
"Syntax: ppp PROVIDER

Es müssen minndestens für den Provider ein Chat-File und Konfigurationsfile
vorhanden sein:
$konfigdir/PROVIDER/chat
$konfigdir/PROVIDER/conf

Außerdem muß das Verzeichnis
$sysconfdir/
zur Systemkonfiguration existieren.

Das Chat-File wird dirket an chat(8) übergeben.
Das Konfigurationsfile sieht z.B. so aus:

\$modem             = '/dev/modem';
\$mail_senden       = 1;
\$mail_empfangen    = 1;
\$news_senden       = 1;
\$news_empfangen    = 0;
\$uhr_stellen       = 1;

wobei 0 für FALSE und 1 für TRUE steht.

Folgende Files sind optional:
$konfigdir/PROVIDER/pppd-options  (Zusätzliche Optionen für den pppd)
$konfigdir/PROVIDER/ppp-up        (Dieses Skript wird ausgeführt, sobald die
                                  PPP-Verbindung aktiv ist.)
$konfigdir/PROVIDER/info.txt      (Freier Text, der von xppp angezeigt wird.)
$konfigdir/PROVIDER/kosten.txt    (Kosten, die von xppp angezeigt werden.)

Ich habe eine Beispielkonfiguration unter folgendem URL abgelegt:
http://www.loescher-online.de/progdata/ppp.tar.bz2

";
exit;
}

######################################################################
#
# Warranty and legal notice
# ~~~~~~~~~~~~~~~~~~~~~~~~~
#
# Copyright (c) 1998 by Stephan Löscher  -  all rights reserved
# My Address: Stephan Löscher, Dr.Troll-str. 3, 82194 Gröbenzell, Germany
# Email: loescher@gmx.de
# WWW: http://www.loescher-online.de/
#
# This program is freeware.
# It is NOT Public-Domain-Software!
# The author (Stephan Löscher) does NOT give up his copyright, but he 
# reserves his copyright. Usage and copying is free of charge for private
# use, but NOT for commercial use!
# 
# You may and should copy this program free of charge, use it,
# give it to your friends, upload it to a BBS or something similar, under
# the following conditions:
# * Don't charge any money for it. If you upload it to a BBS, make sure that
#    it can be downloaded free (without paying for downloading it, except
#    for usage fees that have to be paid anyway). Small copying fees (up to
#    5 DM or 3 $US) may be charged.
#  * Only distribute the whole original package, with all the files included.
#  * This program may not be part of any commercial product or service without
#    the written permission by the author.
#  * If you want to include this program on a CD-ROM and/or book, please send
#    me a free copy of the CD/book (this is not a must, but I would appreciate
#    it very much).
# 
# Distribution of the program is explicitly desired, provided that the above
# conditions are accepted.
# 
# YOU ARE USING THIS PROGRAM AT YOUR OWN RISK! THE AUTHOR (STEPHAN LÖSCHER)
# IS NOT LIABLE FOR ANY DAMAGE OR DATA-LOSS CAUSED BY THE USE OF THIS PROGRAM
# OR BY THE INABILITY TO USE THIS PROGRAM. IF YOU ARE NOT SURE ABOUT THIS, OR
# IF YOU DON'T ACCEPT THIS, THEN DO NOT USE THIS PROGRAM!
# BECAUSE OF THE VARIOUS HARDWARE AND SOFTWARE ENVIRONMENTS INTO WHICH THIS
# PROGRAM MAY BE PUT, NO WARRANTY OF FITNESS FOR A PARTICULAR PURPOSE IS
# OFFERED.
# GOOD DATA PROCESSING PROCEDURE DICTATES THAT ANY PROGRAM BE THOROUGHLY
# TESTED WITH NON-CRITICAL DATA BEFORE RELYING ON IT.
# 
# No part of the documentation may be reproduced, transmitted, transcribed,
# stored in any retrieval system, or translated into any other language in
# whole or in part, in any form or by any means, whether it be electronic,
# mechanical, magnetic, optical, manual or otherwise, without prior written
# consent of the author, Stephan Löscher.
# 
# You may not make any changes or modifications to this software or this
# manual. You may not decompile, disassemble, or otherwise reverse-engineer
# the software in any way.
# If you got the source, then you are permitted to modify it if you
# contact me and tell me your enhancements.
# You also may include the source as a whole or parts of it into other
# programs, as long as you don't make profit directly out of selling
# the result. If you re-use code of this program then do not remove my name!
# If you include this source-code in your projects, mark it clearly as such
# "... derived from code XXX by Stephan Löscher".
# But don't distribute modified code!
# 
# If you believe your copy of this software has been tampered or altered in
# anyway, shape or form, please contact me immediately! Do not hesitate a
# moment to inform me. Remember, this software should be available to all, in
# the original form, so please do not accept modified or damaged versions of
# my software.
# 
# The author reserves his right for taking legal steps if the copyright or the
# license agreement is violated.
# 
# All product names mentioned in this software are trademarks or registered
# trademarks of their respective owners.
# 
# If you have any questions, ideas, suggestions for improvements or if you find
# bugs (I don't hope so.) then feel free to contact me. (Email is appreciated.)
# 
# I'm not a native english speaker. If you are one and discover some strange
# sounding parts in this documentation or in the program, please, feel free
# to point it out to me and give me suggestions for alteration!
# 
# If the program works for you, and you want to honour my efforts, you are
# invited to donate as much as you want... :)
#
# In any case, if you don't like the restrictions in this license, contact
# me, and we can work something out.
#
######################################################################
