➿ E-Mail senden mit CURL

Begonnen von Torxgewinde, 17 Februar 2025, 20:31:24

Vorheriges Thema - Nächstes Thema

Torxgewinde

Danke Leute, ich habe versucht die Punkte unverfälscht in das Wiki zu übernehmen.

moskito

Der Monatswechsel hat Probleme erzeugt, denn wenn das Betriebssystem auf Deutsch eingestellt ist, gibt "strftime" den Monatsnamen ebenfalls auf Deutsch aus und ist damit - zumindest bei einigen Monaten - nicht mehr Emailkonform.
Abhilfe konnte ich bei mir mit einer zusätzlichen Zeilen schaffen:
state:message:[\s\S]* {
setlocale(LC_TIME, "C");
my $emailTo   = 'you@example.com';

Gruß
Danny

FHEM auf Intel NUC/Proxmox & Debian 12 + HM-CFG-USB + zigbee2mqtt + Zwave + Enocean

Torxgewinde

Danke, guter Hinweis, ich habe es im Wiki übernommen.

bertl

Um nicht irgendwelche Einstellungen zu verstellen, welche in anderen Funktionen abweichend benötigt werden, würde ich dann wie folgt vorschlagen:

my $old_locale = setlocale(LC_TIME);;\
setlocale(LC_TIME, "C");;\
my $emailDate = strftime("%a, %d %b %Y %H:%M:%S %z", localtime(time()));;\
setlocale(LC_TIME, $old_locale);;\

Gruß Robert

JoWiemann

Hm,

ich würde locale nicht verändern.
my @monEN = qw(January February March April May June July September October November December);
 my $mailDate = "";
 my @mailArray = split(" ", strftime("%a, %d %m %Y %H:%M:%S %z", localtime(time())));
 $mailArray[2] = $monEN[$mailArray[2]-1];
 
 for my $i (0 .. $#mailArray) {
   $mailDate .= $mailArray[$i] . " ";
 }

 chop($mailDate); # letztes Leerzeichen aus Schleifendurchlauf entfernen

Geht bestimmt noch eleganter.

Grüße Jörg
Jörg Wiemann

Slave: RPi B+ mit 512 MB, COC (868 MHz), CUL V3 (433.92MHz SlowRF); FHEMduino, Aktuelles FHEM

Master: CubieTruck; Debian; Aktuelles FHEM

bertl

#35
Entsprechend des E-Mail Standards RFC-5322 muss das Datums-Format wie folgt sein:

... aus mit Leerzeichen getrennten Werten für den Tag (numerisch), der Monatsbezeichnung (englisches Textkürzel), der Jahreszahl (vierstellig), der Uhrzeit (im Format ,,hh:mm") sowie deren Abweichung von der koordinierten Weltzeit (UTC).
Optional sind die Angaben einer Tagesbezeichnung am Anfang des Eintrags (englisches Textkürzel und Komma) und der Sekunden in Zeitwerten (,,hh:mm:ss") ...


    https://de.wikipedia.org/wiki/Header_(E-Mail)#Date:_Absendedatum_und_Uhrzeit
    https://datatracker.ietf.org/doc/html/rfc5322#page-15

Wenn man locale nicht verändern sollte (laut Jörg - keine Ahnung warum) auch wenn man locale gleich wieder zurück setzt (so wie vorher von mir beschrieben), dann würde sich folgende andere Lösung anbieten:

Den optionalen Wochentag %a weg lassen und den Monatsnamen so ähnlich wie Jörg ersetzen.

my %monate = ("Jän"=>'Jan',"Feb"=>'Feb',"Mär"=>'Mar',"Apr"=>'Apr',"Mai"=>'May',"Jun"=>'Jun',"Jul"=>'Jul',"Aug"=>'Aug',"Sep"=>'Sep',"Okt"=>'Oct',"Nov"=>'Nov',"Dez"=>'Dec');
my $emailDate = strftime("%d %b %Y %H:%M:%S %z", localtime(time()));
my $monat = (split(" ",$emailDate))[1];
$emailDate =~ s/$monat/$monate{$monat}/g;

Viele Wege führen nach Rom - keine Ahnung welcher der Beste ist!

Gruß Robert

JoWiemann

Zitat von: bertl am 05 Mai 2025, 18:11:41Wenn man locale nicht verändern sollte (laut Jörg - keine Ahnung warum) auch wenn man locale gleich wieder zurück setzt (so wie vorher von mir beschrieben), dann würde sich folgende andere Lösung anbieten:

Den optionalen Wochentag %a weg lassen und den Monatsnamen so ähnlich wie Jörg ersetzen.

Gruß Robert


In gibt Module die nonBlocking Funktionen starten, in denen localtime genutzt wird. Doof, wenn der Zuviel genau dann localtime aufruft wenn locale verändert worden ist.

Grüße Jörg
Jörg Wiemann

Slave: RPi B+ mit 512 MB, COC (868 MHz), CUL V3 (433.92MHz SlowRF); FHEMduino, Aktuelles FHEM

Master: CubieTruck; Debian; Aktuelles FHEM

moskito

Noch ein Lösungsansatz, dazu wird allerdings ein Modul benötigt, welches in den meisten Fällen nachinstalliert werden muss.
Hat man Email::Date::Format / libemail-date-format-perl installiert kann mit folgendem Code ein konformes Date-Feld erzeugt werden:
state:message:[\s\S]* {
use Email::Date::Format qw(email_date);
my $emailTo   = 'du@example.com';;\
my $emailFrom = 'fhem@example.de';;\
my $emailServer = 'mail.your-server.de';
my $emailDate = email_date();

Ist leider mit etwas Aufwand verbunden, aber dafür wird nichts mehr verbogen.

Gruß
Danny
FHEM auf Intel NUC/Proxmox & Debian 12 + HM-CFG-USB + zigbee2mqtt + Zwave + Enocean

Torxgewinde

#38
Moin,
Danke für die Diskussion:

Ich würde sagen, @bertl hat da mit der Lösung #1 mit dem Zwischenspeichern der locale und dem Wiederherstellen  unter den Optionen eine gleichzeitig saubere, einfache und wartungsarme Lösung. Alternativ #2 wäre das Enumerieren per String-Array wie von @JoWiemann genauso OK.

Die Lösung #3 die Monate in ein Hash (Sprache1 => Englisch) zu packen und zu holen ist nicht so gut, da müssten wir ja wissen von welcher Sprache aus wir starten um es übersetzen zu können. Die Monate zu enumerieren und mit dem Index zu holen #2 ist da robust unabhängig von der aktuellen locale.

Zu dem Punkt mit den vermuteten Nebenwirkungen auf bereits geforkte Prozesse: Sobald ein Prozess geforkt ist, ist er im ersten Moment eine "Kopie" vom Parentprozess, aber ab da laufen die Prozesse getrennt (inklusive locale). nonBlocking in FHEM arbeitet mit echtem fork() und nicht mit Threads oder Ähnlichem. Der Elternprozess in FHEM ist nur ein einzelner Thread und in dem Codefragment im UserReading nicht unterbrechbar, deswegen kann man dort unbesorgt die locale einmal ändern und wiederherstellen.

Ich tendiere zu der Idee #1, da sie eben keine Wechselwirkungen auf die anderen Kindprozesse hat und mit üblichen POSIX Funktionen auskommt:
my $old_locale = setlocale(LC_TIME);
setlocale(LC_TIME, "C");
my $mailDate = strftime("%a, %d %b %Y %H:%M:%S %z", localtime);
setlocale(LC_TIME, $old_locale);

Meinungen? Ideen?

Edit #1: Das Modul Email::Date::Format zu nutzen #4, wie von @moskito erwähnt, hängt ggf. einige Nutzer ab, die das Modul dann nicht installiert bekommen. Von der Kompaktheit/Übersicht ist es natürlich sehr gut. Was sich allerdings lohnt, ist zu schauen wie es dort gelöst wurde: https://metacpan.org/dist/Email-Date-Format/source/lib/Email/Date/Format.pm Die nutzen also im Grunde auch den Index und lösen das dann mit Arrays in Strings auf. Ich denke, dann mach ich das auch einfach so, nur eben "zu Fuß" und ohne Bibliothek...



Beta-User

Zitat von: Torxgewinde am 09 Mai 2025, 07:39:23Meinungen?
Imo ist das mit dem simplen String-Array (#2) v.a. für Einsteiger nachvollziehbarer, als (nicht als solche direkt erkennbare) POSIX-Funktionen aufzurufen - unabhängig davon, ob das nur Auswirkungen auf einen geforkten Prozess hat, oder tatsächlich kurzfristige Änderungen auf OS-Ebene zeitigt.

Die Schleife+chop könnte man ggf. durch ein "join" ersetzen.
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: ZigBee2mqtt, MiLight@ESP-GW, BT@OpenMQTTGw | ZWave | SIGNALduino | MapleCUN | RHASSPY
svn: u.a Weekday-&RandomTimer, Twilight,  div. attrTemplate-files, MySensors

bertl

Die Lösung von Beta-User gefällt mir am Besten!

Was dann als fertiger Codeschnipsel so aussehen könnte:

my @mailArray = split(" ",strftime("%w %d %m %Y %H:%M:%S %z",localtime(time())));
$mailArray[0] = (qw[Sun Mon Tue Wed Thu Fri Sat])[$mailArray[0]].",";
$mailArray[2] = (qw[Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec])[$mailArray[2]-1];
my $emailDate = join(" ",@mailArray);

Beta-User

Danke für die Rückmeldung :) !

Dasselbe dann nochmal mit etwas weniger "Folklore" zur Klammersetzung bei built-in-Funktionen und Interpolationen:
my @mailArray = split m{\s+}x, strftime("%w %d %m %Y %H:%M:%S %z",localtime( time ));
$mailArray[0] = (qw[Sun Mon Tue Wed Thu Fri Sat])[$mailArray[0]].',';
$mailArray[2] = (qw[Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec])[$mailArray[2]-1];
my $emailDate = join ' ', @mailArray;

Generell bin ich aber weiter der Auffassung, dass man solche Funktionen nicht in userReadings-Attributen direkt unterbringen sollte, und dann auch nochmal bzgl. Interpolation der eigentlichen Anweisung einen Blick auf "qq" (oder eventuell here-doc) werfen könnte...
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: ZigBee2mqtt, MiLight@ESP-GW, BT@OpenMQTTGw | ZWave | SIGNALduino | MapleCUN | RHASSPY
svn: u.a Weekday-&RandomTimer, Twilight,  div. attrTemplate-files, MySensors

Torxgewinde

Ok, zusammengefasst bin ich dann bei dieser Lösung, die die locale nicht verändert und sich stark an den Ideen aus der Implementation von Email::Date::Format / libemail-date-format-perl orientiert:

defmod sendMail dummy
attr sendMail readingList message password
attr sendMail setList message:textField-long password
attr sendMail userReadings state:message:[\s\S]* {\
    my $emailTo   = 'du@example.com';;\
    my $emailFrom = 'fhem@example.de';;\
    my $emailServer = 'mail.your-server.de';;\
    \
    #erzeuge Datumsangabe konform zu RFC-5322:\
    my @emailDateArray = split(' ', strftime("%w %d %m %Y %H:%M:%S %z", localtime));;\
    $emailDateArray[0] = (qw[Sun Mon Tue Wed Thu Fri Sat])[$emailDateArray[0]] . ',';;\
    $emailDateArray[2] = (qw[Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec])[$emailDateArray[2]-1];;\
    my $emailDate = join(' ', @emailDateArray);;\
    \
    # Passwort aus getKeyValue abrufen\
    my ($err, $emailPass) = getKeyValue("${name}_password");;\
    if ($err || !defined $emailPass) {\
        return "Error retrieving password: $err";;\
    }\
    \
    my $message = ReadingsVal($name, 'message', '???');;\
    my $subject = "$name: FHEM Nachricht";;\
    \
    # Betreff extrahieren und aus der Nachricht entfernen\
    if ($message =~ s/(?:Subject|Betreff)=["'](.*?)["']//) {\
        $subject = $1;;\
    }\
    \
    # Empfänger extrahieren und aus der Nachricht entfernen\
    if ($message =~ s/(?:To|An)=["'](.*?)["']//) {\
        $emailTo = $1;;\
    }\
    \
    #Dateianhänge bestimmen\
    my @attachments;;\
    while ($message =~ s/(?:Attachment|Anhang)=["'](.*?)["']//) {\
        my $file = $1;;\
        return ">>$file<< not found, not sending email" unless (-e $file);;\
        push (@attachments, $file);;\
    }\
    \
    #der curl Befehl wird hier aufgebaut:\
    my $cmd = "curl -m 19 --noproxy '*' --no-progress-meter --ssl-reqd smtps://$emailServer:465 ";;\
    $cmd .= "--user '$emailFrom:$emailPass' --mail-from '$emailFrom' --mail-rcpt '$emailTo' ";;\
    $cmd .= "--write-out 'Email sent with status %{http_code}' ";;\
    $cmd .= "-H 'Subject: $subject' ";;\
    $cmd .= "-H 'From: $emailFrom' ";;\
    $cmd .= "-H 'Date: $emailDate' ";;\
    \
    #multipart/alternative für Text und HTML EMail Body\
    $cmd .= "-F '=(;;type=multipart/alternative' ";;\
    \
    # Leerzeichen und <br> bzw <br \> am Anfang entfernen:\
    $message =~ s/^(\s|<br(?:[ \/]+)>)+//;;\
    # Leerzeichen und <br> bzw <br \> am Ende entfernen:\
    $message =~ s/(\s|<br(?:[ \/]+)>)+$//;;\
    \
    #Plaintext E-Mail Inhalt an cURL:\
    $cmd .= "-F \"=$message;;type=text/plain;; charset=UTF-8\" ";;\
    #ersetze \n bzw. \r\n durch ein HTML-tag <br />:\
    $message =~ s/\r?\n/<br \/>/g;;\
    \
    #HTML E-Mail Inhalt an cURL:\
    $cmd .= "-F '= <body>$message</body>;;type=text/html;; charset=UTF-8' ";;\
    $cmd .= "-F '=)' ";;\
    \
    #jetzt alle Attachments (0..N) anhängen:\
    foreach my $file (@attachments) {\
        $cmd .= "-F '=@".$file.";;encoder=base64' ";;\
    }\
    \
    #stderr auch mit in den output sammeln:\
    $cmd .= " 2>&1";;\
    \
    #Blockierender Aufruf von cURL in dieser Funktion via BlockingCall:\
    if (!defined &sendMailfunc1) {\
        *sendMailfunc1 = sub ($) {\
            my ($param) = @_;;\
            my $result;;\
            $result = qx/$param/;;\
            $result = MIME::Base64::encode_base64($result);;\
            $result =~ s/\n//g;;\
            return $result;;\
        }\
    }\
    \
    #Rückgabe über diese Funktion,\
    #anzeigen in dem reading dieses UserReadings:\
    if (!defined &sendMailfunc2) {\
        *sendMailfunc2 = sub ($) {\
            my ($result) = @_;;\
            my $hash = $defs{$name};;\
            $result = MIME::Base64::decode_base64($result);;\
            readingsSingleUpdate($hash, $reading, $result, 1);;\
        }\
    }\
    \
    #funktion falls BlockingCall abgebrochen wird:\
    if (!defined &sendMailfunc3) {\
        *sendMailfunc3 = sub ($) {\
            my ($result) = @_;;\
            my $hash = $defs{$name};;\
            readingsSingleUpdate($hash, $reading, $result, 1);;\
        }\
    }\
    \
    BlockingCall("sendMailfunc1", $cmd, "sendMailfunc2", 20, "sendMailfunc3", "Error: timeout");;\
    \
    return "started cURL command...";;\
},\
state:password:.* {\
    # Passwort speichern\
    my $ret = setKeyValue("${name}_password", ReadingsVal($name, 'password', undef)) // "password stored";;\
    \
    #password wieder aus der Variablen rausnehmen, soll nicht sichtbar bleiben:\
    #\
    # Hinweis: das Password taucht beim Setzen hierüber im Event-Log auf!\
    # Alternativ: { setKeyValue("sendMail_password", "geheimesPasswort") }\
    readingsBulkUpdate($hash, "password", "****");;\
    \
    return "$ret";;\
}

UvG

Hallo zusammen,
nach langen Versuchen habe ich es jetzt geschaft eine E-Mail zu versenden.

Es kam immer die Statusmeldung:

curl: option --no-progress-meter: is unknown
curl: try 'curl --help' or 'curl --manual' for more information

Jetzt habe ich versuchweise beim zusammensetzen des curl-Befehls no-progress-meter entfernt.

my $cmd = "curl -m 19 --noproxy '*' --ssl-reqd smtps://$emailserver:465 ";


und damit fuktioniert es bei mir.

Kann sich das jemand erklären?


Gruß
Ulrich

Torxgewinde

#44
@UvG: Ich vermute mal, dass die curl version alt ist. Was liefert curl --version? Die Version sollte aktueller als 7.67.0 sein, damit der Schalter da ist: https://github.com/curl/curl/issues/4422 bzw https://curl.se/ch/7.67.0.html