#!/usr/bin/perl
#
# sendFAXde  --  sends a fax, sms, email, letter via XML-Soap to fax.de
#
############################################
#
# Copyright 2006  Dr. Andy Spiegl, KasCada Telekommunikation und Marketing
#
############################################
#
# History:
#
# v0.1  2006-03-25: erste Version
# v0.2  2006-03-29: Kommandozeilenparameter
# v0.3  2006-03-30: Returnvariablen zuverlässig auslesen
# v0.4  2006-03-30: Kundendaten abfragen und anzeigen
# v0.41 2006-03-30: Ausgabe-Bugs beseitigt
# v0.5  2006-03-30: kann Text nun auch von STDIN lesen
# v0.6  2006-03-30: jetzt auch mit Umlauten (utf8-Umwandlung)
# v0.7  2006-03-30: Journal abfragen
# v0.8  2006-03-31: Rohgerüst für alle Dokumenttypen (nur SMS fertig)
#
############################################

my $VERSION = "0.8";

############################################

use strict;
use warnings;

use Getopt::Long;
use Pod::Usage;

use Text::Iconv;

use SOAP::Lite
 on_fault => sub
  {
    my($soap, $response) = @_;
    if (ref $response)
    {
      &error_exit("SOAP-Fehler: ". $response->faultstring, 200);
    }
    else
    {
      &error_exit("SOAP-Fehlerstatus: ". $soap->transport->status, 201);
    }
  };

# security for shell calls:
$ENV{'PATH'} = '/bin:/usr/bin';
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};


############################################
# globale VARIABLEN
############################################

my $debug = 0;
my $errors_occurred = 0;

# some self detection
my $self = $0; $self =~ s|.*/||;


############################################
# konfigurierbare VARIABLEN
############################################

# für SOAP via HTTP
my $faxde_soap_uri='urn:XMLWSIntf2-IXMLWS2';
my $faxde_soap_proxy='http://ccs.fax.de/xmlws3.exe/soap/IXMLWS2';


############################################
# globale Konstanten
############################################
my %ResultCodesStandard = (
     '0' => "Ok",
     '1' => "Account nicht gefunden.",
     '2' => "Passwort passt nicht zum Account.",
     '3' => "Konto Testzeit ist abgelaufen.",
     '4' => "Account ist gesperrt.",
);

my %ResultCodesSend = (
     '0' => "Auftrag angenommen, wird ausgeführt",
    '-1' => "Passwort ungültig",
    '-2' => "Kunde nicht bekannt",
    '-3' => "Dokument nicht vorhanden",
    '-4' => "Serverfehler",
    '-5' => "EmpfaengerNr generell gesperrt",
    '-6' => "DokumentFormat nicht unterstützt",
    '-7' => "Dokument nicht konvertierbar",
    '-8' => "Kein Versand, da TEST Aufruf (Schalter=TEST)",
   '-14' => "Dokument nicht konvertierbar",
     '1' => "Nicht autorisiert",
     '2' => "KundenNr ist leer",
     '3' => "Kunde ist unbekannt",
     '6' => "Guthaben noch nicht verfügbar (erste Lastschrift + 14 Tage)",
     '7' => "Guthaben nicht ausreichend (nur iLetter, Jobart=3/4)",
    '10' => "Mailvorlage konnte nicht angelegt werden",
    '11' => "Zu viele Empfänger für zu signierenden Beleg (>1)",
    '12' => "Eingeliefertes Dokument-Format ist nicht PDF",
    '45' => "Kein Empfänger angegeben",
    '75' => "Keine Dateien und/oder Text zum Versand übergeben",
   );

my %ResultCodesStatus = (
     '0' => "Protokoll-Eintrag gefunden, siehe Protokoll-Variable",
     '1' => "Account nicht gefunden.",
     '2' => "Passwort passt nicht zum Account.",
     '3' => "Konto Testzeit ist abgelaufen.",
     '4' => "Account ist gesperrt.",
     '5' => "Kein Protokoll-Eintrag mit dieser JobId gefunden",
     '6' => "Job ist derzeit in Bearbeitung",
);

