#!/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("", ); 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 [--help|--usage] [--version] [--manpage] [--debug] [--nodo|--justprint] [--account] [--password] [--type] [--empfaenger] [--absender] [--sendtext] [--flashsms] [--staturl] [--testKdDaten] [--getKdDaten] [--getKontostand] [--getJournal] =head1 DESCRIPTION B verschickt ein Dokument (Fax, SMS, E-Mail, Brief) über den SOAP-Service von fax.de. Zwingend erforderlich sind die Parameter B, B, B und B. 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