<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>No nix narrets!</title>
	<atom:link href="http://neunzehn83.de/blog/feed/" rel="self" type="application/rss+xml" />
	<link>http://neunzehn83.de/blog</link>
	<description>Ein Mann, ein Blog, kein Plan!</description>
	<lastBuildDate>Sun, 29 Jan 2012 21:19:29 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>Postfix: reject_sender_login_mismatch pro SASL username</title>
		<link>http://neunzehn83.de/blog/2012/01/29/postfix-reject_sender_login_mismatch-pro-sasl-username/</link>
		<comments>http://neunzehn83.de/blog/2012/01/29/postfix-reject_sender_login_mismatch-pro-sasl-username/#comments</comments>
		<pubDate>Sun, 29 Jan 2012 20:25:31 +0000</pubDate>
		<dc:creator>Nils</dc:creator>
				<category><![CDATA[Linux]]></category>

		<guid isPermaLink="false">http://neunzehn83.de/blog/?p=1059</guid>
		<description><![CDATA[Die Postfix-Konfiguration kann einem einige graue Haare verursachen, so wie bei dieser Problemstellung hier. Ich habe manchmal den Eindruck, dass Postfix gar nicht dazu entwickelt worden ist, E-Mails zu senden und zu empfangen, aber dennoch so flexibel ist, dass es sich irgendwie zu einem smtpd konfigurieren lässt.. Prinzipiell erlaubt Postfix mit permit_sasl_authenticated jedem SASL-authentifizierten Benutzer [...]]]></description>
			<content:encoded><![CDATA[<p><em>Die Postfix-Konfiguration kann einem einige graue Haare verursachen, so wie bei dieser Problemstellung hier. Ich habe manchmal den Eindruck, dass Postfix gar nicht dazu entwickelt worden ist, E-Mails zu senden und zu empfangen, aber dennoch so flexibel ist, dass es sich irgendwie zu einem smtpd konfigurieren lässt..</em></p>

<p>Prinzipiell erlaubt Postfix mit <code>permit_sasl_authenticated</code> <em>jedem</em> SASL-authentifizierten Benutzer beliebige <strong>FROM</strong> E-Mail-Adressen zu verwenden. Mit der Option <code>reject_sender_login_mismatch</code> wird dieses Verhalten verboten und überprüft ob der SASL-Username bzw. die damit verknüpften Mail-Adressen und -Aliase mit dem <strong>FROM</strong> der E-Mail übereinstimmt.</p>

<p>Wenn man die Option reject_sender_login_mismatch nun nur auf bestimmte SASL-Usernamen beschränken will, wird es kompliziert. Das lässt sich nämlich nicht so einfach in der Postfix-Konfiguration (main.cf) einstellen, da sämtliche Lookup-Tables nicht den SASL-Username als Index benutzen.</p>

<h2>Was wir wollen</h2>

<p>Der User <strong>foo@example.org</strong> soll ausschließlich E-Mails mit foo@example.org als Absender senden können.<br />
Der User <strong>bar@example.org</strong> soll hingegen beliebige Absenderadressen wählen können.</p>

<p>Das geht nur über einen <a href="http://www.postfix.org/SMTPD_POLICY_README.html">Policy Daemon</a>: Hört sich kompliziert an, ist aber im einfachsten Fall ein simples Script, dem von Postfix unter anderem der SASL-Username übergeben wird.</p>

<h1>smtpd Policy Daemon mit PHP</h1>

<p>Das funktioniert mit anderen Scriptsprachen wie Python oder Perl natürlich ähnlich, und wahrscheinlich sogar besser, wir machen das aber mal, weil wir es können, mit PHP!</p>

<p>Unser PHP Policy Daemon soll unter <code>/usr/local/bin/policyd</code> liegen (chmod +x nicht vergessen, chroot beachten). Die Parameter von Postfix kommen über STDIN. Das Ergebnis geben wir ganz normal über <code>echo</code> an Postfix zurück. Als Ergebnis kommt ACCEPT oder REJECT in Frage, oder aber jede bekannte Postfix UCE restriction, also auch reject_sender_login_mismatch! (Siehe dazu auch <a href="http://www.postfix.org/access.5.html">access(5)</a>)</p>

<p><strong>/usr/local/bin/policyd</strong>
<pre>
&#35;!/usr/bin/php
&lt;?php
    $allow_relay = array(
        'bar@example.org',
    );
       
    $fp = fopen('php://stdin', 'r');
    while (true) {
        $line = fgets($fp, 512);
        if (strpos($line, '=')) {
            list($k, $v) = explode('=', trim($line));
            $env[$k] = $v;
        }<br />
        if($line == "\n") break;
    }
    fclose($fp);
 
    if ($env['sasl_username']) {
        if (!in_array($env['sasl_username'], $allow_relay)) {
            echo "action=reject_sender_login_mismatch\n\n";
            die();
        }
    }
    echo "action=DUNNO\n\n";
</pre></p>

<p>Simple String-Funktionen: Ist der sasl_username <strong>nicht</strong> in unserem Array mit erlaubten Relay-Logins, wird die reject_sender_login_mismatch UCE restriction zurück gegeben. Das FROM muss nun also mit sasl_username übereinstimmen. Im anderen Fall wird ein &#8220;DUNNO&#8221; zurückgegeben, was Postfix dazu veranlasst mit der nächsten Regel weiter zu machen. reject_sender_login_mismatch gilt dann für diesen sasl_username also nicht.</p>

<p>Wichtig ist hier, den STDIN mit <code>fgets</code> zeilenweise zu lesen und aufzuhören sobald eine leere Zeile empfangen wird. Versucht man den gesamten STDIN über file_get_contents zu holen, beträgt der Timeout 60 Sekunden bis das Script fortgeführt wird und Postfix das Ergebnis übergeben werden kann.</p>

<p>Neben dem sasl_username kommen über STDIN noch weitere Parameter, wie z.B. die Absender-Adresse, Empfänger-Adresse oder die Client-IP. Eine vollständige Übersicht gibt es hier: <a href="http://www.postfix.org/SMTPD_POLICY_README.html#protocol">http://www.postfix.org/SMTPD_POLICY_README.html#protocol</a></p>

<h2>Postfix Konfiguration</h2>

<p><strong>master.cf</strong></p>

<pre><code>policy  unix    -       n       n       -       0       spawn
    user=nobody argv=/usr/local/bin/policyd
</code></pre>

<p><strong>main.cf</strong></p>

<p><pre>
smtpd_sender_restrictions = permit_mynetworks,
    <strong>check_policy_service unix:private/policy,</strong>
    permit_sasl_authenticated,
    [..]<br />
</pre></p>

<pre><code>~$ /etc/init.d/postfix restart
</code></pre>

<p>Das wars!</p>
]]></content:encoded>
			<wfw:commentRss>http://neunzehn83.de/blog/2012/01/29/postfix-reject_sender_login_mismatch-pro-sasl-username/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>neunzehn83.de jetzt auch über IPv6 erreichbar</title>
		<link>http://neunzehn83.de/blog/2012/01/12/neunzehn83-de-jetzt-auch-uber-ipv6-erreichbar/</link>
		<comments>http://neunzehn83.de/blog/2012/01/12/neunzehn83-de-jetzt-auch-uber-ipv6-erreichbar/#comments</comments>
		<pubDate>Thu, 12 Jan 2012 22:05:07 +0000</pubDate>
		<dc:creator>Nils</dc:creator>
				<category><![CDATA[Allgemein]]></category>

		<guid isPermaLink="false">http://neunzehn83.de/blog/?p=979</guid>
		<description><![CDATA[Dank IPv6 zu Hause über den Tunnelbroker Sixxs konnte ich nun endlich meinen vServer fit für &#8220;das neue Internet&#8221; machen. Natives IPv6 ist wohl in absehbarer Zeit nicht zu erwarten, da von meinem ISP, der Kabel Baden-Württemberg GmbH, noch kein Termin zur Einführung von IPv6 genannt wurde. Eigentlich ganz einfach Alles was wir brauchen ist: [...]]]></description>
			<content:encoded><![CDATA[<p>Dank IPv6 zu Hause über den Tunnelbroker <a href="//www.sixxs.net">Sixxs</a> konnte ich nun endlich meinen vServer fit für &#8220;das neue Internet&#8221; machen. Natives IPv6 ist wohl in absehbarer Zeit nicht zu erwarten, da von meinem ISP, der Kabel Baden-Württemberg GmbH, noch kein Termin zur Einführung von IPv6 genannt wurde.</p>

<p><img src="/blog/wp-content/uploads/2012/01/v6ping.gif" alt="" title="v6ping" width="612" height="223" class="alignnone size-full wp-image-989" /></p>

<h2>Eigentlich ganz einfach</h2>

<p>Alles was wir brauchen ist:</p>

<ul>
<li>eine IPv6-Adresse</li>
<li>einen Nameserver, der per IPv6 erreichbar ist</li>
<li>DNS AAAA-Records</li>
<li>Apache für den Dualstack-Betrieb konfigurieren</li>
</ul>

<h3>Eine IPv6-Adresse</h3>

<p>Der <a href="//www.netcup.de/bestellen/produkt.php?produkt=88">vServer ALUMINIUM</a> von <a href="//www.netcup.de">Netcup</a>, auf dem neunzehn83.de läuft, ist IPv6 fähig. Per OpenVCP-Panel kann man bis zu 15 einzelne IPv6-Adressen aktivieren. Nach einem Reboot stehen diese zur Verfügung.</p>

<pre><code>~$ sudo ifconfig
eth0      Link encap:Ethernet  HWaddr 00:24:21:b4:xy:ab
      inet addr:188.40.201.74  Bcast:188.40.201.127  Mask:255.255.255.192
      inet6 addr: 2a01:4f8:100:5462:0:bc28:c94a:1/64 Scope:Global
      inet6 addr: 2a01:4f8:100:5462:0:bc28:c94a:2/64 Scope:Global
</code></pre>

<h3>Nameserver</h3>

<p>Für IPv4 schreibt die DeNIC mindestens zwei Nameserver in unterschiedlichen /24 Netzen vor. Bei IPv6 genügt im Moment noch ein einziger. <a href="/blog/2009/12/11/die-technik-hinter-diesem-blog-hosting-fur-schwaben/">Wie bereits erwähnt</a>, verwende ich die Nameserver von <a href="//www.regworld.com">Regworld</a>, meinem Domain-Registrar. Zusätzlich läuft auf dem vServer noch ein Bind Nameserver als zweiter Secondary. Dieser lauscht auch gleichzeitig an der IPv6-Adresse und wird somit als einziger Nameserver für IPv6-Anfragen genutzt.</p>

<p>Die Nameserver von Regworld kommen dafür leider nicht in Frage, da diese (noch) nicht per IPv6 erreichbar sind. Das lässt sich aber relativ leicht lösen, indem man einen Bind-Nameserver an eine oder mehrere IPv6 adressen binded und als reinen Forwarder für die Regworld-Nameserver via IPv4 konfiguriert.</p>

<p>In meinem Fall gebe ich mich aber mal mangels RAM mit nur einem Nameserver für IPv6 zufrieden.</p>

<p>Zu guter Letzt muss noch ein IPv6 GLUE-Record für den Nameserver angelegt werden. Das geht im Domainrobot von Regworld (AutoDNS) genau so wie bei IPv4: Man schreibt den IPv6-Glue einfach per Leerzeichen vom v4-Glue getrennt dahinter:</p>

<pre><code>ns.example.org 1.1.1.1 ::1
</code></pre>

<p>Beim DeNIC-Whois sieht das dann so aus:</p>

<p><img src="/blog/wp-content/uploads/2012/01/ipv6_nameserver_denic.gif" alt="" title="ipv6_nameserver_denic" width="480" height="155" class="alignnone size-full wp-image-985" /></p>

<h3>DNS AAAA-Records</h3>

<p>Was der A-Record bei v4 ist der AAAA (Quad-A) Record bei v6. Diesen legen wir jetzt also für die Domain (neunzehn83.de) und die www-Subdomain an. Zusätzlich habe ich eine neue Subdomain <a href="//ipv6.neunzehn83.de">ipv6.neunzehn83.de</a> erstellt, die nur ein AAAA aber kein A Record hat, somit also exklusiv über IPv6 erreichbar ist.</p>

<pre><code>@    IN A    188.40.201.74    
@    IN AAAA 2a01:4f8:100:5462::bc28:c94a:1
www  IN A    188.40.201.74
www  IN AAAA 2a01:4f8:100:5462::bc28:c94a:1
ipv6 IN AAAA 2a01:4f8:100:5462::bc28:c94a:1
</code></pre>

<p>IPv6 hat übrigens Priorität vor IPv4, weshalb alle Besucher die sowohl mit IPv4 als auch mit IPv6 unterwegs sind, diese Webseite bereits über IPv6 betrachten.</p>

<h3>Apache Dualstack</h3>

<p>Dual-Stack nennt man das Verfahren, bei dem der Apache sowohl per IPv4 also auch per IPv6 erreichbar ist. Alles was dafür nötig ist, ist den Apachen auch auf der IPv6-Adresse lauschen zu lassen und die Virtualhost-Konfiguration um die IPv6-Adresse zu erweitern.</p>

<pre><code>Listen 188.40.201.74:80
Listen [2a01:4f8:100:5462::bc28:c94a:1]:80

&lt;Virtualhost 188.40.201.74:80 [2a01:4f8:100:5462::bc28:c94a:1]:80&gt;
[...]
&lt;/Virtualhost&gt;
</code></pre>

<p>Nach einem Apache-Restart ist alles klar.</p>

<h2>IPv6 und Froxlor</h2>

<p>Dummerweise schreibe ich für diesen Server die Apache-Configs nicht selbst, sondern lass das <a href="//www.froxlor.org/">Froxlor</a> machen. Froxlor ist der Nachfolger von SysCP, unterstützt aber ebenso noch kein Dualstack, weshalb ich selbst an der Source Hand angelegt habe. Alle Änderungen finden sich in <a href="/blog/wp-content/uploads/2012/01/IPv6.patch">diesem Patch</a>, welchen ich auch in einem <a href="//forum.froxlor.org/index.php?/topic/1415-ipv6-dualstack-apache-bind/">Thread bei Froxlor</a> zur Diskussion veröffentlicht habe. In der Datenbank muss die Varchar-Länge des <code>ip</code>-Feldes der <code>panel_ipsandports</code>-Tabelle auf 55 vergrößert werden.</p>

<pre><code>ALTER TABLE `panel_ipsandports` CHANGE `ip` `ip` VARCHAR( 55 ) NOT NULL DEFAULT ''
</code></pre>

<p>Nun können unter <code>IPs und Ports</code> Dualstack-IPs angeben werden, indem die IPv4 einfach durch ein Leerzeichen von der IPv6-Adresse getrennt wird:</p>

<p><img src="http://neunzehn83.de/blog/wp-content/uploads/2012/01/froxlor_ipv6.gif" alt="" title="froxlor_ipv6" width="575" height="183" class="alignnone size-full wp-image-983" /></p>

<p>Achtung: der Patch berücksichtigt nur die Apache und Bind Konfiguration. NGIX und Lighttpd sind unverändert und somit wahrscheinlich nicht funktionsfähig.</p>

<h2>IPv6 und PHP</h2>

<p>Wenn die eigene Webseite auch per IPv6 erreichbar ist, kann das auch Auswirkungen auf PHP-Scripte haben. <code>$_SERVER['REMOTE_ADDR']</code> beinhaltet dann nämlich die IPv6-Adresse als String und die ist in den meisten Fällen deutlich länger als eine IPv4-Adresse. Das kann ein Problem werden, wenn die IP-Adresse in einer Datenbank als ein <code>VARCHAR(15)</code> gespeichert wird. Hier benötigt man nun ein <code>VARCHAR(40)</code>. Auch <a href="//php.net/manual/de/function.ip2long.php">ip2long()</a> funktioniert nicht mit IPv6-Adressen.</p>
]]></content:encoded>
			<wfw:commentRss>http://neunzehn83.de/blog/2012/01/12/neunzehn83-de-jetzt-auch-uber-ipv6-erreichbar/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Froxlor Bug: maximale Länge von ServerAlias (Apache)</title>
		<link>http://neunzehn83.de/blog/2012/01/08/froxlor-bug-maximale-lange-von-serveralias-apache/</link>
		<comments>http://neunzehn83.de/blog/2012/01/08/froxlor-bug-maximale-lange-von-serveralias-apache/#comments</comments>
		<pubDate>Sun, 08 Jan 2012 21:39:56 +0000</pubDate>
		<dc:creator>Nils</dc:creator>
				<category><![CDATA[Webtechnik]]></category>

		<guid isPermaLink="false">http://neunzehn83.de/blog/?p=1002</guid>
		<description><![CDATA[SysCP bzw. dessen Nachfolger Froxlor kümmert sich hier auf diesem Server um das Erstellen diverser Konfigurationsdateien sowie um die Verwaltung der virtuellen User für E-Mail- und FTP-Zugänge. Schon vor einiger Zeit habe ich festgestellt, dass bei Verwendung vieler Alias-Domains die erzeugte Apache-Konfiguration fehlerhaft sein kann. Dann nämlich, wenn die ServerAlias-Direktive mehr als 8000 Zeichen hat. [...]]]></description>
			<content:encoded><![CDATA[<p>SysCP bzw. dessen Nachfolger <a href="http://www.froxlor.org/">Froxlor</a> kümmert sich hier auf diesem Server um das Erstellen diverser Konfigurationsdateien sowie um die Verwaltung der virtuellen User für E-Mail- und FTP-Zugänge.</p>

<p>Schon vor einiger Zeit habe ich festgestellt, dass bei Verwendung vieler Alias-Domains die erzeugte Apache-Konfiguration fehlerhaft sein kann. Dann nämlich, wenn die ServerAlias-Direktive mehr als 8000 Zeichen hat. Scheinbar hat noch keiner zuvor so viele Alias-Domains einer Hauptdomain hinzugefügt. 8000 Zeichen hört sich auch viel an, doch wenn pro Domain jeweils noch die www-Subdomain hinzukommt, reichen bereits ca. 150 Domains um dieses Limit zu erreichen.</p>

<p>Der Apache vHost-Container unterstützt mehrere ServerAlias-Direktiven, so dass bevor die 8000 Zeichen erreicht werden einfach ein neuer ServerAlias-Eintrag erzeugt werden sollte. Die Datei <code>cron_tasks.inc.http.10.apache.php</code> in <code>scripts/jobs</code> ist für das Erstellen der Apache-Konfigurationsdateien verantwortlich und mit wenigen Zeilen auf dieses Verhalten anpassbar (<a href="http://neunzehn83.de/blog/wp-content/uploads/2012/01/ServerAlias.patch">Patch</a>).</p>

<p>Da ich nicht nach jedem Update daran denken möchte, diese Änderung einzuspielen, habe ich bei Froxlor mal ein <a href="http://redmine.froxlor.org/issues/1012">Ticket erstellt</a> und diesen Patch angehängt. Vielleicht schafft er es ja in die 0.9.27 :)</p>
]]></content:encoded>
			<wfw:commentRss>http://neunzehn83.de/blog/2012/01/08/froxlor-bug-maximale-lange-von-serveralias-apache/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP und Y2K38: Werden wir alle sterben?</title>
		<link>http://neunzehn83.de/blog/2011/12/22/php-und-y2k38-werden-wir-alle-sterben/</link>
		<comments>http://neunzehn83.de/blog/2011/12/22/php-und-y2k38-werden-wir-alle-sterben/#comments</comments>
		<pubDate>Thu, 22 Dec 2011 07:58:43 +0000</pubDate>
		<dc:creator>Nils</dc:creator>
				<category><![CDATA[Webtechnik]]></category>

		<guid isPermaLink="false">http://neunzehn83.de/blog/?p=527</guid>
		<description><![CDATA[Der 19. Januar 2038 3:14:07 Uhr ist ein besonderer Zeitpunkt. Dann sind nämlich genau 2147483647 Sekunden seit dem 1. Januar 1970 vergangen. PHPs date()-Funktion arbeitet genau auf dieser Grundlage &#8211; auch bekannt als der UNIX-Timestamp. Der Unix-Timestamp ist in PHP sehr populär. Funktionen wie date() oder strtotime() arbeiten mit UNIX Timestamps. Das ist auf den [...]]]></description>
			<content:encoded><![CDATA[<p>Der 19. Januar 2038 3:14:07 Uhr ist ein besonderer Zeitpunkt. Dann sind nämlich genau 2147483647 Sekunden seit dem 1. Januar 1970 vergangen. PHPs <code>date()</code>-Funktion arbeitet genau auf dieser Grundlage &#8211; auch bekannt als der UNIX-Timestamp.</p>

<p>Der Unix-Timestamp ist in PHP sehr populär. Funktionen wie <code>date()</code> oder <code>strtotime()</code> arbeiten mit UNIX Timestamps. Das ist auf den ersten Blick auch unheimlich praktisch, weil sich damit relativ platzsparend Daten (<em>Plural von Datum</em>) speichern lassen. Außerdem lässt es sich mit einem Timestamp leicht rechnen.</p>

<p>Ein PHP signed Integer auf 32bit-Systemen kann Werte zwischen -2147483648 und 2147483647 annehmen. Am 19. Januar 2038 3:14:08 läuft der Int also über und es ist plötzlich der 13. Dezember 1901 20:45:52 Uhr (Freitag der 13.!).</p>

<p>Folgendes Beispiel zeigt das Verhalten auf 32bit-Systemen. Mit einem 64bit-System und 64bit PHP kann PHPs unsinged Int Werte bis 9223372036854775807 annehmen. Dort stellt sich die Y2K38-Frage also garnicht. Hier stellt sich dann die <strong>Jahr 292471210689-Frage</strong> &#8211; aber bis dahin sind 64bit Systeme genau so ausgestorben, wie es 2038 die 32bit-Systeme sein werden.</p>

<pre><code>var_export(strtotime("20 jan 2038")); // false :(
</code></pre>
]]></content:encoded>
			<wfw:commentRss>http://neunzehn83.de/blog/2011/12/22/php-und-y2k38-werden-wir-alle-sterben/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP CLI Fortschrittsbalken</title>
		<link>http://neunzehn83.de/blog/2011/12/18/php-cli-fortschrittsbalken/</link>
		<comments>http://neunzehn83.de/blog/2011/12/18/php-cli-fortschrittsbalken/#comments</comments>
		<pubDate>Sun, 18 Dec 2011 07:22:49 +0000</pubDate>
		<dc:creator>Nils</dc:creator>
				<category><![CDATA[Webtechnik]]></category>

		<guid isPermaLink="false">http://neunzehn83.de/blog/?p=928</guid>
		<description><![CDATA[Hin und wieder führt man PHP-Skripte direkt auf der Kommandozeile (CLI) aus. Das hat den Vorteil, dass man nicht den Umweg über den Webserver gehen muss, wenn man ihn garnicht braucht. Außerdem lässt sich das Skript leicht mit STRG+C abbrechen, den Webserver hingegen müsste man bei einer Endlosschleife neu starten. Der aktuelle Fortschritt lässt sich [...]]]></description>
			<content:encoded><![CDATA[<p>Hin und wieder führt man PHP-Skripte direkt auf der Kommandozeile (CLI) aus. Das hat den Vorteil, dass man nicht den Umweg über den Webserver gehen muss, wenn man ihn garnicht braucht. Außerdem lässt sich das Skript leicht mit STRG+C abbrechen, den Webserver hingegen müsste man bei einer Endlosschleife neu starten.</p>

<p>Der aktuelle Fortschritt lässt sich auch ganz einfach mit echo ausgeben &#8211; kein (ob_)flush notwendig.</p>

<p>Wenn man mit echo einen Carriage Return (<code>"\r"</code>) ausgibt, wird der Cursor auf den Anfang der Zeile zurückgestellt und man kann eine bereits ausgegebene Zeile überschreiben. Perfekt also für einen Fortschrittsbalken!</p>

<pre><code>$total = 10;
$bar_length = 20;
$spinner = '-\\|/';
for ($i = 1; $i &lt;= $total; $i++) {
    usleep(1000000);

    $spin = $spinner[$i%strlen($spinner)];    
    $cur = sprintf('%'.strlen($total).'.d', $i);
    $percent = $i/$total*100;

    $progress_len = floor($bar_length * $percent / 100);
    $progress = str_repeat('=', $progress_len);
    if ($progress_len &lt; $bar_length) {
        $progress .= '&gt;';
        $progress .= str_repeat('-', $bar_length-$progress_len-1);
    }

    echo " $cur/$total $spin [$progress] $percent%\r";
}
</code></pre>

<p><a href="http://neunzehn83.de/blog/wp-content/uploads/2011/12/php-cli-progress.gif"><img src="http://neunzehn83.de/blog/wp-content/uploads/2011/12/php-cli-progress.gif" alt="" title="php-cli-progress" width="301" height="69" class="alignnone size-full wp-image-960" /></a></p>
]]></content:encoded>
			<wfw:commentRss>http://neunzehn83.de/blog/2011/12/18/php-cli-fortschrittsbalken/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Zwei Jahre (und ein bisschen) neunzehn83.de</title>
		<link>http://neunzehn83.de/blog/2011/12/15/zwei-jahre-und-ein-bisschen-neunzehn83-de/</link>
		<comments>http://neunzehn83.de/blog/2011/12/15/zwei-jahre-und-ein-bisschen-neunzehn83-de/#comments</comments>
		<pubDate>Thu, 15 Dec 2011 22:00:35 +0000</pubDate>
		<dc:creator>Nils</dc:creator>
				<category><![CDATA[Allgemein]]></category>

		<guid isPermaLink="false">http://neunzehn83.de/blog/?p=941</guid>
		<description><![CDATA[Wie auch schon letztes Jahr habe ich den Geburtstag (oder sagt man Jahrestag) meines Blogs knapp verpennt. Macht aber nichts, zu bloggen hab ich nämlich auch vergessen. 2011 waren die Anspüche groß, die Resultate blieben aber wie so oft weit hinter den Erwartungen zurück. So habe ich es in einem Jahr auf gerade einmal 16 [...]]]></description>
			<content:encoded><![CDATA[<p><img src="http://neunzehn83.de/blog/wp-content/uploads/2011/12/6XUxx.jpg" alt="Party Hard" /></p>

<p>Wie auch schon <a href="http://neunzehn83.de/blog/2010/12/07/ein-jahr-neunzehn83-de/">letztes Jahr</a> habe ich den Geburtstag (oder sagt man Jahrestag) meines Blogs knapp verpennt. Macht aber nichts, zu bloggen hab ich nämlich auch vergessen. 2011 waren die Anspüche groß, die Resultate blieben aber wie so oft weit hinter den Erwartungen zurück. So habe ich es in einem Jahr auf gerade einmal 16 Beiträge gebracht. Pfui!</p>

<p>2012 wird aber alles besser. Gebloggt wird ab sofort übrigens mit WordPress 3 und markdown (yay). Ideen für neue Beiträge habe ich einige, mitunter sind das echte Kracher, also dranbleiben, denn die kalte Jahreszeit kommt bestimmt.</p>
]]></content:encoded>
			<wfw:commentRss>http://neunzehn83.de/blog/2011/12/15/zwei-jahre-und-ein-bisschen-neunzehn83-de/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>SVG nach PNG umwandeln mit Textpath [Debian Linux]</title>
		<link>http://neunzehn83.de/blog/2011/08/29/svg-nach-png-umwandeln-mit-textpath-debian-linux/</link>
		<comments>http://neunzehn83.de/blog/2011/08/29/svg-nach-png-umwandeln-mit-textpath-debian-linux/#comments</comments>
		<pubDate>Mon, 29 Aug 2011 20:06:54 +0000</pubDate>
		<dc:creator>Nils</dc:creator>
				<category><![CDATA[Allgemein]]></category>

		<guid isPermaLink="false">http://neunzehn83.de/blog/?p=862</guid>
		<description><![CDATA[Kurz erwähnt, da es mich selbst zu viel Zeit gekostet hat dieses eigentlich einfache Problem zu lösen: Möchte man ein SVG in PNG (oder sonstige Pixelgrafikformate) umwandeln, versucht man das wohl zunächst mit ImageMagick. Das funktioniert solange auch prima, bis im SVG ein &#60;textpath&#62; vorkommt. Dieser verschwindet nämlich bei der Umwandlung in ein PNG mit [...]]]></description>
			<content:encoded><![CDATA[<p>Kurz erwähnt, da es mich selbst zu viel Zeit gekostet hat dieses eigentlich einfache Problem zu lösen:</p>

<p>Möchte man ein SVG in PNG (oder sonstige Pixelgrafikformate) umwandeln, versucht man das wohl zunächst mit <a href="http://www.imagemagick.org/script/index.php"><strong>ImageMagick</strong></a>. Das funktioniert solange auch prima, bis im SVG ein &lt;textpath&gt; vorkommt. Dieser verschwindet nämlich bei der Umwandlung in ein PNG mit imagick. Selbes Problem tritt mit meiner zweiten Wahl, dem Tool <strong>rsvg</strong> (librsvg2-bin), auf.</p>

<p>Mit <strong><a href="http://inkscape.org/?lang=de">inkscape</a></strong> funktioniert die Umwandlung wie gewünscht. Es ist mit Sicherheit weniger verbreitet wie convert (imagick), aber immerhin ist es als Debian-Paket (&#8220;inkscape&#8221;) verfügbar. Die Windowsversion ist im Übrigen ebenfalls einen Blick wert: ein wirklich brauchbares, quelloffenes Vektorgrafikprogramm!</p>
]]></content:encoded>
			<wfw:commentRss>http://neunzehn83.de/blog/2011/08/29/svg-nach-png-umwandeln-mit-textpath-debian-linux/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP-Security! Heute: path traversal</title>
		<link>http://neunzehn83.de/blog/2011/06/26/php-security-heute-path-traversal/</link>
		<comments>http://neunzehn83.de/blog/2011/06/26/php-security-heute-path-traversal/#comments</comments>
		<pubDate>Sun, 26 Jun 2011 15:21:13 +0000</pubDate>
		<dc:creator>Nils</dc:creator>
				<category><![CDATA[Webtechnik]]></category>

		<guid isPermaLink="false">http://neunzehn83.de/blog/?p=884</guid>
		<description><![CDATA[Path traversal bzw. directory traversal ist eine Methode, um aus vorgesehenen Verzeichnissen auszubrechen. In Bezug auf PHP findet diese Sicherheitslücke  Anwendung, da Dateien oftmals anhand des Querystrings eingebunden werden. Beispiel: http://example.org/index.php?site=impressum.php &#60;?php include './includes/sites/' . $_GET['site']; ?&#62; Das ist natürlich fatal. Mittels ../ in $_GET['site'] kann man das vorgegebene Verzeichnis verlassen und je nach Rechten [...]]]></description>
			<content:encoded><![CDATA[<p><img class="alignnone size-full wp-image-888" title="php-security" src="http://neunzehn83.de/blog/wp-content/uploads/2011/06/php-security.jpg" alt="" width="600" height="200" /></p>

<p>Path traversal bzw. directory traversal ist eine Methode, um aus vorgesehenen Verzeichnissen auszubrechen. In Bezug auf PHP findet diese Sicherheitslücke  Anwendung, da Dateien oftmals anhand des Querystrings eingebunden werden. Beispiel:
<pre>http://example.org/index.php?site=impressum.php</pre>
<pre>&lt;?php
    include './includes/sites/' . $_GET['site'];
?&gt;</pre>
Das ist natürlich fatal. Mittels <code>../</code> in $_GET['site'] kann man das vorgegebene Verzeichnis verlassen und je nach Rechten auch sicherheitsrelevante Dateien ausgeben lassen. Denn: wird eine nicht-PHP-Datei mittels include eingebunden wird deren Inhalt 1:1 an den Browser gesendet.</p>

<p>Oftmals wird das Ausbrechen aus einem Verzeichnis dadurch versucht zu verhindern, indem &#8216;../&#8217; aus $_GET['site'] entfernt wird.
<pre>$save = str_replace('../', '', $_GET['site']);</pre>
Auch ganz schlecht: Aus einem <code>....//</code> würde <code>../</code>, da str_replace bereits ersetzte Teile nicht nochmals ersetzt. Außerdem kommen durch URL-Encoding und unterschiedliche Directory-Separator (Linux/Windows) noch weitere Zeichen in Frage, die es erlauben aus einem Verzeichnis auszubrechen.</p>

<p>Sehr verbreitet ist auch die Dateiendung vorzugeben:
<pre>include './includes/sites/' . $_GET['site'] . '.php';</pre>
Je nach Betriebssystem ist hier mit einem NULL-Byte (Url-Encoded: %00) die vorzeitige Terminierung des Strings möglich:
<pre>http://example.org/index.php?site=../../../../../../../etc/passwd%00</pre>
Hier muss man übrigens nicht die exakte Anzahl der nach oben zu gehenden Verzeichnisse wissen &#8211; ist man im Root-Verzeichnis angelangt, können beliebige <code>../</code> ohne Auswirkungen folgen.</p>

<p>Mit aktiviertem allow_url_fopen gibt es noch weitere Zeichen zu prüfen &#8211; das ist dann aber kein path traversal mehr und somit nicht bestandteil dieses Blog-Beitrages.</p>

<h2>Was tun?</h2>

<p>Statt str_replace sollte man besser preg_replace mit einem Pattern wie <code>#&#92;.+[/&#92;]+#</code> verwenden. Noch besser ist es natürlich von vorne herein eine Whitelist zu führen &#8211; also ein Array mit erlaubten Dateinamen und dann prüfen, ob $_GET['site'] in diesem Array vorkommt. Eine weitere Methode ist, den kanonischen, absoluten Pfad mittels realpath() zu erzeugen und dann den Beginn dieses Pfades mit dem Document-Root (bzw. dem erlaubten Verzeichnis) abzugleichen.</p>
]]></content:encoded>
			<wfw:commentRss>http://neunzehn83.de/blog/2011/06/26/php-security-heute-path-traversal/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Variablen tauschen ohne Hilfsvariable?</title>
		<link>http://neunzehn83.de/blog/2011/06/24/variablen-tauschen-ohne-hilfsvariable/</link>
		<comments>http://neunzehn83.de/blog/2011/06/24/variablen-tauschen-ohne-hilfsvariable/#comments</comments>
		<pubDate>Fri, 24 Jun 2011 20:28:29 +0000</pubDate>
		<dc:creator>Nils</dc:creator>
				<category><![CDATA[Webtechnik]]></category>

		<guid isPermaLink="false">http://neunzehn83.de/blog/?p=899</guid>
		<description><![CDATA[Pah! list($a, $b) = array($b, $a);]]></description>
			<content:encoded><![CDATA[<p>Pah!
<pre>list($a, $b) = array($b, $a);</pre></p>
]]></content:encoded>
			<wfw:commentRss>http://neunzehn83.de/blog/2011/06/24/variablen-tauschen-ohne-hilfsvariable/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Über Primary-Keys in URLs .. und sonstwo</title>
		<link>http://neunzehn83.de/blog/2011/06/19/uber-primary-keys-in-urls-und-sonstwo/</link>
		<comments>http://neunzehn83.de/blog/2011/06/19/uber-primary-keys-in-urls-und-sonstwo/#comments</comments>
		<pubDate>Sun, 19 Jun 2011 13:58:02 +0000</pubDate>
		<dc:creator>Nils</dc:creator>
				<category><![CDATA[Webtechnik]]></category>
		<category><![CDATA[»Featured]]></category>

		<guid isPermaLink="false">http://neunzehn83.de/blog/?p=576</guid>
		<description><![CDATA[Ein Primary-Key identifiziert eindeutig ein Tupel (Zeile/Reihe/Row) in einer relationalen Datenbank. Im trivialen Fall ist das eine Integer-Spalte mit auto_increment &#8211; wenn wir von MySQL reden, wie so oft hier. Eine fortlaufende Nummer also. Diese Nummer wird in Webanwendungen oft direkt an den User weitergegeben. Zum Beispiel als Teil der URL oder als Datensatznummer (z.B. [...]]]></description>
			<content:encoded><![CDATA[<p>Ein Primary-Key identifiziert eindeutig ein Tupel (Zeile/Reihe/Row) in einer relationalen Datenbank. Im trivialen Fall ist das eine Integer-Spalte mit <em>auto_increment</em> &#8211; wenn wir von MySQL reden, wie so oft hier. Eine fortlaufende Nummer also. Diese Nummer wird in Webanwendungen oft direkt an den User weitergegeben. Zum Beispiel als Teil der URL oder als Datensatznummer (z.B. Bestellnummer in einem Online-Shop).</p>

<h2>Beispiele</h2>

<h3>1. Online-Shop: $kunde erhält seine Bestellbestätigung per E-Mai mit der Bestellnummer 27.</h3>

<p style="padding-left: 30px;">Das wäre mir (als Shop-Betreiber) ein Dorn im Auge, da es Rückschlüsse auf die Anzahl der bisherigen Bestellungen zulässt. Das lässt sich noch relativ leicht beheben, indem man die auto_increment-Spalte nicht bei 1 sondern bei einer höheren Zahl beginnen lässt (<em>ALTER TABLE tbl AUTO_INCREMENT = 1000;</em>). Doch was, wenn der Kunde zwei Wochen später erneut bestellt, und eine nur wenig höhere Bestellnummer erhält? Dies lässt wiederum Rückschlüsse auf die Häufigkeit von Bestellungen im Shop zu. Daran ändert auch der Beginn bei höheren Zahlen nichts.</p>

<h3>2. URL: http://domain.com/user/123</h3>

<p style="padding-left: 30px;">Wir nehmen an, hinter dieser URL verbirgt sich ein öffentliches Benutzerprofil. Auch hier selbes Problem wie oben (Aussage über Quantität). Zudem kann man hier, aufgrund der fortlaufenden Nummer, die &#8220;Umgebung erkunden&#8221;, andere Benutzerprofile erraten oder per Script sämtliche Userprofile crawlen (for i=1; i&lt;999;i++). Hier bräuchte man also im Idealfall eine zufällig fortlaufende Zahlen/Nummernkombination, mit genügend &#8220;Lücken&#8221; um das direkte erraten anderer Profile zu <span style="text-decoration: line-through;">verhindern</span> erschweren. Dennoch sollte die User-ID natürlich so kurz wie möglich sein.</p>

<p>Das dürfte ja alles soweit bekannt sein. In den meisten Szenarien spielt das auch überhaupt keine Rolle. In einem Blog oder Forum z.B. dürfen die Post-IDs gerne den Primary-Keys entsprechen. Was aber, wenn der paranoide Webmaster Rückschlüsse nicht (oder sagen wir besser: nur erschwert) zulassen will?</p>

<h2>Anforderungen an eine ID-Verschleierung</h2>

<ul>
<li>wenig Overhead (nicht länger als nötig)</li>
<li>leicht reversibel (ohne Datenbankabfrage, ohne Speicherung der verschleierten ID in extra DB-Spalte)</li>
<li>Erhöhung des PK um 1 soll große Änderung der verschleierten ID bewirken</li>
<li>kollisionsfrei, zu 100%</li>
<li>optional: Spielraum/Lücken um das Erraten von weiteren IDs zu erschweren</li>
</ul>

<h2>Mögliche Lösungen zum Verbergen der ID</h2>

<h3>GUID</h3>

<p><a href="http://de.wikipedia.org/wiki/Globally_Unique_Identifier">GUID</a>s haben mit Sicherheit Ihre Daseinsberechtigung. In den meisten Fällen sind sie aber einfach nur des Guten zu viel. Von der Performance auf Datenbankseite möchte ich jetzt gar nicht reden. Auch nicht über die Tatsache, dass GUID in URLs schrecklich aussehen. Das Ziel muss sein, so wenig wie möglich Overhead zu erzeugen. GUID: no-go!</p>

<h3>Hashen des PKs</h3>

<p>Ein Hash resultiert immer in einem String mit fester Länge. Selbst bei sehr kleinen Primary-Keys, wäre der Hash immer gleich lang. Welch&#8217; Verschwendung! Außerdem sind Hashs nicht kollisionsfrei*, und sie lassen sich auch nicht zurückrechnen. D.h. man müsste den Hash zusätzlich zum Primary-Key speichern (noch mehr Overhead).</p>

<p>[* zugegeben, in diesem Ganzzahlbereich um den es hier geht wohl schon]</p>

<h3>Basis</h3>

<p>Man kann den Primary-Key einfach in einer anderen Basis darstellen, Base32 od. 64 zum Beispiel. Wenn man darauf besteht, dass die ID nach wie vor aus Zahlen bestehen soll, hat man Pech. Auch doof ist, dass eine Erhöhung des PK um 1 nur eine sehr kleine Änderung der verschleierten ID bewirkt. Beispiel: 12345(base10) = C1P (base32), 12346(base10) = C1Q(base32)).</p>

<h3>Random-ID</h3>

<p>Die Idee dahinter ist, im Vorfeld einen Pool an Random-IDs zu erzeugen (in einer extra DB-Tabelle). Ein UNIQUE-Key verhindert Duplikate. Beim Anlegen eines neuen Datensatzes, wird aus der Kandidaten-Tabelle dann eine Zufallszahl geholt und mit dem PK verknüpft. Das Ganze geht auch ohne vorheriges Erstellen der Kandidaten-Tabelle. Endlos-Schleife und race conditions nicht ausgeschlossen.</p>

<h3>ID Verschleiern</h3>

<p>Alles &#8220;Bekannte&#8221; scheint hier nicht zum Erfolg zu führen. Es gibt unendlich viele Methoden, durch logische Verknüpfung (<a href="http://de.wikipedia.org/wiki/Kontravalenz">XOR</a> in erster Linie) und sonstige mathematische Funktionen, Zahlen zu verschleiern. Eine einfache, auf diese Problematik passende, möchte ich hier vorstellen.</p>

<p>Man nehme <strong>einmalig</strong> eine zufällige Zahl (&#8220;secret&#8221;), die mindestens so groß ist, wie die höchste Primary ID. Der längste PK kann, wenn wir ein MySQL SMALLINT unsigned annehmen, max. 65535 sein. Unsere Zufallszahl: 99565. Diese Zahl ist systemweit immer gleich &#8211; wird also für alle späteren Ver- und Entschleierungen verwendet.</p>

<p>Das eigentliche Prozedere ist einfach: Die binäre Präsentation des PK wird umgekehrt und mit der Zufallszahl mit XOR verknüpft. Von der Zufallszahl verwenden wir nur so viele binäre Stellen, wie unser PK hat. Da durch das Umkehren und XORen führende Nullen entstehen können, stellen wir immer eine binäre 1 dem  umgekehrten Binärstring voran &#8211; sonst kommt es zu Kollisionen. Beispiel:
    PK 12      =  1100
    Flip +1    = 10011
    99565      = _1100[0010011101101] (das ist unser Secret)
    XOR        = 11111 = 31</p>

<pre><code>PK 13      =  1101
Flip +1    = 11011
99565      = _1100[0010011101101] das ist unser Secret)
XOR        = 10111 = 23
</code></pre>

<h2>PHP-Code</h2>

<pre><code>function obfuscateID($pk, $secret, $margin = 5) {
    if ($margin) {
        $pk = base_convert($pk, 10, 10-$margin);
    }

    $pk = bindec(1 . strrev(decbin($pk)));
    $pk ^= bindec(substr(decbin($secret), 0, strlen(decbin($pk))-1));

    return $pk;
}

function deObfuscateID($pk, $secret, $margin = 5) {
    $pk ^= bindec(substr(decbin($secret), 0, strlen(decbin($pk))-1));
    $pk = bindec(substr(strrev(decbin($pk)), 0, -1));

    if ($margin) {
        $pk = base_convert($pk, 10-$margin, 10);
    }

    return $pk;
}
</code></pre>

<p><a href="http://neunzehn83.de/blog/wp-content/uploads/2011/06/obfuscateID.html">Hier mit Highlighting</a>.</p>

<p>Vorsicht mit bindec() und decbin() auf 32bit-Systemen: hier sind max. Umwandlungen bis zu 4,294,967,295 möglich. Wer auf 32bit mehr braucht, muss sich diese Funktionen auf Stringbasis selber basteln. Anregung dazu findet man wie so oft in den Kommentaren des PHP-Manuals.</p>

<h2>Sicherheitsmarge</h2>

<p>Um das Erraten von Nachbarn zu erschweren, können die Lücken zwischen den PKs vergrößert werden. Das passiert mit dem Parameter $margin. Dieser muss ebenso wie das $secret systemweit immer gleich sein. Beim Zurückwandeln muss also das selbe Margin angegeben werden, wie für die Verschleierung. $margin kann ein Wert zwischen 0 und 9 haben und basiert auf einer simplen Basisumwandlung (umgerechnet wird von Basis 10 in Basis 10-$margin).</p>

<h2>Ausgabe</h2>

<p>Bis hierhin war&#8217;s ganz schön trocken. Höchste Zeit für ein wenig Praxis! Der folgende PHP-Schnipsel in Kombination mit den obigen Funktionen..</p>

<pre><code>$secret = 99565;
for ($i = 1; $i &amp;lt; 1000; $i++) {
    $camo = obfuscateID($i, $secret);  
    echo "$i: $camo &lt;br /&gt;";
}
</code></pre>

<p>ergibt folgende Ausgabe:
<pre>[..]
524: 7960
525: 15439
526: 11343
527: 13391
528: 9295
[..]
991: 19678
992: 29406
993: 21214
994: 25310
995: 30430
[..]</pre>
Äußerst nett, wie ich finde!</p>

<h2>Zahlen oder Alphanum?</h2>

<p>Um Platz zu sparen, könnte man die nach wie vor aus Zahlen bestehende, verschleierte ID in eine höhere Basis (32, 64) umwandeln. Auch mit dieser Umwandlung kann man relativ einfach für (weitere) Lücken sorgen. Da dies aber gefühlt ohnehin schon mein längster Blogbeitrag ist (in jedem Fall was die investierte Zeit angeht), lassen wir das mal so offen stehen bzw. überlasse ich es dem Leser als Übung :)</p>
]]></content:encoded>
			<wfw:commentRss>http://neunzehn83.de/blog/2011/06/19/uber-primary-keys-in-urls-und-sonstwo/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>

