Verbindung zu SMTP Server ohne telnet oder nc testen

Um die Verbindung zu einem SMTP Server zu testen um zum Beispiel einen Fehler durch eine Firewall auszuschließen nutze ich normalerweise telnet:

user@host ~ $ telnet mail.example.net 25
Trying 1.2.3.4...
Connected to mail.example.net.
Escape character is '^]'.
220 mail.example.net ESMTP Postfix

Wenn telnet nicht installiert ist geht das auch noch mit netcat:

user@host ~ $ nc mail.example.net 25
220 mail.example.net ESMTP Postfix

Heute hatte ich ein System auf dem beides nicht existierte. Da habe ich eine Methode mit python gefunden die sehr gut funktioniert hat:

user@host ~ $ python
Python 2.7.9 (default, Mar  1 2015, 12:57:24) 
[GCC 4.9.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import socket
>>> conn=socket.create_connection(('mail.example.net',25))
>>> input=conn.makefile()
>>> print input.readline()
220 mail.example.net ESMTP Postfix

>>> conn.sendall('QUIT')
>>> conn.close()
>>> 

Smarthome? Monitoring? Notifications mit altem Android Handy!

Heute morgen bin ich über die coolste App seit langem gestolpert. Es handelt sich um den Home24-MediaPlayer (Google Play oder direkter Download der APK).

Die App ist nicht etwa ein stupider Media Player wie der Name vermuten würde, sondern eine ziemlich geniale Möglichkeit um Benachrichtigungen zu verschicken. Die App – einmal gestartet – lauscht per HTTP auf Port 50000 und kann per GET Parameter angewiesen werden unterschiedlichste Dinge zu tun. Man kann das Handy vibrieren lassen, eine Nachricht in den Benachrichtigungen anzeigen lassen oder den Bildschirm anschalten.
Einzelne Kommandos können mit einer | getrennt verknüpft werden. Kommandos mit Parametern können per & verknüpft werden. Das angesprochene Beispiel ist:

curl "http://192.168.1.1:50000/statusbar=Hallo Welt|vibrate|screentimeout=3"

Deutlich cooler sind aber die weiteren Funktionen. So ist es zum Beispiel möglich eine auf dem Gerät vorliegende MP3 abzuspielen. Damit kann man mit simplen Mitteln einen Türgong realisieren. Beispiel:

curl "http://192.168.1.1:50000/track=gong.ogg"

Noch besser finde ich die Text-To-Speach Funktion:

curl "http://192.168.1.1:50000/tts=Alarm! Der Einbruchssensor beim Fenster im Schlafzimmer wurde ausgelöst!"

Aber man kann – eine SIM Karte im Gerät vorausgesetzt – auch SMS verschicken. Ganz einfach:

curl "http://192.168.1.1:50000/sms=017612345678&message=Es ist keiner Zuhause, das Schlafzimmerfenster ist offen und es gibt eine Unwetterwarnung vor starkem Gewitter."

Eine Übersicht der Möglichen Befehle gibt es auf der Webseite vom Home24-MediaPlayer.

Der Phantasie sind hier keine Grenzen gesetzt. Benachrichtigungen auf dem Smartphone wenn man Zuhause ist, SMS wenn man nicht Zuhause ist, Türgong über einen angeschlossenen Lautsprecher und so weiter. Und als Sysadmin ist das eine supersimple günstige einfache Möglichkeit auch Benachrichtigungen zu bekommen…. Genial!

Anwesenheitserkennung für Smarthome mit der Fritz!Box via TR-064

Wer sich mit dem Kontext SmartHome beschäftigt, der hat früher oder später immer die Frage: Wie erkenne ich möglichst automatisiert, ob jemand zuhause ist oder nicht? Am einfachsten geht das mit dem Smartphone. Das hat man in der Regel bei sich. Der am häufigsten anzutreffende Ansatz ist dieses in regelmäßigen Abständen anzupingen und auf der Basis den Anwesenheitsstatus zu definieren.

Da ich mein Smartphone aber nicht anpingen kann fällt die Lösung für mich flach. Ich habe aber eine Fritz!Box und mein Handy bezieht darüber per DHCP eine IP-Adresse. In der Fritz!Box ist auch sichtbar, ob das Gerät aktiv oder nicht aktiv ist.

Die Fritz!Box hat eine Schnittstelle für Anwendungen basierend auf TR-064. Der Zugriff darauf muss in dem Webinterface unter

  • Heimnetz ->
  • Heimnetzübersicht ->
  • Netzwerkeinstellungen ->
  • Zugriff für Anwendungen zulassen

freigeschaltet werden. Anschließend kann via SOAP mit der Fritz!Box kommuniziert und unter anderem auch der Status eines angeschlossenen Gerätes ermittelt werden. Dafür muss man nur die MAC-Adresse kennen.

Die Antwort der Fritz!Box sieht dann zum Beispiel so aus:

<?xml version="1.0"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
  <s:Body>
    <u:GetSpecificHostEntryResponse xmlns:u="urn:dslforum-org:service:Hosts:1">
      <NewIPAddress>11.22.33.44</NewIPAddress>
      <NewAddressSource>DHCP</NewAddressSource>
      <NewLeaseTimeRemaining>0</NewLeaseTimeRemaining>
      <NewInterfaceType>802.11</NewInterfaceType>
      <NewActive>1</NewActive>
      <NewHostName>mySmartphoneDevice</NewHostName>
    </u:GetSpecificHostEntryResponse>
  </s:Body>
</s:Envelope>

Für die Hausautomatisierung setze ich Homematic mit einer CCU2 ein. Dort habe ich eine Systemvariable definiert und über die XML API kann diese auch per GET aktualisiert werden. Dafür muss man nur die sogenannte ise_id der Variable kennen, die findet man unter der folgenden URL: http://ccu2/config/xmlapi/sysvarlist.cgi

Die Anfrage an die Fritz!Box und das anschließende setzen der Systemvariable in der CCU2 habe ich in PHP wie folgt gelöst:

<?php 
 
// Array of MAC addresses and coresponndig ise_id on ccu2
$check[] = array('mac' => 'AA:BB:CC:DD:EE:FF', 'ise_id' => '1234');
$check[] = array('mac' => '11:22:33:44:55:66', 'ise_id' => '4321');
 
 
for ($j = 0; $j < count($check); $j++) {
 
	$soapUrl = "http://fritz.box:49000/upnp/control/hosts";
 
	$xml_post_string = '<?xml version="1.0" encoding="utf-8"?>
<s:Envelope s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" >
  <s:Body>
    <u:GetSpecificHostEntry xmlns:u="urn:dslforum-org:service:Hosts:1">
      <NewMACAddress>' . $check[$j]['mac'] . '</NewMACAddress>
    </u:GetSpecificHostEntry>
  </s:Body>
</s:Envelope>';
 
	$headers = array(
		"Content-type: text/xml;charset="utf-8"",
		"Accept: text/xml",
		"Cache-Control: no-cache",
		"Pragma: no-cache",
		"SoapAction:urn:dslforum-org:service:Hosts:1#GetSpecificHostEntry",
		"Content-length: ".strlen($xml_post_string),
        ); 
 
 
 
	$ch = curl_init();
	curl_setopt($ch, CURLOPT_URL, $soapUrl);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
	curl_setopt($ch, CURLOPT_TIMEOUT, 10);
	curl_setopt($ch, CURLOPT_POST, true);
	curl_setopt($ch, CURLOPT_POSTFIELDS, $xml_post_string);
	curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
 
	$response = curl_exec($ch); 
	curl_close($ch);
 
	$parser = simplexml_load_string($response);
	$parser->registerXPathNamespace("a", "urn:dslforum-org:service:Hosts:1");
 
	$NewActive_h = $parser->xpath('//NewActive/text()');
	$NewActive =  $NewActive_h[0]->__toString();
 
	if ($NewActive == 1) {
		$result = file_get_contents("http://ccu2/config/xmlapi/statechange.cgi?ise_id=" . $check[$j]['ise_id'] . "&new_value=true");
	}
	else {
		$result = file_get_contents("http://ccu2/config/xmlapi/statechange.cgi?ise_id=" . $check[$j]['ise_id'] . "&new_value=false");
	}
}
?>

Als Cronjob laufen lassen irgendwo, zum Beispiel auf einem Raspberry Pi, und schon hat man eine korrekte Systemvariable in der CCU2 auf dessen Basis man dann weitere Dinge machen kann.

Wartungsseite für alle Apache vhosts eines Webservers realisieren

Über eine Lösung um einen einzelnen Apache Vhost mit einer Wartungsseite auszustatten habe ich bereits vor einigen Jahren hier geschrieben.

Jetzt stand ich vor der Aufgabe wegen Wartungsarbeiten auf meinem Webserver eine Wartungsseite für alle dort gehosteten Apache Vhosts zu schalten. Davor sitzt ein Gate-Server mit Apache und mod_proxy. Eine Lösung das zu realisieren wäre auf dem Gate-Server mit mod_rewrite nach dem Vorhandensein einer Maintenance-Datei zu schauen und wenn die Bedingung zutrifft alle Anfragen auf die Wartungsseite weiterzuleiten. Eine solche Lösung ist in Ansätzen unter anderem hier skizziert. Unschön finde ich dabei die unnötig vielen Prüfungen nach der Maintenance-Datei.

Ich habe nun eine andere Lösung realisiert, die in meinen Augen deutlich einfacher und eleganter ist. Auf dem Gate-Server lasse ich einen Apache-Vhost auf einem gesonderten Port laufen auf dem die Wartungsseite angezeigt wird. Für den Fall, dass ich die Wartungsseite vorschalten möchte, leite ich einfach allen HTTP und HTTPS Traffic auf die IP des Gate-Servers und den gesonderten Port um. Mit nur einer iptables Regel ist somit für alle vhosts auf dem Webserver eine Wartungsseite vorgeschaltet:

iptables -t nat -A OUTPUT -p tcp -d INTERNALWEBSERVERIP --match multiport --dports 80,443 -j DNAT --to-destination EXTERNALGATESERVERIP:PORT

Bei dem Apache Vhost auf dem Gateserver ist noch zu beachten, dass man nach Möglichkeit den aufrufenden Browsern / Bots etc. mitteilt, dass es sich um etwas temporäres handelt. Aus diesem Grund habe ich in dem Vhost zwei Dinge beachtet:

  1. Es wird ein HTTP Status Code 503 (Temporarily Unavailable) gesendet
  2. Es wird der HTTP Header Retry-After gesetzt

Im vhost sieht das wie folgt aus. Spannend sind die letzten 3 Zeilen. Meine Wartungsseite heißt übrigens auch 503.html

<VirtualHost *:PORT>
  ServerName maintenance.example.net
  ServerAdmin webmaster@example.net
 
  DocumentRoot "/var/www/maintenance"
 
  <Directory "/var/www/maintenance">
    Options -Indexes
    AllowOverride None
    Require all granted
  </Directory>
 
 
  ErrorLog "/var/log/apache2/maintenance.example.net_error.log"
  CustomLog "/var/log/apache2/maintenance.example.net_access.log" combined 
 
  RedirectMatch 503  ^/(?!503.html)  
  ErrorDocument 503 /503.html
  Header always set Retry-After "18000"
</VirtualHost>