smtplib starttls bricht ab - fehlende Datei?

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Rotmilan
User
Beiträge: 32
Registriert: Mittwoch 30. Dezember 2020, 21:59
Wohnort: Nordbayern

Hallo zusammen,

ich habe ein kleines Programm für Python3 geschrieben, mit dem ich die smtplib ausprobieren wollte.

aus Diskretionsgründen ist manches natürlich aus-ge-ixt ;-)

Wie man an der Ausgabe dann sieht, läuft alles gut bis smtpObj.starttls aufgerufen wird, hier scheint eine Datei nicht gefunden zu werden, und ich weiß nicht warum. Googeln hat nicht wirklich weitergeholfen, darum versuche ich es jetzt mal hier...

Hier also nun der Code:

import smtplib

betreff = 'Testmail'
emailtext = 'Dies ist der Text der Testmail'

print('1')
smtpObj = smtplib.SMTP('mx.freenet.de', 587)
print('2')
print(smtpObj.ehlo())
print('3')
smtpObj.starttls('xxx@freenet.de', 'xxx')
print('4')
print(smtpObj.sendmail('xxx@freenet.de', 'xxx@freenet.de', betreff + '\n' + emailtext))
print('5')
print(smtpObj.quit())



Ausgabe ist dann wie folgt:

/home/.../xxx.py
1
2
(250, b'sub4.freenet.de Hello p57a43350.dip0.t-ipconnect.de [xx.xxx.xx.xx]\nSIZE 209715200\n8BITMIME\nPIPELINING\nAUTH LOGIN PLAIN CRAM-MD5\nSTARTTLS\nHELP')
3
/home/.../xxx.py:27: DeprecationWarning: keyfile and certfile are deprecated, use a custom context instead
smtpObj.starttls('xxx@freenet.de', 'xxx')
Traceback (most recent call last):
File "/home/.../xxx.py", line 27, in <module>
smtpObj.starttls('xxx@freenet.de', 'xxx')
File "/usr/lib64/python3.8/smtplib.py", line 770, in starttls
context = ssl._create_stdlib_context(certfile=certfile,
File "/usr/lib64/python3.8/ssl.py", line 787, in _create_unverified_context
context.load_cert_chain(certfile, keyfile)
FileNotFoundError: [Errno 2] No such file or directory

Process finished with exit code 1


Ich hoffe mir kann da jemand weiterhelfen ;-)
Achso, ich benutze Fedora32 mit PyCharm - falls das irgendwie relevant werden sollte...

LG
Benutzeravatar
__blackjack__
User
Beiträge: 14054
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Als erstes würde ich den Code mal so umschreiben das die `DeprecationWarning` nicht mehr kommt. Nützt ja nicht so viel einen Fehler in einem Ansatz zu beheben den man sowieso nicht mehr nutzen sollte. Was denkst Du denn was die Argumente von `starttls()` bedeuten? Vergleich das mal mit der Dokumentation.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase). Wobei dieser `Obj`-Zusatz eh unsinnig ist und einfach entfernt gehört. *Alles* was man an einen Namen binden kann ist in Python ein Objekt. Dieser abgekürzte Suffix bringt also keinen Erkenntnisgewinn für den Leser.

`SMTP`-Objekte sind Kontextmanager, das sollte man deshalb am besten mit der ``with``-Anweisung verwenden. Dann braucht man selbst nicht mehr `quit()` aufrufen und es wird in jedem Fall aufgerufen.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Rotmilan
User
Beiträge: 32
Registriert: Mittwoch 30. Dezember 2020, 21:59
Wohnort: Nordbayern

Hallo blackjack,

ich danke Dir für Deine Hilfe. Sie hätte durchaus kürzer ausfallen könne, denn ich habe mich bei „smtpObj.starttls“ vertan. Der Hinweis von dir, die Argumente zu überprüfen hat mich hier noch mal genauer hinschauen lassen. Ich habe hier die Zugangsdaten in die Klammer geschrieben, die aber tatsächlich in die „smtpObj.login“ gehört hätten, welche ich komplett vergessen hatte. :oops: :evil:

Was die Schreibweise der Namen angeht, so habe ich das auch schon gehört. Habe aber in dem Fall brav von meinem erst kürzlich erworbenen Buch (dpunkt.verlag – von Al Sweigart – mehr schreibe ich jetzt nicht – weiß nicht ob das mit Werbung dann kritisch wird hier) abgeschrieben.
Ich werde es jetzt aber wohl nicht gleich in die Tonne werfen, nur weil die von der Standardsyntax abweichen. Ich denke mal das ist einfach gemacht um es für Anfänger wie mich noch ein bisschen leserlicher zu machen. Aber auf jeden Fall danke für den Hinweis - das mit den Klassen und Konstanten war mir so noch nicht bewußt.

