Squeezebox mit einem gehosteten Rechner remote Verbinden.

Squeezebox

Die Logitech Squeezebox ist ein feines System um eine größere Musiksammlung endlich in ordentlicher Qualität auf eine Stereoanlage streamen zu können. Bisher hatte ich hierzu im LAN eine eeBox von Asus stehen, aber leider ist das auch nicht so sehr praktisch wenn immer erst ein PC gebootet werden muß bevor man gemütlich Musik hören kann. Es liegt also nahe die Musiksammlung auf einen gehosteten Rechner zu kopieren.

Leider beziehen sich die verfügbaren Anleitungen auf die unteressanten Nerdfälle, in denen man irgendwie von außen auf einen Rechner zuhause zugreifen will. Dabei ist der umgekehrte Fall viel interessanter: verschiedene Squeezeboxen am gleichen DSL Zugang wollen auf eine gemeinsame Musiksammlung auf einem gehosteten (root-)Server zugreifen.

Insbesondere wenn man eine Classic Squeezebox hat, ist das nicht so ganz einfach, denn hier gibt es keine Möglichkeit den Passwortschutz zu verwenden. Deshalb habe ich einen anderen Ansatz gewählt. Der Server wird per dynamisch konfigurierter Firewall nur für die aktuelle DSL IP freigegeben.

Schritt für Schritt Anleitung

Die Lösung besteht aus drei Teilen.

  • Eine Fritz!Box für den DSL Zugang (oder ein anderer Zugangsrouter der frei konfigurierbare DYNDNS Einstellungen bietet)
  • Ein CGI Programm auf dem gehosteten Rechner (da läuft doch eh ein Webserver :) )
  • Ein Cronjob der die Firewalleinstellungen vornimmt. Der Squeezeboxserver läuft permanent durch.

Getestete Versionen

Squeezebox-Server in Version 7.6.1, Debian-Squeeze auf dem Server, Squeezebox Hardware "Radio" und "Classic". Zugang 1&1 DSL mit Fritz!Box 7270v3, Firmware 74.05.05

Anpassungen auf der Fritz!Box

In der Fritz!Box wird der DynDNS Client eingerichtet. Falls man den schon anderweitig benötigt muß man sich ggf. auf dem gehosten Rechner was einfallen lassen. Ich brauche ihn nicht, deshalb habe ich mich da nicht weiter drum gekümmert.

Im Reiter "Dynamic-DNS" muß der "Benutzerdefinierte Anbieter" ausgewählt werden. Unter Update-URL trägt man sich eine URI ein, unter der man auf dem gehosten Rechner ein CGI-Script ablegen kann. Zum Beispiel http://www.siski.de/cgi-bin/update.pl.

[Screenshot Fritz!Box DynDNS Dialog]

Domainname ist einfach eine gültige Domain, unter Benutzername und Kennwort gibt man einfach einen Usernamen und Kennwort ein. Diese Daten braucht man später für die Konfiguration des Webservers, die Fritz!Box meldet sich per Basic-Authentisierung an, was für diesen Zweck hinreichend ist.

Anpassungen auf dem gehosteten Rechner, I - CGI Skript

Auf dem gehosten Rechner muß nun noch ein CGI Skript abgelegt werden, also unter der URI cgi-bin/update.pl.

Mittels .htaccess soll diese Datei geschützt werden.

# Zugriffssteuerung
AuthType Basic
AuthName "DynDNS"
AuthUserFile /home/carsten/.htusers
require user carsten

Hierbei kann der user carsten natürlich angepasst werden. Folgendermassen kann die Datei .htusers erzeugt werden, wichtig ist, das der Webserver .htusers lesen kann, ggf. kann man die Datei auch woanders hinlegen, aber nicht in einen Bereich der von außen lesbar ist

cd /home/carsten
htpasswd -c .htusers carsten

Nach diesen Maßnahmen kann das folgende Skript update.pl auf dem Webserver abgelegt werden.

#!/usr/bin/perl -w

# (C) 2011-09-19 Carsten Groß 

# Skript um das "dyn-dns" auto-update der Fritz!Box als dynamischen 
# Firewall-konfigurator zu nutzen. Hierdurch kann die Firewall einfach 
# so angepasst, daß nur die dynamische IP Adresse des DSL Zugangs 
# freigeschaltet ist
# 
# Die Fritz!Box schickt einfach einen leeren request mit Basic-authentisierung 
# Aus dem Environment kann man sich die request-ip und den usernamen
# herauslesen. Dieser wird gespeichert um die Firewall zu konfigurieren. 

use strict;
use CGI::Carp qw(fatalsToBrowser);

my $data_storage = "/var/run/squeezebox-firewall/ip-addr.conf"; 