my %ResultCodesQSignGet = (
     '0' => "Beleg wurde signiert, PDF mit Stempel und Signatur-Datei stehen bereit",
     '1' => "Account nicht gefunden.",
     '2' => "Passwort passt nicht zum Account.",
     '3' => "Konto Testzeit ist abgelaufen.",
     '4' => "Account ist gesperrt.",
     '5' => "Beleg steht zur Bearbeitung an, ist aber noch nicht erledigt.",
);



############################################
# command line options
############################################
# option defaults
my $showhelp = 0;
my $showmanpage = 0;
my $showversion = 0;
my $account = "";
my $password = "";
my $doctype = "";
my @recipients = ();
my $absender = "";
my $flashsms = 0;
my $staturl = "";               # z.B. 'http://...?JOBID=$JID&STAT=$STAT&COST=$COST'
my $SendText = "";
my $nodo = 0;
my $testKdDaten = 0;
my $getKdDaten = 0;
my $getKontostand = 0;
my $getJournal = 0;

GetOptions(
   "help|usage"     => \$showhelp,       # show usage
   "manpage"        => \$showmanpage,    # show manpage
   "version"        => \$showversion,    # show programm version
   "debug+"         => \$debug,          # (incremental option)
   "nodo|justprint" => \$nodo,           # just print what would have been done
   "account=s"      => \$account,        # User Account bei fax.de
   "password=s"     => \$password,       # User Passwort bei fax.de
   "type=s"         => \$doctype,        # Fax, SMS, Brief, ...
   "empfaenger=s"   => \@recipients,     # TelNr des/der Empfänger
   "absender=s"     => \$absender,       # TelNr des Absenders
   "staturl=s"      => \$staturl,        # URL Aufruf nach Versand
   "flashsms"       => \$flashsms,       # SMS als Flash-SMS senden
   "sendtext=s"     => \$SendText,       # Text zum Versenden
   "testKdDaten"    => \$testKdDaten,    # zuerst Zugangsdaten überprüfen
   "getKdDaten"     => \$getKdDaten,     # unsere Kundendaten abfragen
   "getKontostand"  => \$getKontostand,  # Kontostand abfragen
   "getJournal=i"  =>  \$getJournal,     # History-Liste abholen
          ) or pod2usage(-exitstatus => 1, -verbose => 0);

# turn off buffering (sinnvoll für Debugging)
$| = 1  if $debug;