Nachdem ich das dann korrigiert hatte, ist mir noch aufgefallen, dass ich im E-Mailtext unbedingt "From:" (das war im Buch vergessen worden) und "Subject:" (das hingegen war im Buch gestanden) an den Anfang stellen muss.

Weiterhin habe ich bemerkt, dass ich Vorsichtig sein muss mit Links, da mir freenet gleich mal meine Nachricht als Spam abgelehnt hat. Auch für nachfolgende Nachrichten reichte es nicht den Link zu entfernen, sondern ich habe den Text deutlich ändern müssen. Da bin ich mal gespannt, ob das dann so weiter funktioniert, oder ob ich mir einen anderen Anbieter suchen muss, der nicht so streng ist.

Achso, das mit dem quit() wird auch in dem verwendeten Buch so empfohlen und funktiert auch. Wie das mit dem „with“ gemeint ist, verstehe ich in der knappen Ausführung leider nicht.

Ohne die Namens-Schönheitsfehler auszubessern funktioniert der Code jetzt in der folgenden Weise:

Code: Alles auswählen

import smtplib

def sendemail(nachricht, betreff='Standardbetreff'):
    pw = 'xyz'
    absender = 'sender@freenet.de'
    empfaenger = 'empfaenger@freenet.de'
    server = 'mx.freenet.de'
    port = 587

    smtpObj = smtplib.SMTP(server, port)
    smtpObj.ehlo()
    smtpObj.starttls()
    smtpObj.login(absender, pw)
    gesamtnachricht = 'From: ' + absender + '\n' + 'Subject: ' + betreff + 
       '\n' + nachricht
    x = smtpObj.sendmail(absender, empfaenger, gesamtnachricht)
    smtpObj.quit()

    return x

sendemail('Der Nachrichtentext, der Versand werden soll.')
Benutzeravatar
__blackjack__
User
Beiträge: 14054
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Rotmilan: Was meinst Du mit „noch ein bisschen leserlicher“?

Was in den Maildaten auch fehlt ist die Leerzeile die zwischen den Headern und dem Mailtext sein sollte. Das könnte auch ein Grund sein warum ein Mailserver die Mail ablehnt, oder zumindest mit höherer Wahrscheinlichkeit als Spam einstuft, denn E-Mail-Programme machen das in aller Regel korrekt, womit dieser Fehler auf ein Skript hindeutet, was wiederum eher Spammer verwenden würden.

Der Aufbau einer Nachricht wird in RFC5322 spezifiziert. In der Regel gilt das Programme die das verarbeiten etwas entspannter sein sollten was die Regeln betrifft und so einiges durchgehen lassen was nicht der Spezifikation entspricht, aber wenn man Sachen falsch macht, welche die verbreiteten E-Mail-Programme *nicht* falsch machen, steigt das Risiko das irgendwo ein Programm auf dem Weg das als Spam einstuft und wegfiltert.

Aber auch erlaubte aber ungewöhnliche Eigenschaften der Nachricht können für Spamfilter suspekt aussehen. Beispielsweise, dass kein "To:"-Header vorhanden ist. Ich kenne keinen E-Mail-Client der den nicht setzt.

Dein letztes Beispiel kommt nicht am Compiler vorbei, weil in der Zeile wo `gesamtnachricht` definiert wird ein Zeilenumbruck im Ausdruck ist, der so nicht erlaubt ist. Den könnte man beispielsweise machen wenn der Ausdruck in Klammern stünde, weil der Compiler weiss, dass ein Ausdruck noch nicht zuende sein kann solange noch austehende schliessende Klammern fehlen.

Das ``with``-Schlüsselwort kann man mit Objekten verwenden die `__enter__()` und `__exit__()` entsprechend implementieren, also Kontextmanager sind. Das sorgt dafür das beim Verlassen des ``with``-Blocks *egal aus welchem Grund*, die `__exit__()`-Methode aufgerufen wird, die in der Regel aufräumarbeiten enthält. Bei `SMTP`-Objekten ist das der Aufruf der `quit()`-Methode.

Zwischen Worte in Namen setzt man Unterstriche. So weiss man jetzt nicht ob die Funktion deutsch oder englisch benannt ist — `sende_mail()` oder `send_email()`?

Das zusammenstückeln von Zeichenketten und Werten mittels ``+`` ist eher BASIC als Python. Dafür gibt es die `format()`-Methode auf Zeichenketten und f-Zeichenkettenliterale.

Zeichenkettenliterale die nur durch „whitespace“-Zeichen getrennt sind, setzt der Compiler automatisch zu einer Zeichenkette zusammen. So kann man die Nachricht lesbarer auf mehrere Zeilen im Quelltext verteilen.

`x` ist ein schlechter Name für — ja was ist das denn eigentlich? Namen sollen dem Leser vermitteln was der Wert dahinter bedeutet. `x` tut das beim X-Anteil von Koordinaten oder für eine Gleitkommazahl für eine generische mathematische Funktion. Eher nicht für den Rückgabewert von `sendmail()`.

