Seite 1 von 2
einfacher SMTP-Server
Verfasst: Montag 26. November 2007, 10:54
von droptix
Ich möchte einen einfachen SMTP-Server schreiben, der E-Mails aus dem lokalen Netzwerk empfängt und dann folgendes macht:
- E-Mails ohne Anhang werden einfach ignoriert (gelöscht)
- E-Mails mit Anhang werden weiter verarbeitet: Anhang extrahieren und als Datei auf der Festplatte speichern; anschließend ursprüngliche E-Mail verwerfen (löschen)
Ich komme mit dem Modul
smtpd bzw. der extrem kurzen
Dokumentation nicht klar.
Habe
hier auch noch ein Beispiel namens SMTP Mailsink gefunden, aber das macht erstens was anderes und zweitens steige ich nicht richtig durch.
Hat jemand ein paar hilfreiche Tipps?
bin weiter, aber...
Verfasst: Mittwoch 28. November 2007, 09:41
von droptix
Ich hab mich durchgewühlt und bin nun ein Stück weiter. Der SMTP-Server steht und empfängt Mails. Die Methode
smtpd.SMTPServer.process_message() sieht bei mir erstmal so aus:
Code: Alles auswählen
def process_message(self, peer, mailfrom, rcpttos, data):
print peer, mailfrom, rcpttos, data
Und sie gibt aus:
D:\#devroot\Python-Tools\SMTPServer>C:\Programme\Python24\python.exe mail2file.p
y
server started
('127.0.0.1', 1428)
from@domain.de ['
to@domain.de'] Message-ID: <474D2808.
8040707@domain.de>
Date: Wed, 28 Nov 2007 09:34:16 +0100
From: droptix <
from@domain.de>
User-Agent: Thunderbird 2.0.0.9 (Windows/20071031)
MIME-Version: 1.0
To: SMTPServer <
to@domain.de>
Subject: Betreff
Content-Type: multipart/mixed;
boundary="------------080205020705070304070307"
This is a multi-part message in MIME format.
--------------080205020705070304070307
Content-Type: text/plain; charset=ISO-8859-15; format=flowed
Content-Transfer-Encoding: 7bit
Text
--------------080205020705070304070307
Content-Type: application/x-shockwave-flash;
name="test_attachment.swf"
Content-Transfer-Encoding: base64
Content-Disposition: inline;
filename="test_attachment.swf"
Q1dTBsg9AAB4nN27dVxVzdc4Ovtw6O4SOHR3q0gJ0pLSSHd3KQdUBMSgU0EQkFS64UgoUtLS
[...]
wQ2u/2DK3yEY3ZHWBeDmhWkxvf9fq/sWhA==
--------------080205020705070304070307--
Ich möchte nun den Anhang extrahieren und ihn unter seinem urprünglichen Dateinamen speichern. Außerdem benötige ich die Empfänger-E-Mail-Adresse.
Ideen dazu?
Verfasst: Mittwoch 28. November 2007, 10:30
von gerold
Hallo droptix!
Mit dem mail-Modul kannst du Emails auseinander nehmen.
mfg
Gerold

danke
Verfasst: Mittwoch 28. November 2007, 13:53
von droptix
Hat prima funktioniert. Du meintest das Modul
email.
Für alle die es interessiert, ich hab es so gelöst:
Code: Alles auswählen
import email
# [...]
class MyClass(smtpd.SMTPServer):
# [...]
def process_message(self, peer, mailfrom, rcpttos, data):
print "%s Getting email message from %s to %s" % (time.strftime("%Y/%m/%d %H:%M:%S"), mailfrom, ", ".join(rcpttos))
# parse email data
msg = email.message_from_string(data)
if msg.is_multipart():
# extract attachments
for part in msg.walk():
filename = part.get_filename()
if filename:
attachment = part.get_payload()
# write attachment
for r in recipients:
fp = file(filename, "wb")
fp.write(attachment.decode("base64"))
fp.close()
Wäre noch cool, wenn man die Decodierung "base64" automatisch erkennt... obwohl kann es denn sein dass bei Dateianhängen eine andere Codierung in Frage kommt?
Re: danke
Verfasst: Mittwoch 28. November 2007, 14:28
von gerold
droptix hat geschrieben:Du meintest das Modul
email.

sorry, die Hand war schneller als das Hirn.
mfg
Gerold

Verfasst: Mittwoch 28. November 2007, 21:23
von droptix
Ah, danke! Werd ich dann gleich testen...
Nochwas anderes: ich würde das kleine Tool gern als Dienst laufen lassen. Unter Unix muss es also die Parameter "start", "restart" und "stop" kennen. Wenn ich das Tool starte, dann wird es ja nie beendet... weil es via Thread in einem Server-Loop hängt.
Wie könnte ich am besten an diese Problematik rangehen? Gibt's dazu gute Literatur/Tutorials im Netz?
Verfasst: Mittwoch 28. November 2007, 22:00
von gerold
droptix hat geschrieben:Unter Unix muss es also die Parameter "start", "restart" und "stop" kennen.
Hallo droptix!
Nein. Nicht dein Programm kennt diese Parameter, sondern das Init-Skript das für dein Programm zuständig ist. Dieses Init-Skript (sieh dir mal /etc/init.d/skeleton an) reagiert dann auf die übergebenen Parameter ("start", "stop",...) und sendet an dein Programm ein Signal. Dein Programm reagiert dann auf dieses Signal. Das übermitteln des Signals übernimmt oft das Programm "start-stop-daemon". Dafür ist es ideal, wenn dein Programm ein PID-File mit der eigenen Prozessnummer hinterlegt. Diese wird dann verwendet um ein Signal an dein Programm zu schicken.
-
http://docs.python.org/lib/module-signal.html
- man kill
- man start-stop-daemon
mfg
Gerold

Verfasst: Mittwoch 28. November 2007, 22:17
von Leonidas
Die für Daemons wichtigen Signale wären vor allem SIGTERM (exit gracefully) und SIGHUP (reload). Man könnte auch SIGKILL dazuzählen, aber da ein Prozess sowieso nicht auf SIGKILL reagieren kann ist es nicht sonderlich wichtig..
Verfasst: Donnerstag 29. November 2007, 16:40
von droptix
gerold hat geschrieben:[Dieses Init-Skript (sieh dir mal /etc/init.d/skeleton an) reagiert dann auf die übergebenen Parameter ("start", "stop",...) und sendet an dein Programm ein Signal. Dein Programm reagiert dann auf dieses Signal.
Hab ein Verständnisproblem: Wie kann denn das "isoliert" laufende Programm vom Init-Skript angesprochen werden und darauf reagieren? Ich hätte mir es jetzt wieder nur via SimpleXMLRPCServer (oder eine Art Server basierend auf Sockets) vorstellen können, aber damit schießt man ja gleich mit Kanonen auf Spatzen... Oder man liest ständig eine Datei ein und erwartet dort drin ein Signal, was die Performance sicher beeinträchtigt.
Kann man sagen: "Schicke Befehl an den Prozess mit der Nummer X"? Und das Programm hinter dem Prozess X kann das Signal empfangen?
Verfasst: Donnerstag 29. November 2007, 16:47
von monocult
hab selbst so was gesucht bin aber noch nicht dazu gekommen es zu testen.
demon + init.d script
http://homepage.hispeed.ch/py430/python/daemon.py
http://homepage.hispeed.ch/py430/python/daemon
Verfasst: Donnerstag 29. November 2007, 16:49
von Rebecca
droptix:
http://en.wikipedia.org/wiki/Signal_%28computing%29
Wenn du kill oder ctrl-z benutzt, tust du auch nichts anderes, als Signale versenden.
Verfasst: Donnerstag 29. November 2007, 17:13
von droptix
Rebecca hat geschrieben:Wenn du kill oder ctrl-z benutzt, tust du auch nichts anderes, als Signale versenden.
Ja, aber nur direkt im Programm selber, während es läuft... dort kann ich einen KeyboardInterrupt abfangen. Ich habe quasi einen "Listener" mitlaufen, der ständig danach lauscht, oder?
Aber kann ein zweites Programm z.B. ein Ctrl+C Signal an ein laufendes Programm senden?
Verfasst: Donnerstag 29. November 2007, 17:29
von Rebecca
Falsch: Wenn du in der Shell Ctrl-C drueckst, sendet der Shell-Prozess an deinen Prozess ein Signal. Du bist in der Shell nie "direkt im Programm selber", sie Shell haengt immer dazwischen. Ja, du kannst den Prozessen, die dir gehoeren, verschiedene Signale von anderen Prozessen aus senden.
Aber wie gesagt, lies dir den Wikipedia-Artikel durch, oder die kill-Manpage, oder Python-Dokumentation zu kill, oder...
Verfasst: Donnerstag 29. November 2007, 17:34
von Rebecca
Nachtrag: Probier mal
aus und schau dir an, was das Programm ausgibt.

Aber fuer deinen urspruenglichen Zweck sind andere Signale nuetzlicher, wie Leonidas schon sagte.
Verfasst: Freitag 30. November 2007, 18:51
von droptix
Also ich hab mich ein wenig versucht durchzuwühlen, es scheitert aber an ein paar anschaulichen Beispielen...
Ich kann mir nun vorstellen, dass ein Programm ein Signal an einen Prozess sendet. Nun muss aber mein Prozess das Signal noch empfangen und entsprechend reagieren -> dort scheitert es.
Aus anderen Programmiersprachen kenne ich so etwas wie "listener" oder auch "event listener", die z.B. auf Maus- oder Tastendrücke reagieren. Eine vordefinierte (leere) Methode so einer Listener-Klasse kann man überschreiben und dann sinngemäß etwa sowas hier machen:
Code: Alles auswählen
class Listener():
__signal = None
def listen(self):
if self.__signal == signal.SIGTERM:
# close all running stuff and terminate program
pass
elif self.__signal == signal.SIGHUP:
# close and restart
pass
Auch mit dem
Python-Doc-Beispiel komme ich nicht so richtig weiter. Hätte jemand für mich ein anschauliches Beispiel bestehend aus zwei Teilen:
a) Python-Programm, welches in einem Endlos-Loop läuft; bei Signal SIGTERM wird es beendet, bei Signal SIGHUP wird die Endlosschleife verlassen und neu gestartet
b) ein Python-Programm welches ein Signal an a) sendet; ich nehme an b) muss dazu erst a) starten und sich die Prozess-Id merken
Verfasst: Freitag 30. November 2007, 19:35
von gerold
Hallo droptix!
Code: Alles auswählen
#!/usr/bin/env python
# -*- coding: iso-8859-15 -*-
import signal
import time
class Signals(object):
canceled = False
reload_it = False
signals = Signals()
def signal_handler(signum, frame):
print 'Signal handler called with signal', signum
if signum == signal.SIGTERM:
signals.canceled = True
elif signum == signal.SIGHUP:
signals.reload_it = True
def main():
signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGHUP, signal_handler)
while True:
if signals.canceled:
print "Canceled"
break
if signals.reload_it:
signals.reload_it = False
print "Reloaded"
continue
time.sleep(1)
print "."
if __name__ == "__main__":
main()
mfg
Gerold