open(FH, ">" .  $data_storage . "_tmp") || die "Cannot open configuration file for write: $!\n"; 

my $remote_addr = $ENV{'REMOTE_ADDR'}; 
my $remote_user = $ENV{'REMOTE_USER'}; 
my $local_addr = $ENV{'SERVER_ADDR'}; 

print FH "[config]\n";
print FH "remote-ip=" . $remote_addr . "\n";
print FH "remote-user=" . $remote_user . "\n";
print FH "local-ip=" . $local_addr . "\n"; 
close(FH); 

# Das brauchen wir, damit die Datei nie leer ist
rename $data_storage . "_tmp", $data_storage; 

print "Content-type: text/plain\n";
print "Content-Length: 0\n\n"; 
exit(0);

Nun muß noch das Verzeichnis /var/run/squeezebox-firewall/ angelegt werden, in welchem die Datei zur Parameterübergabe gespeichert wird. Durch diese Übergabe kann die eigentliche Firewallkonfiguriation von einem Job des root-Benutzers erledigt werden, während die Parameterübergabe über ein unpriviligiertes CGI Skript läuft (wwwrun heißt der Benutzer unter dem der Webbserver läuft unter Debian, bitte ggf. prüfen und anpassen).

mkdir -p /var/run/squeezebox-firewall/
chown wwwrun:root /var/run/squeezebox-firewall/

Nach dem Ablegen des Skriptes auf dem Rechner muß nun das ganze noch mit einem Webbrowser und der Fritz!Box getestet werden. Bei dem Aufruf mit dem Webbrowser (z.B. Firefox) wird man nach den Zugangsdaten der Basic-Authentisierung gefragt, falls nicht, muß man hier nachbessern. Nach dem Testaufruf (der nur eine leere Seite zurückliefern darf) muß in /var/run/squeezebox-firewall/ip-addr.conf die aktuelle IP-Adresse des DSL Zugangs abgespeichert sein (Die Datei ist grob im Format der Windows-ini-Dateien).

Anpassungen auf dem gehosteten Rechner, II - Firewall + Cronjob

Nun kann man sich an den zweiten Teil machen, nämlich die Firewall Konfiguration. In einem globalen Bootup-Skript muß man sich folgende Regeln einbauen und diese aktivieren. Insbesondere braucht man die iptables Tabelle Squeezebox_Allow.

#! /bin/sh

MYIP=IP_ADDRESSE_DES_ROOT_SERVERS

iptables -F Squeezebox_Allow
iptables -X Squeezebox_Allow

iptables -N Squeezebox_Allow

iptables -A INPUT -p tcp --dport 9000 -j Squeezebox_Allow
iptables -A INPUT -p tcp --dport 9090 -j Squeezebox_Allow
iptables -A INPUT -p tcp --dport 3483 -j Squeezebox_Allow
iptables -A INPUT -p udp --dport 3483 -j Squeezebox_Allow
# Default ist: Alles zu!
iptables -A Squeezebox_Allow -p tcp -d ${MYIP} --dport 3483 -j REJECT --reject-with tcp-reset
iptables -A Squeezebox_Allow -p tcp -d ${MYIP} --dport 9090 -j REJECT --reject-with tcp-reset
iptables -A Squeezebox_Allow -p tcp -d ${MYIP} --dport 9000 -j REJECT --reject-with tcp-reset
iptables -A Squeezebox_Allow -p udp -d ${MYIP} --dport 3483 -j REJECT 

Damit die Firewall auf dem Server dynamisch angepasst wird jetzt noch ein cronjob für den root-Benutzer installiert.

#! /usr/bin/perl -w 

# crontab-teil der die IP Adressen richtig einstellt. 
# aber jeweils die Zeitstempel prüft damit bei einer Änderung
# die Firewall umkonfiguriert wird
use strict; 

my $data_storage = "/var/run/squeezebox-firewall/ip-addr.conf";
my $timestamp    = "/var/run/squeezebox-firewall/last-run"; 

