cgicorner.ch

Informationen rund um Perl/CGI

Sie sind hier: Home > CGI Hilfe > Tutorial > Kapitel 10 - Mailversand

Tutorial - Kapitel 10: Mailversand

Voraussetzungen für dieses Kapitel

  • Webserver mit Perl/CGI-Unterstützung
  • UNIX-Server mit sendmail oder Windows-Server mit Sendmail-Clone

Einleitung

Für viele Einsatzzwecke wird ein Mailversand gewünscht. Sei dies für ein Kontaktformular, ein Gästebuch mit Benachrichtigung bei neuem Eintrag oder vielem mehr. Dieses Thema befasst sich ausschliesslich mit dem Mailversand unter Linux-Servern mittels sendmail. Unter Windows existieren ebenfalls sendmail-Clones, welche aber teilweise nicht die volle Unterstützung bieten. Einige Clones werden in meiner Knowledge-Base vorgestellt. Allenfalls kann auch auf das Perl SMTP-Modul zurückgegriffen werden, welches für UNIX und Windows existiert.

Aufbau eines Mails

Bevor man Mails verschicken kann, muss man den groben Aufbau eines Mails kennen. Dieses sieht, korrekt aufbgebaut, wie folgt aus:

From: "Absendername" <absender@server.ch>
To: "Empfaengername" <empfaenger@server.ch>
CC: "Kopie Empfaenger" <empfaenger2@server.ch>
BCC: "Blindkopie Empfaenger" <empfaenger3@server.ch>
Subject: Betreff des Mails
X-Mailer: Mein Mailclient (Angabe optional)

Text des Mails
Zweite Zeile

Wichtig ist die Leerzeile zwischen Headerinformation und dem eigentlichen Text. Geht diese vergessen, so funktioniert gar nichts.

sendmail unter Perl

Wichtig bei sendmail und Perl ist vor allem der Parameter -t. Dieser bewirkt, dass die Empfänger anhand der Headerinformationen ausgelesen werden. Dadurch muss dieser nicht zweimal angegeben werden. Der Auszug der man-page sagt folgendes:

-t     Extract recipients from message headers. This requires that no
       recipients be specified on the command line.

Der Parameter -oi definiert, dass ein Mail durch einen allein stehenden Punkt auf einer Zeile nicht beendet werden kann und sollte der Vollständigkeit halber immer aufgeführt werden.

Der Pfad zu sendmail ist in der Regel übrigens /usr/sbin/sendmail.

In einer ersten kleinen Übung schreiben wir mal eine Subroutine, wie man es beispielsweise von PHP kennt.

# &mail("empfaenger","betreff","text","zus. headerinformationen");
sub mail {
  open(MAIL,"| /usr/sbin/sendmail -oi -t");
  if ($_[3] !~ m/From:/i) {     # kein Absender in zus. Headerinformationen
    print MAIL "From: absender\@server.ch\n"; # Default-Absender setzen
  }
  print MAIL "To: $_[0]\n";                   # Empfänger (1. Parameter)
  print MAIL "Subject: $_[1]\n";              # Betreff (2. Parameter)
  print MAIL "$_[3]\n" unless ($_[3] eq "");  # zus. Headerinformationen (4. Param)
  print MAIL "\n";                            # Leerzeile nach Header
  print MAIL $_[2];                           # Nachrichtentext (3. Parameter)
  close(MAIL);
}

Die Subroutine öffnet eine Pipe zu sendmail. Danach wird überprüft, ob in den zusätzlichen Headerinformationen kein "From:" vorkommt. Wenn nicht wird der Standard-Absender verwendet. Danach wird Empfänger und Betreff an die Pipe gesendet. Dann die zusätzlichen Headerinformationen, sofern vorhanden. Zuletzt noch eine Leerzeile und dann der Nachrichtentext.

Der Syntax, wie die Funktion aufgerufen wird, ist in der ersten Zeile (Kommentar) dargestellt.

Die Funktion lässt sich noch ausbauen, indem z.B. noch eine Datumszeile eingesetzt wird. Die Datumszeile ist wie folgt aufgebaut:

Date: Tue, 14 Oct 2003 20:25:14 +0200

HTML-Mails

Wenn anstelle von reinen Text-Mails HTML-Mails versendet werden sollen, so reicht eine zusätzliche Headerzeile aus:

Content-Type: text/html\n

Danach kann im Textteil eine HTML-Seite erstellt werden, inkl. <html>- und <body>-Tags. Beispiel:

From: absender@server.ch
To: empfaenger@server.ch
Subject: HTML-Mail
Content-Type: text/html

<html><body>Dies ist ein <b>Test</b> HTML-Mail.</body></html>

MIME-Mails

Mails mit Dateianhängen ist gemäss MIME-Standard möglich. Dabei werden Dateianhänge in der Regel 7-Bit codiert. Für diese Codierung hilft eine kleine Subroutine:

sub encodeBase64 {    # &encodeBase64($file)
  my ($res, $eol, $padding) = ("", "\n", undef);
  while (($_[0] =~ /(.{1,45})/gs)) {
    $res .= substr(pack('u', $1), 1);
    chop $res;
  }
  $res =~ tr#` -_#AA-Za-z0-9+/#;
  $padding = (3 - length($_[0]) % 3) % 3;
  $res =~ s#.{$padding}$#'=' x $padding#e if $padding;
  $res =~ s#(.{1,76})#$1$eol#g if (length $eol);
  return $res;
}

Angewendet wird die Funktion wie folgt:

print &encodeBase64("filename.zip");

Ebenfalls benötigt ein MIME-Mail noch ein sog. Boundary, bestehend aus einer eindeutigen Zeichenkette (z.B. Zufallszahl + UNIX-Time + Domain).

Komplett sieht ein Mail dann so aus:

From: absender@server.ch
To: empfaenger@server.ch
Subject: MIME-Mail mit Anhang
MIME-Version: 1.0
Content-Type: multipart/mixed;
	boundary="----=_31066248898.server.ch"

------=_31066248898.server.ch
Content-Type: text/plain;
	charset="iso-8859-1"
Content-Transfer-Encoding: 8bit

Hier ist der Text des Mails.

------=_31066248898.server.ch
Content-Type: application/x-zip-compressed;
	name="test.zip"
Content-Transfer-Encoding: base64
Content-Disposition: attachment;
	filename="test.zip"

[7-Bit MIME-code]

------=_31066248898.server.ch--

Missbrauch verhindern

In letzter Zeit werden immer öfters Formulare für den (meist millionenfachen) Spam-Versand missbraucht. Aus diesem Grund möchte ich in diesem Kapitel einmal ein paar mögliche Fehlerquellen detaillierter behandeln. Grundsätzlich existiert eine goldene Regel bei Mail-Formularen:

Es darf NIE gestattet sein, Zeilenumbrüche innerhalb des Headers einzufügen, da ansonsten ein Missbrauch eine Kleinigkeit darstellt!

Konkretes Beispiel gefällig? Bitte sehr:

open(MAIL,"| /usr/sbin/sendmail -oi -t");
print MAIL "From: $FORM{absender}\n";
print MAIL "To: webmaster\@server.ch\n";
print MAIL "Subject: Feedback\n\n";
print MAIL "Der Absender hat folgendes Feedback abgegeben:\n$FORM{feedback}";
close(MAIL);

Das oben genannte Formular stellt eine hohe Missbrauchsgefahr dar!. Gutgläubig geht der Entwickler davon aus, dass der Absender im Feld "absender" eine Mailadresse einträgt, unter "feedback" einen kleinen Text, also z.B.

absender hmuster@server.ch
feedback Tolle Seite!

Das fertige Mail sähe dann so aus und wäre völlig OK:

From: hmuster@server.ch
To: webmaster\@server.ch
Subject: Feedback

Der Absender hat folgendes Feedback abgegeben:
Tolle Seite!

Ein Spammer würde aber folgendes ins Formular eintragen:

absender spamsender@server.ch\n
BCC: empfaenger1@server.ch, empfaenger2@server.ch, ..., empfaenger1000@server.ch\n
Subject: Low price vi@agra\n
Content-Type: text/html\n
\n
<html><body>\n
Buy cheap vi@gra at <a href="http://www.viagra.com">http://www.viagra.com</a>\n
<body></html>\n
<!--
feedback ...

Das Mail sähe dann so aus:

From: spamsender@server.ch
BCC: empfaenger1@server.ch, empfaenger2@server.ch, ..., empfaenger1000@server.ch
Subject: Low price vi@agra
Content-Type: text/html

<html><body>
Buy cheap vi@gra at <a href="http://www.viagra.com">http://www.viagra.com</a>
<body></html>
<!--
To: webmaster\@server.ch
Subject: Feedback

Der Absender hat folgendes Feedback abgegeben:
...

Der eigentliche Empfänger und Betreff sind dabei im Kommentar des Mails verschwunden; der Spammer kann Absender, Empfänger, Betreff und Nachrichtentext frei wählen und seine Werbenachrichten millionenfach über Ihren Webserver versenden. Dabei spielt es keine Rolle, ob das Eingabefeld "absender" nur einzeilig ist und gar keine Zeilenumbrüche zulässt: Spammer müssen nicht ihr Eingabeformular verwenden, sondern können sich ihr eigenes mit Ihrem Script als action basteln.

Wie könnte das Formular nun verbessert werden? Grundsätzlich gilt es, Eingaben in Header-Feldern besonders genau zu prüfen. In unserem Beispiel war im Header nur eine Absender-Mailadresse vorhanden: ein einfacher Check auf eine korrekt formatierte Mailadresse, wie es im nächsten Kapitel als Beispiel aufgeführt wird, hätte also gereicht. Wenn aber auch variablere Teile wie Teil des Betreffs aus einem Formular stammen, hat man vielleicht nicht die Möglichkeit, den Text so genau einzuschränken. In diesem Fall reicht es schon aus, wenn man sagt, dass alles ausser Zeilenumbrüche gestattet ist. Dafür reicht eine kleine Regular-Expression, welche ich hier etwas vorgreifen möchte (Regular Expressions werden im nächsten Kapitel detailliert behandelt):

if ($FORM{"absender"} =~ m/[\r\n]/) {
  die("don't use this script to send spam mails!");
}

if ($FORM{"betreff"} =~ m/[\r\n]/) {
  die("don't use this script to send spam mails!");
}

Die drei Zeilen Code sollten für jede im Mail-Header verwendete Variable vor dem Mail-Versand (also dem Öffnen von sendmail) eingefügt werden. Die if-Abfrage macht nichts anderes, als bei einem Zeilenumbruch (\n oder \r) das Script mit der Ausgabe einer Fehlermeldung sofort zu beenden.

Zusammenfassung

Der Versand von Mails via sendmail erfolgt mit einer Pipe zu sendmail. Wichtig ist der Parameter "-t" bei sendmail, damit die Empfänger aus dem Header-Teil gelesen werden. Ansonsten entspricht der Mailversand einem Schreiben in eine Datei, da vor dem print-Befehl einfach noch der Name des Filehandlers hingeschrieben werden muss.

Die Header-Informationen sind jeweils mit einer Leerzeile vom eigentlichen Mailtext getrennt. HTML-Mails sind durch eine zusätzliche Header-Zeile möglich. Dateianhänge erfordern ein MIME-codiertes Mail mit durch Boundaries getrennten Abschnitten. Das komplizierte am Mailversand ist nicht die Technik, sondern die korrekte Formatierung des Mails.

Jedes neue Mailformular sollte dabei auf seine Missbrauchsgefahr geprüft werden. Dabei gilt die Grundregel "Variablen im Headerbereich von Mails müssen besonders sorgfältig geprüft werden, NIE darf ein Zeilenumbruch in einem solchen Feld erlaubt sein".

Wie geht's weiter?

Kapitel 11 befasst sich mit Regular Expressions, dem Herzstück von Perl.

Das vorherige Kapitel (9) beschäftigte sich Datei-Operationen.

Sie können aber auch zurück zum Inhaltsverzeichnis und dort ein beliebiges anderes Kapitel auswählen.