Verfasst: Freitag 30. November 2007, 19:43
von Leonidas
Also a) war wirklich nicht so schwer.
Code: Alles auswählen
import signal, time
class Listener(object):
def complain(self, signum, frame):
self.loop = False
def run(self):
signal.signal(signal.SIGHUP, self.complain)
runs = 0
while True:
runs += 1
print "Starting run %d" % runs
self.loop = True
while self.loop:
time.sleep(1)
Listener().run()
Den Handler zu SIGTERM habe ich mir gespart, da das Programm beendet wird, auch wenn man nicht darauf reagiert. Wenn man vor dem Beenden Code ausführen will, würde ich eher auf `atexit` zurückgreifen.
b) habe ich mir gespart, stattdessen
kill -s HUP `pidof python` verwendet.
Verfasst: Samstag 1. Dezember 2007, 12:41
von HWK
Hallo, Gerold!
In Deinem Beispiel leuchtet mir die Objektorientierung nicht ein. Wenn man eine Klasse Signals schafft, sollte man dann nicht signal_handler als Methode dieser Klasse konstruieren anstatt als globale Funktion? Wenn man aber doch die globale Funktion verwendet, kann man dann nicht auf Signals verzichten und die Klassenattribute als globale Variablen definieren? Man hat ja mit signals sowieso eine globale Variable?
MfG
HWK
Verfasst: Samstag 1. Dezember 2007, 13:29
von gerold
HWK hat geschrieben:In Deinem Beispiel leuchtet mir die Objektorientierung nicht ein.
Hallo HWK!
Mein Beispiel hat mit OO nichts am Hut. Und globale Variablen? Kenn ich nicht!
lg
Gerold

Verfasst: Samstag 1. Dezember 2007, 15:12
von veers
Zum Glück ist signals keine globale Variable! *duck*