###############################################################
# Subroutinen
###############################################################
sub ReadConfig() {
    # Search the configuration file for the "per user" base in the homedir,
    # afterwards search it at the global position 
	if (! open(FH, "<", $data_storage)) {
		print STDERR "Error reading configuration file $data_storage: $!\n";
		exit(1);
	}
	my @FILEDATA = ;
	chomp @FILEDATA;
	close(FH);
	my $srv;
	my %CONFIG;
	foreach my $read (@FILEDATA) {
		if ( $read =~ m/^#/ ) {
			next; # ignore comment lines
		}
		if ( $read =~ m/\[/ ) {
			$read =~ s/[\[\]\s]//g;
			$srv = $read;
		} elsif ($read =~ m/\=/) {
			$read =~ m/^\s*([\w\-.]+)\s*=\s*(.*)\s*$/; 
			my $key = $1; 
			my $value = $2; 
			$CONFIG{$srv}{$key} = $value;
		} else {
			# keine [], kein = Zeichen, ignorieren
			next;
		}
	}
	return \%CONFIG;
}

sub FlushRules() {
	system("iptables -F Squeezebox_Allow");
	return;
}


sub ForbidAll($) {
	# Alles andere verbieten
	my $ip = shift; 
	system("iptables -A Squeezebox_Allow -p tcp -d " . $ip . " --dport 3483 -j REJECT --reject-with tcp-reset");
	system("iptables -A Squeezebox_Allow -p tcp -d " . $ip . " --dport 9090 -j REJECT --reject-with tcp-reset"); 
	system("iptables -A Squeezebox_Allow -p tcp -d " . $ip . " --dport 9000 -j REJECT --reject-with tcp-reset");
	system("iptables -A Squeezebox_Allow -p udp -d " . $ip . " --dport 3483 -j REJECT");
	return; 
}

# Flag das sich merkt ob wir laufen müssen... 
my $need_run = 0;

# Okay.. lese die Zeitstempel der beiden Dateien ein.

my @TSD = stat $data_storage; 
if (! @TSD) {
	print STDERR "Cannot stat configuration file $data_storage: $!\n";
	exit(1); 
}
my $mtime_data_storage = $TSD[9];

my @TST = stat $timestamp; 

# Last Timestamp fehlt, wir müssen laufen
my $mtime_timestamp = $TST[9]; 
if (! defined $mtime_timestamp) {
	$need_run = 1; 
}

my $current_time = time();

#
if (defined $mtime_timestamp && $current_time - $mtime_data_storage > 87000) {
	print STDERR "Last touch of $data_storage was on " .localtime($mtime_data_storage) . ". This is too old. Firewalling all ports.\n"; 
	$need_run = 2; 

}

if (defined $mtime_data_storage && defined $mtime_timestamp && ($mtime_data_storage >= $mtime_timestamp)) {
	$need_run = 1; 
}

firewall: 
if ($need_run > 0) {
	# Read configuration file $data_storage 
	my $conf = ReadConfig(); 
	my $ip = $$conf{'config'}{'remote-ip'}; 
	my $user = $$conf{'config'}{'remote-user'}; 
	my $my_ip = $$conf{'config'}{'local-ip'}; 
	if (! defined $ip || $ip !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}.\d{1,3}$/ ) {
		print STDERR "Sorry, $data_storage contains invalid remote-ip address $ip.\n";
		exit(1);
	}
	if (! defined $my_ip || $my_ip !~ /^\d{1,3}\.\d{1,3}\.\d{1,3}.\d{1,3}$/ ) {
		print STDERR "Sorry, $data_storage contains invalid local-ip address $my_ip.\n";
		exit(1);
	}
	FlushRules();
	if ($need_run == 1) {
		print STDERR "Setting up firewall rules for Squeezebox clients in NATed Network [$ip]\n";
		#  Client von ALLOWEDREMOTEIP erlauben
		system("iptables -A Squeezebox_Allow -p tcp -d $my_ip -s $ip --dport 9000 -j ACCEPT");
		system("iptables -A Squeezebox_Allow -p tcp -d $my_ip -s $ip --dport 9090 -j ACCEPT");
		system("iptables -A Squeezebox_Allow -p tcp -d $my_ip -s $ip --dport 3483 -j ACCEPT"); 
		system("iptables -A Squeezebox_Allow -p udp -d $my_ip -s $ip --dport 3483 -j ACCEPT"); 
	}
	ForbidAll($my_ip);
	open(FH, ">", $timestamp) || die "Sorry, cannot write timestamp-file $timestamp: $!\n"; 
	close(FH); 
}
exit(0); 

Unter diesem Link kann man sich das Skript auch einfach herunterladen und im Homeverzeichniss von root in bin/squeezebox-firewall.perl speichern.

Mit dem Kommando crontab -e als root wird folgender cronjob installiert

MAILTO=gueltige@mailadresse

# m h  dom mon dow   command
0-59/1 * * * * /root/bin/squeezebox-firewall.pl

Damit läuft der cronjob jede Minute. Falls sich die IP Adresse des Einwahlrechners ändert wird innerhalb einer Minute die Firewall angepasst. Die regulären Ausrücke und der Zugangsschutz sorgen für eine gewisse Basissicherheit.

© Carsten Groß - Letzte Änderung 24.06.2012 18:24 - Impressum