Zwischenstand (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
import smtplib

PASSWORT = "xyz"
ABSENDER = "sender@freenet.de"
EMPFAENGER = "empfaenger@freenet.de"
SERVER = "mx.freenet.de"
PORT = 587


def sende_mail(text, betreff="Standardbetreff"):
    with smtplib.SMTP(SERVER, PORT) as smtp:
        smtp.ehlo()
        smtp.starttls()
        smtp.login(ABSENDER, PASSWORT)
        return smtp.sendmail(
            ABSENDER,
            EMPFAENGER,
            (
                f"From: {ABSENDER}\n"
                f"To: {EMPFAENGER}\n"
                f"Subject: {betreff}\n"
                f"\n"
                f"{text}\n"
            ),
        )


def main():
    sende_mail("Der Nachrichtentext, der Versand werden soll.")


if __name__ == "__main__":
    main()
Das ist aber alles recht fragil. Alles was in die Nachricht kommt muss beispielsweise als ASCII kodierbar sein.

Man könnte sich eine Nachricht als Objekt zusammenbauen mit den Zutaten die man im `email`-Package in der Standardbibliothek findet, aber selbst das deckt nicht alle Fälle ab um die man sich wirklich nicht selbst kümmern möchte. Das ist alles nicht so wirklich praxistauglich. Da würde man ein externes Package wie `marrow.mailer` oder etwas vergleichbares benutzen.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Rotmilan
User
Beiträge: 32
Registriert: Mittwoch 30. Dezember 2020, 21:59
Wohnort: Nordbayern

Hallo blackjack,

vielen Dank, dass du dir noch mal so viel Mühe gemacht hast mit dem Antworten. Dein Code schaut ganz offensichtlich übersichtlicher aus.

Da ich das in ein größeres Programm einfüge, habe ich mir nicht die Mühe gemacht, die Struktur – also Shebang und die Einleitung des Hauptteils (also „if __name__ ….“ - gibt’s da auch nen Begriff für? Die Bedeutung weiß ich schon 😉) noch einzufügen.

Was ich hier nicht ganz verstehe: Warum du die Funktion main() noch „zwischengeschaltet“ hast.

Gut, und das mit with hast du ja dankenswerter Weise auch schon gut beschrieben.

Dein geänderter Beispiel-Code hilft mir auch ganz gut, weil ich das mit den f-Zeichenliteralen ganz sicher nicht verstanden hätte.

Gut, und zu guter letzt: Das mit x war jetzt auch nicht so ernst gemeint – aber das hast du ja sowieso eleganter gelöst - da kommt man als Anfänger nicht gleich drauf, auch wenn es logisch ist...

Vielen Dank!
Rotmilan
User
Beiträge: 32
Registriert: Mittwoch 30. Dezember 2020, 21:59
Wohnort: Nordbayern

Nachtrag @blackjack:

jetzt habe ich doch noch eine Frage: Es ist mir bisher nicht aufgefallen, dass du die Konstanten vor die Funktion geschrieben hast - ich dachte die wären dann in der Funktion gar nicht vorhanden, weil die ja erst mal nicht global sind? Oder irre ich da?

Und dann noch: Variablen und Konstanten: Für den Interpreter ist da kein Unterschied zwischen den beiden, oder irre ich da? Ich bin jetzt davon ausgegangen, dass die Großschreibung nur Formsache aber nicht syntaxrelevant ist?
Sirius3
User
Beiträge: 18274
Registriert: Sonntag 21. Oktober 2012, 17:20

Alle Namen auf oberster Ebene sind automatisch global. Und da es ja Konstanten sind, ist das auch gut so. Für den Compiler ist das egal, aber für Dich als Leser ist es wichtig zu wissen, das es sich um Konstanten handelt.
Rotmilan
User
Beiträge: 32
Registriert: Mittwoch 30. Dezember 2020, 21:59
Wohnort: Nordbayern

Auf oberster Ebene heißt vermutlich unbedingt vor Funktionen oder Klassen, denn der Interpreter muss die Konstanten ja da dann da auch schon kennen, sonst würde er sie zuerst in einer Funktion lesen, und dann wäre das ja nicht mehr auf oberster Ebene - liege ich da so richtig?

Es ergibt sich dabei für mich dann auch gleich noch die Frage, ob das für Variablen auch so gilt, dass diese global sind, wenn sie auf "oberster Ebene" schon einen Wert erhalten.
Sirius3
User
Beiträge: 18274
Registriert: Sonntag 21. Oktober 2012, 17:20

Ja, das ist schon richtig, aber dort sollte es ja erst gar keine Variablen geben.
Rotmilan
User
Beiträge: 32
Registriert: Mittwoch 30. Dezember 2020, 21:59
Wohnort: Nordbayern

ok, ja, das klingt logisch ;-)
danke :-)
Antworten