# are there more arguments?
if ($#ARGV >= 0)
{
  pod2usage(-message => "ERROR: unknown arguments \"@ARGV\".\n",
            -exitstatus => 2,
            -verbose => 0
           );
}

pod2usage(-exitstatus => 0, -verbose => 1)  if $showhelp;
pod2usage(-exitstatus => 0, -verbose => 2)  if $showmanpage;

if ($showversion)
{ print "$self - Version: $VERSION\n"; exit; }

if ($debug)
{
  print "DEBUG-Modus($debug): schalte $self in Debugmodus.\n";
}

if ($nodo)
{
  print "JUSTPRINT-Modus: nichtrückgängigmachbare Aktionen werden nicht ausgeführt.\n";
}


############################################
# Hauptprogramm
############################################

# Parameter prüfen
##############################

# Account und Passwort aus Environment holen
if (not $account)
{
  if ($ENV{'FAXDE_ACCT'})
  {
    $account = $ENV{'FAXDE_ACCT'};
  }
  else
  {
    &error_exit("Weder Parameter \"--account\" noch Env-Variable FAXDE_ACCT gesetzt.", 5);
  }
}
&print_debug("fax.de-Account: $account", 1);

if (not $password)
{
  if ($ENV{'FAXDE_PASS'})
  {
    $account = $ENV{'FAXDE_PASS'};
  }
  else
  {
    &error_exit("Weder Parameter \"--password\" noch Env-Variable FAXDE_PASS gesetzt.", 6);
  }
}
&print_debug("fax.de-Passwort: $password", 1);

if (not ($getKdDaten or $getKontostand or $getJournal)) # weniger Parameter nötig
{
  if (not $doctype)
  {
    &error_exit("Welche Art von Dokument soll verschickt werden?  (Parameter \"--type\")", 7);
  }
  &print_debug("Dokument-Typ: $doctype", 1);

  # Empfänger prüfen
  if (not @recipients)
  {
    &error_exit("Parameter \"--empfaenger\" nicht gesetzt.", 8);
  }
  &print_debug("Empfänger: ". join(", ", @recipients), 1);

  if ($#recipients > 0)
  {
    &error_exit("Mehr als ein Empfaenger pro Aufruf ist noch nicht implementiert.", 9);
  }

  # optionale Parameter
  if ($absender)
  {
    &print_debug("Absender: $absender", 1);
  }

  if ($flashsms)
  {
    &print_debug("Flash-SMS: SMS direkt auf Handy-Display senden", 1);
  }

  if ($staturl)
  {
    &print_debug("Status-URL: $staturl", 1);
  }

  # Text prüfen
  if (not $SendText)
  {
    # read from STDIN if avail
    if (not -t STDIN)
    {
      $SendText = join("", <STDIN>);
      chomp($SendText);
    }
    else
    {
      &error_exit("Parameter \"--text\" nicht gesetzt und STDIN leer.", 10);
    }
  }
  &print_debug("Text zum Versenden: $SendText", 1);
}


# und jetzt ab in die Wanne mit der Seife...
##############################
my $soap = SOAP::Lite
  -> uri($faxde_soap_uri)
  -> proxy($faxde_soap_proxy);

my $response;


# evtl. erstmal Account und Passwort prüfen und den Kundenname abfragen
##############################
if ($testKdDaten)
{
  $response = $soap->KundenName(SOAP::Data->name(Account => $account),
                                SOAP::Data->name(Password => $password)
                               );
  # war alles okay?
  &checkResultCode($response->result, 0, \%ResultCodesStandard);

  my $KdName;
  $KdName = $response->paramsout;
  &print_debug("KdName (1.Methode): $KdName", 0);

  # sicherer:
  $KdName = &getAnswerVar($response, "KdName");
  &print_debug("KdName (2.Methode): $KdName", 0);
}

# nur unsere Kundendaten abfragen und beenden
##############################
if ($getKdDaten)
{
  $response = $soap->KundenDaten(SOAP::Data->name(Account => $account),
                                SOAP::Data->name(Password => $password)
                               );
  # war alles okay?
  &checkResultCode($response->result, 0, \%ResultCodesStandard);

  my $KdDaten = &getAnswerVar($response, "KdDaten");

  print "OK Kundendaten:\n";

  foreach my $var (@{$KdDaten})
  {
    print $var ."\n";
  }

  &cleanup_and_exit(0);
}

# nur Kontostand abfragen und beenden
##############################
if ($getKontostand)
{
  $response = $soap->Kontostand(SOAP::Data->name(Account => $account),
                                SOAP::Data->name(Password => $password)
                               );
  # war alles okay?
  &checkResultCode($response->result, 0, \%ResultCodesStandard);

  my $Kontostand = &getAnswerVar($response, "KdKontostand");

  print "OK $Kontostand\n";
  &cleanup_and_exit(0);
}

# nur Journal abholen und beenden
##############################
if ($getJournal)
{
  $response = $soap->Journal(SOAP::Data->name(Account => $account),
                             SOAP::Data->name(Password => $password),
                             SOAP::Data->name(AnzLines => $getJournal),
                             SOAP::Data->name(OffSet => 0)
                            );
  # ResultCodes für Funktion "Journal" sind leider undokumentiert,
  # also benutzen wir halt ResultCodesStandard
  &checkResultCode($response->result, 0, \%ResultCodesStandard);

  my $Journal = &getAnswerVar($response, "JournalTab");

  print "OK Journal:\n";

  foreach my $var (@{$Journal})
  {
    print $var ."\n";
  }

  &cleanup_and_exit(0);
}

# das Dokument zusammenbauen und abschicken
##############################
my $jobart;
my $schalter = "";
my $SendText_string;

# was verschicken wir?
if ($doctype =~ /^fax|0$/i)
{
  $jobart = 0;

  &error_exit("Sorry, Faxen ist noch nicht implementiert.", 100);
}

elsif ($doctype =~ /^sms|1$/i)
{
  $jobart = 1;

  $schalter .= "SMSFROM=$absender,"  if $absender;
  $schalter .= "FLASHSMS,"  if $flashsms;

  # Fax.de mag keine Umlaute, aber UTF8
  my $to_utf8 = Text::Iconv->new('ISO8859-1', 'UTF-8');
  $SendText = $to_utf8->convert($SendText);

  # um das Typwandlungen (z.B. base64-Kodieren) zu vermeiden
  # (specify type explicitly and it won't be encoded as base64)
  $SendText_string = SOAP::Data->type(string => $SendText);

}

elsif ($doctype =~ /^e-?mail|2$/i)
{
  $jobart = 2;

  &error_exit("Sorry, E-Mail ist noch nicht implementiert.", 102);
}

elsif ($doctype =~ /^i?letter|3$/i)
{
  $jobart = 3;

  &error_exit("Sorry, iLetter ist noch nicht implementiert.", 103);
}

elsif ($doctype =~ /^i?lettercol(or)?|4$/i)
{
  $jobart = 4;

  &error_exit("Sorry, iLetterColor ist noch nicht implementiert.", 104);
}

elsif ($doctype =~ /^sign|8$/i)
{
  $jobart = 8;

  &error_exit("Sorry, Signieren ist noch nicht implementiert.", 108);
}

else
{
  &error_exit("unbekannter Dokumenttyp \"$doctype\".", 111);
}


# für alle Dokumenttypen gemeinsam
if ($staturl)
{
  $schalter .= "STATURL=\"$staturl\",";
}

# letztes, überflüssiges Komma wieder wegwerfen
$schalter =~ s/,$//;
&print_debug("Schalter: $schalter", 1);

# (specify type explicitly and it won't be encoded as base64)
my $schalter_string = SOAP::Data->type(string => $schalter);

if ($nodo)
{
  print "NODO: Folgendes wäre versendet worden:\n";
  print <<"EOT";
             \$soap->Send(SOAP::Data->name(Account => $account),
                          SOAP::Data->name(Password => $password),
                          SOAP::Data->name(JobArt => $jobart),
                          SOAP::Data->name(EmpfaengerNr => $recipients[0]),
                          SOAP::Data->name(SendText => $SendText),
                          SOAP::Data->name(Schalter => $schalter),
                       );
EOT
  print "OK (nodo)\n";
}

else
{
  $response = $soap->Send(SOAP::Data->name(Account => $account),
                          SOAP::Data->name(Password => $password),
                          SOAP::Data->name(JobArt => $jobart),
                          SOAP::Data->name(EmpfaengerNr => $recipients[0]),
                          SOAP::Data->name(SendText => $SendText_string),
                          SOAP::Data->name(Schalter => $schalter_string),
                       );
  # war alles okay?
  &checkResultCode($response->result, 0, \%ResultCodesSend);

  # Antwort auswerten
  my (@answers, $JobId, $AnzahlEmpfaenger, $AnzahlSeiten, $CheckDocFilename, $CheckDocFile);

  $JobId = &getAnswerVar($response, "JobId");
  $AnzahlEmpfaenger = &getAnswerVar($response, "AnzahlEmpfaenger");
  $AnzahlSeiten = &getAnswerVar($response, "AnzahlSeiten");
  $CheckDocFilename = &getAnswerVar($response, "CheckDocFilename");
  $CheckDocFile = &getAnswerVar($response, "CheckDocFile");

  &print_debug("JobID = $JobId", 1);
  &print_debug("AnzahlEmpfaenger = $AnzahlEmpfaenger", 1);
  &print_debug("AnzahlSeiten = $AnzahlSeiten", 1);
  &print_debug("CheckDocFilename = $CheckDocFilename", 1);
  &print_debug("CheckDocFile = $CheckDocFile", 1);


  # fehlerfrei abgeschlossen, JobId ausgeben
  print "OK $JobId\n";
}

&cleanup_and_exit(0);


####################################
# Hilfsroutinen
####################################

# liest eine Antwort-Variable aus einer SOAP-Response
#  1. Parameter: soap-Response Referenz
#  2. Parameter: Variablenname
# Rückgabe: Variablen-Wert oder "undef"
sub getAnswerVar
{
  my ($response, $match) = @_;

  my $value;
  if ($response->match("//$match"))
  {
    $value = $response->valueof;   # can be undef too
    if (defined $value)
    {
      &print_debug("$match: $value", 2);
      return $value;
    }
    else
    {
      &print_debug("$match: undef", 2);
      return "undef";
    }
  }
  else
  {
    &print_debug("$match doesn't exist", 2);
    return "undef";
  }
}

sub checkResultCode
{
  my ($result, $okResult, $ResultCodes) = @_;

  if ($result == $okResult)
  {
    if ($ResultCodes->{$result})
    {
      &print_debug($ResultCodes->{$result}, 1);
    }
    else
    {
      &print_debug("Server hat mit OK geantwortet.", 1);
    }
  }
  else
  {
    my $exitCode = 100+$result;
    $exitCode = 255  if $exitCode > 255;
    $exitCode = 1    if $exitCode < 0;

    if ($ResultCodes->{$result})
    {
      &error_exit($ResultCodes->{$result}, $exitCode);
    }
    else
    {
      &error_exit("unbekannter Rückgabe-Status ". $result .".", $exitCode);
    }
  }
}


sub print_error
{
  my ($text) = @_;

  print "ERROR: ". $text ."\n";

  $errors_occurred++;

  if ($errors_occurred > 10)
  {
    print "ERROR: Zu viele Fehler ($errors_occurred) aufgetreten -> Abbruch\n";
    &cleanup_and_exit();
  }
}

sub print_debug
{
  my ($text, $debug_level) = @_;

  $debug_level = 0  unless $debug_level;

  if ($debug >= $debug_level)
  {
    print "DEBUG($debug_level): ". $text ."\n";
  }
}

sub error_exit
{
  my ($text, $exitcode) = @_;

  &print_error("($exitcode) " . $text);
  &cleanup_and_exit($exitcode);
}

# nötige Aufräumarbeiten am Ende
sub cleanup
{
  &print_debug("cleanup done.", 2);
}

# Exitcode als optionaler Parameter
sub cleanup_and_exit
{
  my ($exitcode) = @_;
  $exitcode = 0  unless $exitcode;

  &cleanup();

  if ($errors_occurred)
  {
    &print_debug("Fertig, aber es sind $errors_occurred Fehler aufgetreten.\n", 1);
    exit 100+$errors_occurred  unless $exitcode;
  }

  &print_debug("$self (v$VERSION) erfolgreich beendet.\n", 1);
  exit 0;
}



#----------------------------------------------------------------------------
# Doku
#----------------------------------------------------------------------------

__END__

=head1 NAME

sendFAXde  --  sendet ein Dokument via XML-Soap zu fax.de

=head1 SYNOPSIS

C<sendFAXde> [--help|--usage] [--version] [--manpage] [--debug] [--nodo|--justprint]
 [--account] [--password] [--type] [--empfaenger] [--absender] [--sendtext] [--flashsms]
 [--staturl] [--testKdDaten] [--getKdDaten] [--getKontostand] [--getJournal]


=head1 DESCRIPTION

B<sendFAXde> verschickt ein Dokument (Fax, SMS, E-Mail, Brief) über den
SOAP-Service von fax.de.  Zwingend erforderlich sind die Parameter
B<account>, B<password>, B<empfaenger> und B<sendtext>.  Der Text kann auch
über STDIN übergeben werden.

=head1 OPTIONS

Alle Optionen können mit einem eindeutigen Anfang abgekürzt werden.

=over 3

=item B<--account>

Der Account-Name aus den Zugangsdaten.  Es wird alternativ aus der
Environmentvariable FAXDE_ACCT gelesen.

=item B<--password>

Das Passwort aus den Zugangsdaten.  Es wird alternativ aus der
Environmentvariable FAXDE_PASS gelesen.

=item B<--type>

Art des Auftrags
 0,fax         : Fax
 1,sms         : SMS
 2,email       : E-Mail
 3,letter      : iLetter (s/w)
 4,lettercolor : iLetter (s/w)
 8,sign        : Dokument zum Signieren hochladen (5min Warten vor dem Abholen)

=item B<--empfaenger>

Handy-Nummer der Empfänger.  Bisher ist nur ein einzelner Empfänger implementiert.

=item B<--absender>

Optionaler Parameter zur Angabe der Handy-Nummer des Absenders.
Default ist "Fax.de".

=item B<--sendtext>

Der Text-Inhalt des zu verschickenden Dokuments.  Kann auch über STDIN
übergeben werden.  Beim Rechnungsversand per E-Mail: individueller Mailbody
zur signierten Rechnung.

=item B<--flashsms>

Die SMS wird als Flash-SMS verschickt, d.h. sie erscheint direkt auf dem
Handy-Display des Empfängers.

=item B<--staturl>

Diese URL wird nach erfolgtem Versand aufgerufen.

Folgende Platzhalter sind möglich:
 $JID          JobID dieses Auftrages
 $RCV          Empfaenger-Nummer / Adresse
 $STAT         Sende-Status wie Aufruf ,,Status()" mit verkürztem Ergebniss
               (OK,BUSY,NOANSWER,VOICE,NORING,ISDNERR,NOSERVICE,BLACKLIST)
 $MSGS         Anzahl Seiten / Messages
 $COST         Sende-Gebühren in Euro netto + MwSt
 $RID          Remote-ID des Empfängers (nur FAX)
 $CID          Kunden-ID des Versenders (wird in [] hinter Empfänger-Nr. gegeben)

Beispiel:
 STATURL=www.fax.de/status&kdid=2010&JOBID=$JID&STAT=$STAT

=item B<--testKdDaten>

Vor dem Versand werden die Kundendaten überprüft.  Nur interessant für die
Fehlersuche.

=item B<--getKdDaten>

Frägt die gespeicherten Kundendaten ab.

=item B<--getKontostand>

Frägt den aktuellen Kontostand ab.

=item B<--getJournal>

Holt eine Journaltabelle der letzten Versendungen ab.  Im Parameter wird
angegeben, wieviele Einträge zurückgegeben werden sollen (Maximum 100).

=item B<--debug>

Debugmeldungen ausgeben (kann mehrfach angegeben werden, um detailliertere Informationen zu sehen).

=item B<--nodo>, B<--justprint>

Nichts wirklich ausführen, sondern nur so tun als ob.

=item B<--help>, B<--usage>

Syntax anzeigen

=item B<--manpage>

Die komplette Manpage anzeigen

=item B<--version>

Programmversion anzeigen


=head1 Links

Client and server side SOAP::LITE implementation
 http://cpan.uwinnipeg.ca/htdocs/SOAP-Lite/SOAP/Lite.html

User Guide
 http://guide.soaplite.com/

Cookbook
 http://cookbook.soaplite.com/


=head1 EXITCODES

B<0>  Alles bestens

Alles andere bedeutet nichts Gutes.


=head1 AUTHOR

Dr. Andy Spiegl <A.Spiegl@kascada.com>

